Show inline previews for LSP document colors (#32816)
https://github.com/user-attachments/assets/ad0fa304-e4fb-4598-877d-c02141f35d6f Closes https://github.com/zed-industries/zed/issues/4678 Also adds the code to support `textDocument/colorPresentation` counterpart that serves as a resolve mechanism for the document colors. The resolve itself is not run though, and the editor does not accommodate color presentations in the editor yet — until a well described use case is provided. Use `lsp_document_colors` editor settings to alter the presentation and turn the feature off. Release Notes: - Start showing inline previews for LSP document colors
This commit is contained in:
parent
acb0210d26
commit
f46957584f
22 changed files with 1796 additions and 268 deletions
|
@ -1045,6 +1045,19 @@
|
|||
// Automatically update Zed. This setting may be ignored on Linux if
|
||||
// installed through a package manager.
|
||||
"auto_update": true,
|
||||
// How to render LSP `textDocument/documentColor` colors in the editor.
|
||||
//
|
||||
// Possible values:
|
||||
//
|
||||
// 1. Do not query and render document colors.
|
||||
// "lsp_document_colors": "none",
|
||||
// 2. Render document colors as inlay hints near the color text (default).
|
||||
// "lsp_document_colors": "inlay",
|
||||
// 3. Draw a border around the color text.
|
||||
// "lsp_document_colors": "border",
|
||||
// 4. Draw a background behind the color text..
|
||||
// "lsp_document_colors": "background",
|
||||
"lsp_document_colors": "inlay",
|
||||
// Diagnostics configuration.
|
||||
"diagnostics": {
|
||||
// Whether to show the project diagnostics button in the status bar.
|
||||
|
|
|
@ -323,6 +323,7 @@ impl Server {
|
|||
.add_request_handler(forward_read_only_project_request::<proto::SynchronizeBuffers>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::InlayHints>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::ResolveInlayHint>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::GetColorPresentation>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::GetCodeLens>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferByPath>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::GitGetBranches>)
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
};
|
||||
use call::ActiveCall;
|
||||
use editor::{
|
||||
Editor, RowInfo,
|
||||
DocumentColorsRenderMode, Editor, EditorSettings, RowInfo,
|
||||
actions::{
|
||||
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst,
|
||||
ExpandMacroRecursively, Redo, Rename, SelectAll, ToggleCodeActions, Undo,
|
||||
|
@ -16,7 +16,7 @@ use editor::{
|
|||
};
|
||||
use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use gpui::{TestAppContext, UpdateGlobal, VisualContext, VisualTestContext};
|
||||
use gpui::{App, Rgba, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
FakeLspAdapter,
|
||||
|
@ -1951,6 +1951,283 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
|
||||
let expected_color = Rgba {
|
||||
r: 0.33,
|
||||
g: 0.33,
|
||||
b: 0.33,
|
||||
a: 0.33,
|
||||
};
|
||||
let mut server = TestServer::start(cx_a.executor()).await;
|
||||
let executor = cx_a.executor();
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
server
|
||||
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
let active_call_b = cx_b.read(ActiveCall::global);
|
||||
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
|
||||
cx_a.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings::<EditorSettings>(cx, |settings| {
|
||||
settings.lsp_document_colors = Some(DocumentColorsRenderMode::None);
|
||||
});
|
||||
});
|
||||
});
|
||||
cx_b.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings::<EditorSettings>(cx, |settings| {
|
||||
settings.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
client_a.language_registry().add(rust_lang());
|
||||
client_b.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
|
||||
..lsp::ServerCapabilities::default()
|
||||
},
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
);
|
||||
|
||||
// Client A opens a project.
|
||||
client_a
|
||||
.fs()
|
||||
.insert_tree(
|
||||
path!("/a"),
|
||||
json!({
|
||||
"main.rs": "fn main() { a }",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
|
||||
active_call_a
|
||||
.update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Client B joins the project
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
active_call_b
|
||||
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
|
||||
executor.start_waiting();
|
||||
|
||||
// The host opens a rust file.
|
||||
let _buffer_a = project_a
|
||||
.update(cx_a, |project, cx| {
|
||||
project.open_local_buffer(path!("/a/main.rs"), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let editor_a = workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
|
||||
let requests_made = Arc::new(AtomicUsize::new(0));
|
||||
let closure_requests_made = Arc::clone(&requests_made);
|
||||
let mut color_request_handle = fake_language_server
|
||||
.set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
|
||||
let requests_made = Arc::clone(&closure_requests_made);
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
requests_made.fetch_add(1, atomic::Ordering::Release);
|
||||
Ok(vec![lsp::ColorInformation {
|
||||
range: lsp::Range {
|
||||
start: lsp::Position {
|
||||
line: 0,
|
||||
character: 0,
|
||||
},
|
||||
end: lsp::Position {
|
||||
line: 0,
|
||||
character: 1,
|
||||
},
|
||||
},
|
||||
color: lsp::Color {
|
||||
red: 0.33,
|
||||
green: 0.33,
|
||||
blue: 0.33,
|
||||
alpha: 0.33,
|
||||
},
|
||||
}])
|
||||
}
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
assert_eq!(
|
||||
0,
|
||||
requests_made.load(atomic::Ordering::Acquire),
|
||||
"Host did not enable document colors, hence should query for none"
|
||||
);
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(
|
||||
Vec::<Rgba>::new(),
|
||||
extract_color_inlays(editor, cx),
|
||||
"No query colors should result in no hints"
|
||||
);
|
||||
});
|
||||
|
||||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
let editor_b = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
|
||||
color_request_handle.next().await.unwrap();
|
||||
executor.run_until_parked();
|
||||
|
||||
assert_eq!(
|
||||
1,
|
||||
requests_made.load(atomic::Ordering::Acquire),
|
||||
"The client opened the file and got its first colors back"
|
||||
);
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(
|
||||
vec![expected_color],
|
||||
extract_color_inlays(editor, cx),
|
||||
"With document colors as inlays, color inlays should be pushed"
|
||||
);
|
||||
});
|
||||
|
||||
editor_a.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone()));
|
||||
editor.handle_input(":", window, cx);
|
||||
});
|
||||
color_request_handle.next().await.unwrap();
|
||||
executor.run_until_parked();
|
||||
assert_eq!(
|
||||
2,
|
||||
requests_made.load(atomic::Ordering::Acquire),
|
||||
"After the host edits his file, the client should request the colors again"
|
||||
);
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(
|
||||
Vec::<Rgba>::new(),
|
||||
extract_color_inlays(editor, cx),
|
||||
"Host has no colors still"
|
||||
);
|
||||
});
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(vec![expected_color], extract_color_inlays(editor, cx),);
|
||||
});
|
||||
|
||||
cx_b.update(|_, cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings::<EditorSettings>(cx, |settings| {
|
||||
settings.lsp_document_colors = Some(DocumentColorsRenderMode::Background);
|
||||
});
|
||||
});
|
||||
});
|
||||
executor.run_until_parked();
|
||||
assert_eq!(
|
||||
2,
|
||||
requests_made.load(atomic::Ordering::Acquire),
|
||||
"After the client have changed the colors settings, no extra queries should happen"
|
||||
);
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(
|
||||
Vec::<Rgba>::new(),
|
||||
extract_color_inlays(editor, cx),
|
||||
"Host is unaffected by the client's settings changes"
|
||||
);
|
||||
});
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(
|
||||
Vec::<Rgba>::new(),
|
||||
extract_color_inlays(editor, cx),
|
||||
"Client should have no colors hints, as in the settings"
|
||||
);
|
||||
});
|
||||
|
||||
cx_b.update(|_, cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings::<EditorSettings>(cx, |settings| {
|
||||
settings.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
|
||||
});
|
||||
});
|
||||
});
|
||||
executor.run_until_parked();
|
||||
assert_eq!(
|
||||
2,
|
||||
requests_made.load(atomic::Ordering::Acquire),
|
||||
"After falling back to colors as inlays, no extra LSP queries are made"
|
||||
);
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(
|
||||
Vec::<Rgba>::new(),
|
||||
extract_color_inlays(editor, cx),
|
||||
"Host is unaffected by the client's settings changes, again"
|
||||
);
|
||||
});
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(
|
||||
vec![expected_color],
|
||||
extract_color_inlays(editor, cx),
|
||||
"Client should have its color hints back"
|
||||
);
|
||||
});
|
||||
|
||||
cx_a.update(|_, cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings::<EditorSettings>(cx, |settings| {
|
||||
settings.lsp_document_colors = Some(DocumentColorsRenderMode::Border);
|
||||
});
|
||||
});
|
||||
});
|
||||
color_request_handle.next().await.unwrap();
|
||||
executor.run_until_parked();
|
||||
assert_eq!(
|
||||
3,
|
||||
requests_made.load(atomic::Ordering::Acquire),
|
||||
"After the host enables document colors, another LSP query should be made"
|
||||
);
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(
|
||||
Vec::<Rgba>::new(),
|
||||
extract_color_inlays(editor, cx),
|
||||
"Host did not configure document colors as hints hence gets nothing"
|
||||
);
|
||||
});
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(
|
||||
vec![expected_color],
|
||||
extract_color_inlays(editor, cx),
|
||||
"Client should be unaffected by the host's settings changes"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
|
||||
let mut server = TestServer::start(cx_a.executor()).await;
|
||||
|
@ -2834,6 +3111,16 @@ fn extract_hint_labels(editor: &Editor) -> Vec<String> {
|
|||
labels
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
|
||||
editor
|
||||
.all_inlays(cx)
|
||||
.into_iter()
|
||||
.filter_map(|inlay| inlay.get_color())
|
||||
.map(Rgba::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
|
||||
git::blame::BlameEntry {
|
||||
sha: sha.parse().unwrap(),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use super::*;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{
|
||||
DisplayPoint, EditorSettings, InlayId,
|
||||
DisplayPoint, EditorSettings,
|
||||
actions::{GoToDiagnostic, GoToPreviousDiagnostic, Hover, MoveToBeginning},
|
||||
display_map::{DisplayRow, Inlay},
|
||||
test::{
|
||||
|
@ -870,11 +870,11 @@ async fn test_random_diagnostics_with_inlays(cx: &mut TestAppContext, mut rng: S
|
|||
|
||||
editor.splice_inlays(
|
||||
&[],
|
||||
vec![Inlay {
|
||||
id: InlayId::InlineCompletion(post_inc(&mut next_inlay_id)),
|
||||
position: snapshot.buffer_snapshot.anchor_before(position),
|
||||
text: Rope::from(format!("Test inlay {next_inlay_id}")),
|
||||
}],
|
||||
vec![Inlay::inline_completion(
|
||||
post_inc(&mut next_inlay_id),
|
||||
snapshot.buffer_snapshot.anchor_before(position),
|
||||
format!("Test inlay {next_inlay_id}"),
|
||||
)],
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2014,11 +2014,11 @@ pub mod tests {
|
|||
map.update(cx, |map, cx| {
|
||||
map.splice_inlays(
|
||||
&[],
|
||||
vec![Inlay {
|
||||
id: InlayId::InlineCompletion(0),
|
||||
position: buffer_snapshot.anchor_after(0),
|
||||
text: "\n".into(),
|
||||
}],
|
||||
vec![Inlay::inline_completion(
|
||||
0,
|
||||
buffer_snapshot.anchor_after(0),
|
||||
"\n",
|
||||
)],
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{HighlightStyles, InlayId};
|
||||
use collections::BTreeSet;
|
||||
use gpui::{Hsla, Rgba};
|
||||
use language::{Chunk, Edit, Point, TextSummary};
|
||||
use multi_buffer::{
|
||||
Anchor, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, RowInfo, ToOffset,
|
||||
|
@ -39,6 +40,7 @@ pub struct Inlay {
|
|||
pub id: InlayId,
|
||||
pub position: Anchor,
|
||||
pub text: text::Rope,
|
||||
color: Option<Hsla>,
|
||||
}
|
||||
|
||||
impl Inlay {
|
||||
|
@ -54,6 +56,26 @@ impl Inlay {
|
|||
id: InlayId::Hint(id),
|
||||
position,
|
||||
text: text.into(),
|
||||
color: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn mock_hint(id: usize, position: Anchor, text: impl Into<Rope>) -> Self {
|
||||
Self {
|
||||
id: InlayId::Hint(id),
|
||||
position,
|
||||
text: text.into(),
|
||||
color: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color(id: usize, position: Anchor, color: Rgba) -> Self {
|
||||
Self {
|
||||
id: InlayId::Color(id),
|
||||
position,
|
||||
text: Rope::from("◼"),
|
||||
color: Some(Hsla::from(color)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,16 +84,23 @@ impl Inlay {
|
|||
id: InlayId::InlineCompletion(id),
|
||||
position,
|
||||
text: text.into(),
|
||||
color: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debugger_hint<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
|
||||
pub fn debugger<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
|
||||
Self {
|
||||
id: InlayId::DebuggerValue(id),
|
||||
position,
|
||||
text: text.into(),
|
||||
color: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn get_color(&self) -> Option<Hsla> {
|
||||
self.color
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Item for Transform {
|
||||
|
@ -296,6 +325,14 @@ impl<'a> Iterator for InlayChunks<'a> {
|
|||
}
|
||||
InlayId::Hint(_) => self.highlight_styles.inlay_hint,
|
||||
InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint,
|
||||
InlayId::Color(_) => match inlay.color {
|
||||
Some(color) => {
|
||||
let style = self.highlight_styles.inlay_hint.get_or_insert_default();
|
||||
style.color = Some(color);
|
||||
Some(*style)
|
||||
}
|
||||
None => self.highlight_styles.inlay_hint,
|
||||
},
|
||||
};
|
||||
let next_inlay_highlight_endpoint;
|
||||
let offset_in_inlay = self.output_offset - self.transforms.start().0;
|
||||
|
@ -634,24 +671,24 @@ impl InlayMap {
|
|||
.take(len)
|
||||
.collect::<String>();
|
||||
|
||||
let inlay_id = if i % 2 == 0 {
|
||||
InlayId::Hint(post_inc(next_inlay_id))
|
||||
let next_inlay = if i % 2 == 0 {
|
||||
Inlay::mock_hint(
|
||||
post_inc(next_inlay_id),
|
||||
snapshot.buffer.anchor_at(position, bias),
|
||||
text.clone(),
|
||||
)
|
||||
} else {
|
||||
InlayId::InlineCompletion(post_inc(next_inlay_id))
|
||||
Inlay::inline_completion(
|
||||
post_inc(next_inlay_id),
|
||||
snapshot.buffer.anchor_at(position, bias),
|
||||
text.clone(),
|
||||
)
|
||||
};
|
||||
let inlay_id = next_inlay.id;
|
||||
log::info!(
|
||||
"creating inlay {:?} at buffer offset {} with bias {:?} and text {:?}",
|
||||
inlay_id,
|
||||
position,
|
||||
bias,
|
||||
text
|
||||
"creating inlay {inlay_id:?} at buffer offset {position} with bias {bias:?} and text {text:?}"
|
||||
);
|
||||
|
||||
to_insert.push(Inlay {
|
||||
id: inlay_id,
|
||||
position: snapshot.buffer.anchor_at(position, bias),
|
||||
text: text.into(),
|
||||
});
|
||||
to_insert.push(next_inlay);
|
||||
} else {
|
||||
to_remove.push(
|
||||
self.inlays
|
||||
|
@ -1183,11 +1220,11 @@ mod tests {
|
|||
|
||||
let (inlay_snapshot, _) = inlay_map.splice(
|
||||
&[],
|
||||
vec![Inlay {
|
||||
id: InlayId::Hint(post_inc(&mut next_inlay_id)),
|
||||
position: buffer.read(cx).snapshot(cx).anchor_after(3),
|
||||
text: "|123|".into(),
|
||||
}],
|
||||
vec![Inlay::mock_hint(
|
||||
post_inc(&mut next_inlay_id),
|
||||
buffer.read(cx).snapshot(cx).anchor_after(3),
|
||||
"|123|",
|
||||
)],
|
||||
);
|
||||
assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
|
||||
assert_eq!(
|
||||
|
@ -1260,16 +1297,16 @@ mod tests {
|
|||
let (inlay_snapshot, _) = inlay_map.splice(
|
||||
&[],
|
||||
vec![
|
||||
Inlay {
|
||||
id: InlayId::Hint(post_inc(&mut next_inlay_id)),
|
||||
position: buffer.read(cx).snapshot(cx).anchor_before(3),
|
||||
text: "|123|".into(),
|
||||
},
|
||||
Inlay {
|
||||
id: InlayId::InlineCompletion(post_inc(&mut next_inlay_id)),
|
||||
position: buffer.read(cx).snapshot(cx).anchor_after(3),
|
||||
text: "|456|".into(),
|
||||
},
|
||||
Inlay::mock_hint(
|
||||
post_inc(&mut next_inlay_id),
|
||||
buffer.read(cx).snapshot(cx).anchor_before(3),
|
||||
"|123|",
|
||||
),
|
||||
Inlay::inline_completion(
|
||||
post_inc(&mut next_inlay_id),
|
||||
buffer.read(cx).snapshot(cx).anchor_after(3),
|
||||
"|456|",
|
||||
),
|
||||
],
|
||||
);
|
||||
assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
|
||||
|
@ -1475,21 +1512,21 @@ mod tests {
|
|||
let (inlay_snapshot, _) = inlay_map.splice(
|
||||
&[],
|
||||
vec![
|
||||
Inlay {
|
||||
id: InlayId::Hint(post_inc(&mut next_inlay_id)),
|
||||
position: buffer.read(cx).snapshot(cx).anchor_before(0),
|
||||
text: "|123|\n".into(),
|
||||
},
|
||||
Inlay {
|
||||
id: InlayId::Hint(post_inc(&mut next_inlay_id)),
|
||||
position: buffer.read(cx).snapshot(cx).anchor_before(4),
|
||||
text: "|456|".into(),
|
||||
},
|
||||
Inlay {
|
||||
id: InlayId::InlineCompletion(post_inc(&mut next_inlay_id)),
|
||||
position: buffer.read(cx).snapshot(cx).anchor_before(7),
|
||||
text: "\n|567|\n".into(),
|
||||
},
|
||||
Inlay::mock_hint(
|
||||
post_inc(&mut next_inlay_id),
|
||||
buffer.read(cx).snapshot(cx).anchor_before(0),
|
||||
"|123|\n",
|
||||
),
|
||||
Inlay::mock_hint(
|
||||
post_inc(&mut next_inlay_id),
|
||||
buffer.read(cx).snapshot(cx).anchor_before(4),
|
||||
"|456|",
|
||||
),
|
||||
Inlay::inline_completion(
|
||||
post_inc(&mut next_inlay_id),
|
||||
buffer.read(cx).snapshot(cx).anchor_before(7),
|
||||
"\n|567|\n",
|
||||
),
|
||||
],
|
||||
);
|
||||
assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
|
||||
|
|
|
@ -29,6 +29,7 @@ mod inlay_hint_cache;
|
|||
pub mod items;
|
||||
mod jsx_tag_auto_close;
|
||||
mod linked_editing_ranges;
|
||||
mod lsp_colors;
|
||||
mod lsp_ext;
|
||||
mod mouse_context_menu;
|
||||
pub mod movement;
|
||||
|
@ -63,8 +64,8 @@ use dap::TelemetrySpawnLocation;
|
|||
use display_map::*;
|
||||
pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
|
||||
pub use editor_settings::{
|
||||
CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
|
||||
SearchSettings, ShowScrollbar,
|
||||
CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
|
||||
ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowScrollbar,
|
||||
};
|
||||
use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
|
||||
pub use editor_settings_controls::*;
|
||||
|
@ -79,6 +80,7 @@ use futures::{
|
|||
stream::FuturesUnordered,
|
||||
};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use lsp_colors::LspColorData;
|
||||
|
||||
use ::git::blame::BlameEntry;
|
||||
use ::git::{Restore, blame::ParsedCommitMessage};
|
||||
|
@ -109,10 +111,9 @@ pub use items::MAX_TAB_TITLE_LEN;
|
|||
use itertools::Itertools;
|
||||
use language::{
|
||||
AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
|
||||
CursorShape, DiagnosticEntry, DiagnosticSourceKind, DiffOptions, DocumentationConfig,
|
||||
EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, Language,
|
||||
OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
|
||||
WordsQuery,
|
||||
CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
|
||||
EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
|
||||
Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
|
||||
language_settings::{
|
||||
self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
|
||||
all_language_settings, language_settings,
|
||||
|
@ -125,7 +126,7 @@ use markdown::Markdown;
|
|||
use mouse_context_menu::MouseContextMenu;
|
||||
use persistence::DB;
|
||||
use project::{
|
||||
BreakpointWithPosition, CompletionResponse, LspPullDiagnostics, ProjectPath, PulledDiagnostics,
|
||||
BreakpointWithPosition, CompletionResponse, ProjectPath,
|
||||
debugger::{
|
||||
breakpoint_store::{
|
||||
BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
|
||||
|
@ -274,16 +275,19 @@ impl InlineValueCache {
|
|||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum InlayId {
|
||||
InlineCompletion(usize),
|
||||
Hint(usize),
|
||||
DebuggerValue(usize),
|
||||
// LSP
|
||||
Hint(usize),
|
||||
Color(usize),
|
||||
}
|
||||
|
||||
impl InlayId {
|
||||
fn id(&self) -> usize {
|
||||
match self {
|
||||
Self::InlineCompletion(id) => *id,
|
||||
Self::Hint(id) => *id,
|
||||
Self::DebuggerValue(id) => *id,
|
||||
Self::Hint(id) => *id,
|
||||
Self::Color(id) => *id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1134,6 +1138,8 @@ pub struct Editor {
|
|||
inline_value_cache: InlineValueCache,
|
||||
selection_drag_state: SelectionDragState,
|
||||
drag_and_drop_selection_enabled: bool,
|
||||
next_color_inlay_id: usize,
|
||||
colors: Option<LspColorData>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
|
||||
|
@ -1795,13 +1801,13 @@ impl Editor {
|
|||
editor
|
||||
.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
|
||||
}
|
||||
project::Event::LanguageServerAdded(..)
|
||||
| project::Event::LanguageServerRemoved(..) => {
|
||||
project::Event::LanguageServerAdded(server_id, ..)
|
||||
| project::Event::LanguageServerRemoved(server_id) => {
|
||||
if editor.tasks_update_task.is_none() {
|
||||
editor.tasks_update_task =
|
||||
Some(editor.refresh_runnables(window, cx));
|
||||
}
|
||||
editor.pull_diagnostics(None, window, cx);
|
||||
editor.update_lsp_data(false, Some(*server_id), None, window, cx);
|
||||
}
|
||||
project::Event::SnippetEdit(id, snippet_edits) => {
|
||||
if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
|
||||
|
@ -2070,6 +2076,8 @@ impl Editor {
|
|||
],
|
||||
tasks_update_task: None,
|
||||
pull_diagnostics_task: Task::ready(()),
|
||||
colors: None,
|
||||
next_color_inlay_id: 0,
|
||||
linked_edit_ranges: Default::default(),
|
||||
in_project_search: false,
|
||||
previous_search_ranges: None,
|
||||
|
@ -2211,7 +2219,8 @@ impl Editor {
|
|||
|
||||
editor.minimap =
|
||||
editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
|
||||
editor.pull_diagnostics(None, window, cx);
|
||||
editor.colors = Some(LspColorData::new(cx));
|
||||
editor.update_lsp_data(false, None, None, window, cx);
|
||||
}
|
||||
|
||||
editor.report_editor_event("Editor Opened", None, cx);
|
||||
|
@ -4899,6 +4908,15 @@ impl Editor {
|
|||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
|
||||
self.display_map
|
||||
.read(cx)
|
||||
.current_inlays()
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
|
||||
if self.semantics_provider.is_none() || !self.mode.is_full() {
|
||||
return;
|
||||
|
@ -16241,8 +16259,14 @@ impl Editor {
|
|||
let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
|
||||
buffers
|
||||
.into_iter()
|
||||
.flat_map(|buffer| {
|
||||
Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
|
||||
.filter_map(|buffer| {
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.lsp_store().update(cx, |lsp_store, cx| {
|
||||
lsp_store.pull_diagnostics_for_buffer(buffer, cx)
|
||||
})
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
}) else {
|
||||
|
@ -19066,7 +19090,7 @@ impl Editor {
|
|||
.into_iter()
|
||||
.flatten()
|
||||
.for_each(|hint| {
|
||||
let inlay = Inlay::debugger_hint(
|
||||
let inlay = Inlay::debugger(
|
||||
post_inc(&mut editor.next_inlay_id),
|
||||
Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
|
||||
hint.text(),
|
||||
|
@ -19117,17 +19141,15 @@ impl Editor {
|
|||
.register_buffer_with_language_servers(&edited_buffer, cx)
|
||||
});
|
||||
});
|
||||
if edited_buffer.read(cx).file().is_some() {
|
||||
self.pull_diagnostics(
|
||||
Some(edited_buffer.read(cx).remote_id()),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
cx.emit(EditorEvent::BufferEdited);
|
||||
cx.emit(SearchEvent::MatchesInvalidated);
|
||||
|
||||
if let Some(buffer) = edited_buffer {
|
||||
self.update_lsp_data(true, None, Some(buffer.read(cx).remote_id()), window, cx);
|
||||
}
|
||||
|
||||
if *singleton_buffer_edited {
|
||||
if let Some(buffer) = edited_buffer {
|
||||
if buffer.read(cx).file().is_none() {
|
||||
|
@ -19190,6 +19212,7 @@ impl Editor {
|
|||
.detach();
|
||||
}
|
||||
}
|
||||
self.update_lsp_data(false, None, Some(buffer_id), window, cx);
|
||||
cx.emit(EditorEvent::ExcerptsAdded {
|
||||
buffer: buffer.clone(),
|
||||
predecessor: *predecessor,
|
||||
|
@ -19209,7 +19232,7 @@ impl Editor {
|
|||
cx.emit(EditorEvent::ExcerptsRemoved {
|
||||
ids: ids.clone(),
|
||||
removed_buffer_ids: removed_buffer_ids.clone(),
|
||||
})
|
||||
});
|
||||
}
|
||||
multi_buffer::Event::ExcerptsEdited {
|
||||
excerpt_ids,
|
||||
|
@ -19220,7 +19243,7 @@ impl Editor {
|
|||
});
|
||||
cx.emit(EditorEvent::ExcerptsEdited {
|
||||
ids: excerpt_ids.clone(),
|
||||
})
|
||||
});
|
||||
}
|
||||
multi_buffer::Event::ExcerptsExpanded { ids } => {
|
||||
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
|
||||
|
@ -19366,6 +19389,15 @@ impl Editor {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
|
||||
colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
|
||||
}) {
|
||||
if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
|
||||
self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
|
||||
}
|
||||
self.refresh_colors(true, None, None, window, cx);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
|
@ -20251,6 +20283,18 @@ impl Editor {
|
|||
|
||||
self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
|
||||
}
|
||||
|
||||
fn update_lsp_data(
|
||||
&mut self,
|
||||
update_on_edit: bool,
|
||||
for_server_id: Option<LanguageServerId>,
|
||||
for_buffer: Option<BufferId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'_, Self>,
|
||||
) {
|
||||
self.pull_diagnostics(for_buffer, window, cx);
|
||||
self.refresh_colors(update_on_edit, for_server_id, for_buffer, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn vim_enabled(cx: &App) -> bool {
|
||||
|
@ -20937,12 +20981,6 @@ pub trait SemanticsProvider {
|
|||
new_name: String,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Result<ProjectTransaction>>>;
|
||||
|
||||
fn pull_diagnostics_for_buffer(
|
||||
&self,
|
||||
buffer: Entity<Buffer>,
|
||||
cx: &mut App,
|
||||
) -> Task<anyhow::Result<()>>;
|
||||
}
|
||||
|
||||
pub trait CompletionProvider {
|
||||
|
@ -21460,85 +21498,6 @@ impl SemanticsProvider for Entity<Project> {
|
|||
project.perform_rename(buffer.clone(), position, new_name, cx)
|
||||
}))
|
||||
}
|
||||
|
||||
fn pull_diagnostics_for_buffer(
|
||||
&self,
|
||||
buffer: Entity<Buffer>,
|
||||
cx: &mut App,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
let diagnostics = self.update(cx, |project, cx| {
|
||||
project
|
||||
.lsp_store()
|
||||
.update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
|
||||
});
|
||||
let project = self.clone();
|
||||
cx.spawn(async move |cx| {
|
||||
let diagnostics = diagnostics.await.context("pulling diagnostics")?;
|
||||
project.update(cx, |project, cx| {
|
||||
project.lsp_store().update(cx, |lsp_store, cx| {
|
||||
for diagnostics_set in diagnostics {
|
||||
let LspPullDiagnostics::Response {
|
||||
server_id,
|
||||
uri,
|
||||
diagnostics,
|
||||
} = diagnostics_set
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let adapter = lsp_store.language_server_adapter_for_id(server_id);
|
||||
let disk_based_sources = adapter
|
||||
.as_ref()
|
||||
.map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
|
||||
.unwrap_or(&[]);
|
||||
match diagnostics {
|
||||
PulledDiagnostics::Unchanged { result_id } => {
|
||||
lsp_store
|
||||
.merge_diagnostics(
|
||||
server_id,
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: uri.clone(),
|
||||
diagnostics: Vec::new(),
|
||||
version: None,
|
||||
},
|
||||
Some(result_id),
|
||||
DiagnosticSourceKind::Pulled,
|
||||
disk_based_sources,
|
||||
|_, _| true,
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
}
|
||||
PulledDiagnostics::Changed {
|
||||
diagnostics,
|
||||
result_id,
|
||||
} => {
|
||||
lsp_store
|
||||
.merge_diagnostics(
|
||||
server_id,
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: uri.clone(),
|
||||
diagnostics,
|
||||
version: None,
|
||||
},
|
||||
result_id,
|
||||
DiagnosticSourceKind::Pulled,
|
||||
disk_based_sources,
|
||||
|old_diagnostic, _| match old_diagnostic.source_kind {
|
||||
DiagnosticSourceKind::Pulled => false,
|
||||
DiagnosticSourceKind::Other
|
||||
| DiagnosticSourceKind::Pushed => true,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn inlay_hint_settings(
|
||||
|
|
|
@ -50,6 +50,22 @@ pub struct EditorSettings {
|
|||
pub diagnostics_max_severity: Option<DiagnosticSeverity>,
|
||||
pub inline_code_actions: bool,
|
||||
pub drag_and_drop_selection: bool,
|
||||
pub lsp_document_colors: DocumentColorsRenderMode,
|
||||
}
|
||||
|
||||
/// How to render LSP `textDocument/documentColor` colors in the editor.
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DocumentColorsRenderMode {
|
||||
/// Do not query and render document colors.
|
||||
None,
|
||||
/// Render document colors as inlay hints near the color text.
|
||||
#[default]
|
||||
Inlay,
|
||||
/// Draw a border around the color text.
|
||||
Border,
|
||||
/// Draw a background behind the color text.
|
||||
Background,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
|
@ -521,6 +537,11 @@ pub struct EditorSettingsContent {
|
|||
///
|
||||
/// Default: true
|
||||
pub drag_and_drop_selection: Option<bool>,
|
||||
|
||||
/// How to render LSP `textDocument/documentColor` colors in the editor.
|
||||
///
|
||||
/// Default: [`DocumentColorsRenderMode::Inlay`]
|
||||
pub lsp_document_colors: Option<DocumentColorsRenderMode>,
|
||||
}
|
||||
|
||||
// Toolbar related settings
|
||||
|
|
|
@ -22,8 +22,8 @@ use indoc::indoc;
|
|||
use language::{
|
||||
BracketPairConfig,
|
||||
Capability::ReadWrite,
|
||||
FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
|
||||
Override, Point,
|
||||
DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
|
||||
LanguageName, Override, Point,
|
||||
language_settings::{
|
||||
AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
|
||||
LanguageSettingsContent, LspInsertMode, PrettierSettings,
|
||||
|
|
|
@ -16,8 +16,9 @@ use crate::{
|
|||
ToDisplayPoint,
|
||||
},
|
||||
editor_settings::{
|
||||
CurrentLineHighlight, DoubleClickInMultibuffer, MinimapThumb, MinimapThumbBorder,
|
||||
ScrollBeyondLastLine, ScrollbarAxes, ScrollbarDiagnostics, ShowMinimap, ShowScrollbar,
|
||||
CurrentLineHighlight, DocumentColorsRenderMode, DoubleClickInMultibuffer, MinimapThumb,
|
||||
MinimapThumbBorder, ScrollBeyondLastLine, ScrollbarAxes, ScrollbarDiagnostics, ShowMinimap,
|
||||
ShowScrollbar,
|
||||
},
|
||||
git::blame::{BlameRenderer, GitBlame, GlobalBlameRenderer},
|
||||
hover_popover::{
|
||||
|
@ -5676,6 +5677,7 @@ impl EditorElement {
|
|||
|
||||
self.paint_lines_background(layout, window, cx);
|
||||
let invisible_display_ranges = self.paint_highlights(layout, window);
|
||||
self.paint_document_colors(layout, window);
|
||||
self.paint_lines(&invisible_display_ranges, layout, window, cx);
|
||||
self.paint_redactions(layout, window);
|
||||
self.paint_cursors(layout, window, cx);
|
||||
|
@ -5703,6 +5705,7 @@ impl EditorElement {
|
|||
for (range, color) in &layout.highlighted_ranges {
|
||||
self.paint_highlighted_range(
|
||||
range.clone(),
|
||||
true,
|
||||
*color,
|
||||
Pixels::ZERO,
|
||||
line_end_overshoot,
|
||||
|
@ -5717,6 +5720,7 @@ impl EditorElement {
|
|||
for selection in selections.iter() {
|
||||
self.paint_highlighted_range(
|
||||
selection.range.clone(),
|
||||
true,
|
||||
player_color.selection,
|
||||
corner_radius,
|
||||
corner_radius * 2.,
|
||||
|
@ -5792,6 +5796,7 @@ impl EditorElement {
|
|||
for range in layout.redacted_ranges.iter() {
|
||||
self.paint_highlighted_range(
|
||||
range.clone(),
|
||||
true,
|
||||
redaction_color.into(),
|
||||
Pixels::ZERO,
|
||||
line_end_overshoot,
|
||||
|
@ -5802,6 +5807,48 @@ impl EditorElement {
|
|||
});
|
||||
}
|
||||
|
||||
fn paint_document_colors(&self, layout: &mut EditorLayout, window: &mut Window) {
|
||||
let Some((colors_render_mode, image_colors)) = &layout.document_colors else {
|
||||
return;
|
||||
};
|
||||
if image_colors.is_empty()
|
||||
|| colors_render_mode == &DocumentColorsRenderMode::None
|
||||
|| colors_render_mode == &DocumentColorsRenderMode::Inlay
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let line_end_overshoot = layout.line_end_overshoot();
|
||||
|
||||
for (range, color) in image_colors {
|
||||
match colors_render_mode {
|
||||
DocumentColorsRenderMode::Inlay | DocumentColorsRenderMode::None => return,
|
||||
DocumentColorsRenderMode::Background => {
|
||||
self.paint_highlighted_range(
|
||||
range.clone(),
|
||||
true,
|
||||
*color,
|
||||
Pixels::ZERO,
|
||||
line_end_overshoot,
|
||||
layout,
|
||||
window,
|
||||
);
|
||||
}
|
||||
DocumentColorsRenderMode::Border => {
|
||||
self.paint_highlighted_range(
|
||||
range.clone(),
|
||||
false,
|
||||
*color,
|
||||
Pixels::ZERO,
|
||||
line_end_overshoot,
|
||||
layout,
|
||||
window,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_cursors(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
|
||||
for cursor in &mut layout.visible_cursors {
|
||||
cursor.paint(layout.content_origin, window, cx);
|
||||
|
@ -6240,6 +6287,7 @@ impl EditorElement {
|
|||
fn paint_highlighted_range(
|
||||
&self,
|
||||
range: Range<DisplayPoint>,
|
||||
fill: bool,
|
||||
color: Hsla,
|
||||
corner_radius: Pixels,
|
||||
line_end_overshoot: Pixels,
|
||||
|
@ -6290,7 +6338,7 @@ impl EditorElement {
|
|||
.collect(),
|
||||
};
|
||||
|
||||
highlighted_range.paint(layout.position_map.text_hitbox.bounds, window);
|
||||
highlighted_range.paint(fill, layout.position_map.text_hitbox.bounds, window);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8061,6 +8109,12 @@ impl Element for EditorElement {
|
|||
cx,
|
||||
);
|
||||
|
||||
let document_colors = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.colors
|
||||
.as_ref()
|
||||
.map(|colors| colors.editor_display_highlights(&snapshot));
|
||||
let redacted_ranges = self.editor.read(cx).redacted_ranges(
|
||||
start_anchor..end_anchor,
|
||||
&snapshot.display_snapshot,
|
||||
|
@ -8808,6 +8862,7 @@ impl Element for EditorElement {
|
|||
highlighted_ranges,
|
||||
highlighted_gutter_ranges,
|
||||
redacted_ranges,
|
||||
document_colors,
|
||||
line_elements,
|
||||
line_numbers,
|
||||
blamed_display_rows,
|
||||
|
@ -9013,6 +9068,7 @@ pub struct EditorLayout {
|
|||
tab_invisible: ShapedLine,
|
||||
space_invisible: ShapedLine,
|
||||
sticky_buffer_header: Option<AnyElement>,
|
||||
document_colors: Option<(DocumentColorsRenderMode, Vec<(Range<DisplayPoint>, Hsla)>)>,
|
||||
}
|
||||
|
||||
impl EditorLayout {
|
||||
|
@ -9735,17 +9791,18 @@ pub struct HighlightedRangeLine {
|
|||
}
|
||||
|
||||
impl HighlightedRange {
|
||||
pub fn paint(&self, bounds: Bounds<Pixels>, window: &mut Window) {
|
||||
pub fn paint(&self, fill: bool, bounds: Bounds<Pixels>, window: &mut Window) {
|
||||
if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
|
||||
self.paint_lines(self.start_y, &self.lines[0..1], bounds, window);
|
||||
self.paint_lines(self.start_y, &self.lines[0..1], fill, bounds, window);
|
||||
self.paint_lines(
|
||||
self.start_y + self.line_height,
|
||||
&self.lines[1..],
|
||||
fill,
|
||||
bounds,
|
||||
window,
|
||||
);
|
||||
} else {
|
||||
self.paint_lines(self.start_y, &self.lines, bounds, window);
|
||||
self.paint_lines(self.start_y, &self.lines, fill, bounds, window);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9753,6 +9810,7 @@ impl HighlightedRange {
|
|||
&self,
|
||||
start_y: Pixels,
|
||||
lines: &[HighlightedRangeLine],
|
||||
fill: bool,
|
||||
_bounds: Bounds<Pixels>,
|
||||
window: &mut Window,
|
||||
) {
|
||||
|
@ -9779,7 +9837,11 @@ impl HighlightedRange {
|
|||
};
|
||||
|
||||
let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
|
||||
let mut builder = gpui::PathBuilder::fill();
|
||||
let mut builder = if fill {
|
||||
gpui::PathBuilder::fill()
|
||||
} else {
|
||||
gpui::PathBuilder::stroke(px(1.))
|
||||
};
|
||||
builder.move_to(first_top_right - top_curve_width);
|
||||
builder.curve_to(first_top_right + curve_height, first_top_right);
|
||||
|
||||
|
|
358
crates/editor/src/lsp_colors.rs
Normal file
358
crates/editor/src/lsp_colors.rs
Normal file
|
@ -0,0 +1,358 @@
|
|||
use std::{cmp, ops::Range};
|
||||
|
||||
use collections::HashMap;
|
||||
use futures::future::join_all;
|
||||
use gpui::{Hsla, Rgba};
|
||||
use language::point_from_lsp;
|
||||
use lsp::LanguageServerId;
|
||||
use multi_buffer::Anchor;
|
||||
use project::DocumentColor;
|
||||
use settings::Settings as _;
|
||||
use text::{Bias, BufferId, OffsetRangeExt as _};
|
||||
use ui::{App, Context, Window};
|
||||
use util::post_inc;
|
||||
|
||||
use crate::{
|
||||
DisplayPoint, Editor, EditorSettings, EditorSnapshot, InlayId, InlaySplice, RangeToAnchorExt,
|
||||
display_map::Inlay, editor_settings::DocumentColorsRenderMode,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct LspColorData {
|
||||
colors: Vec<(Range<Anchor>, DocumentColor, InlayId)>,
|
||||
inlay_colors: HashMap<InlayId, usize>,
|
||||
render_mode: DocumentColorsRenderMode,
|
||||
}
|
||||
|
||||
impl LspColorData {
|
||||
pub fn new(cx: &App) -> Self {
|
||||
Self {
|
||||
colors: Vec::new(),
|
||||
inlay_colors: HashMap::default(),
|
||||
render_mode: EditorSettings::get_global(cx).lsp_document_colors,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_mode_updated(
|
||||
&mut self,
|
||||
new_render_mode: DocumentColorsRenderMode,
|
||||
) -> Option<InlaySplice> {
|
||||
if self.render_mode == new_render_mode {
|
||||
return None;
|
||||
}
|
||||
self.render_mode = new_render_mode;
|
||||
match new_render_mode {
|
||||
DocumentColorsRenderMode::Inlay => Some(InlaySplice {
|
||||
to_remove: Vec::new(),
|
||||
to_insert: self
|
||||
.colors
|
||||
.iter()
|
||||
.map(|(range, color, id)| {
|
||||
Inlay::color(
|
||||
id.id(),
|
||||
range.start,
|
||||
Rgba {
|
||||
r: color.color.red,
|
||||
g: color.color.green,
|
||||
b: color.color.blue,
|
||||
a: color.color.alpha,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
DocumentColorsRenderMode::None => {
|
||||
self.colors.clear();
|
||||
Some(InlaySplice {
|
||||
to_remove: self.inlay_colors.drain().map(|(id, _)| id).collect(),
|
||||
to_insert: Vec::new(),
|
||||
})
|
||||
}
|
||||
DocumentColorsRenderMode::Border | DocumentColorsRenderMode::Background => {
|
||||
Some(InlaySplice {
|
||||
to_remove: self.inlay_colors.drain().map(|(id, _)| id).collect(),
|
||||
to_insert: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_colors(&mut self, colors: Vec<(Range<Anchor>, DocumentColor, InlayId)>) -> bool {
|
||||
if self.colors == colors {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.inlay_colors = colors
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, (_, _, id))| (*id, i))
|
||||
.collect();
|
||||
self.colors = colors;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn editor_display_highlights(
|
||||
&self,
|
||||
snapshot: &EditorSnapshot,
|
||||
) -> (DocumentColorsRenderMode, Vec<(Range<DisplayPoint>, Hsla)>) {
|
||||
let render_mode = self.render_mode;
|
||||
let highlights = if render_mode == DocumentColorsRenderMode::None
|
||||
|| render_mode == DocumentColorsRenderMode::Inlay
|
||||
{
|
||||
Vec::new()
|
||||
} else {
|
||||
self.colors
|
||||
.iter()
|
||||
.map(|(range, color, _)| {
|
||||
let display_range = range.clone().to_display_points(snapshot);
|
||||
let color = Hsla::from(Rgba {
|
||||
r: color.color.red,
|
||||
g: color.color.green,
|
||||
b: color.color.blue,
|
||||
a: color.color.alpha,
|
||||
});
|
||||
(display_range, color)
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
(render_mode, highlights)
|
||||
}
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
pub(super) fn refresh_colors(
|
||||
&mut self,
|
||||
update_on_edit: bool,
|
||||
for_server_id: Option<LanguageServerId>,
|
||||
buffer_id: Option<BufferId>,
|
||||
_: &Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if !self.mode().is_full() {
|
||||
return;
|
||||
}
|
||||
let Some(project) = self.project.clone() else {
|
||||
return;
|
||||
};
|
||||
if self
|
||||
.colors
|
||||
.as_ref()
|
||||
.is_none_or(|colors| colors.render_mode == DocumentColorsRenderMode::None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let all_colors_task = project.read(cx).lsp_store().update(cx, |lsp_store, cx| {
|
||||
self.buffer()
|
||||
.update(cx, |multi_buffer, cx| {
|
||||
multi_buffer
|
||||
.all_buffers()
|
||||
.into_iter()
|
||||
.filter(|editor_buffer| {
|
||||
buffer_id.is_none_or(|buffer_id| {
|
||||
buffer_id == editor_buffer.read(cx).remote_id()
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.into_iter()
|
||||
.filter_map(|buffer| {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
let colors_task =
|
||||
lsp_store.document_colors(update_on_edit, for_server_id, buffer, cx)?;
|
||||
Some(async move { (buffer_id, colors_task.await) })
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
cx.spawn(async move |editor, cx| {
|
||||
let all_colors = join_all(all_colors_task).await;
|
||||
let Ok((multi_buffer_snapshot, editor_excerpts)) = editor.update(cx, |editor, cx| {
|
||||
let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let editor_excerpts = multi_buffer_snapshot.excerpts().fold(
|
||||
HashMap::default(),
|
||||
|mut acc, (excerpt_id, buffer_snapshot, excerpt_range)| {
|
||||
let excerpt_data = acc
|
||||
.entry(buffer_snapshot.remote_id())
|
||||
.or_insert_with(Vec::new);
|
||||
let excerpt_point_range =
|
||||
excerpt_range.context.to_point_utf16(&buffer_snapshot);
|
||||
excerpt_data.push((
|
||||
excerpt_id,
|
||||
buffer_snapshot.clone(),
|
||||
excerpt_point_range,
|
||||
));
|
||||
acc
|
||||
},
|
||||
);
|
||||
(multi_buffer_snapshot, editor_excerpts)
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut new_editor_colors = Vec::<(Range<Anchor>, DocumentColor)>::new();
|
||||
for (buffer_id, colors) in all_colors {
|
||||
let Some(excerpts) = editor_excerpts.get(&buffer_id) else {
|
||||
continue;
|
||||
};
|
||||
match colors {
|
||||
Ok(colors) => {
|
||||
for color in colors {
|
||||
let color_start = point_from_lsp(color.lsp_range.start);
|
||||
let color_end = point_from_lsp(color.lsp_range.end);
|
||||
|
||||
for (excerpt_id, buffer_snapshot, excerpt_range) in excerpts {
|
||||
if !excerpt_range.contains(&color_start.0)
|
||||
|| !excerpt_range.contains(&color_end.0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some(color_start_anchor) = multi_buffer_snapshot
|
||||
.anchor_in_excerpt(
|
||||
*excerpt_id,
|
||||
buffer_snapshot.anchor_before(
|
||||
buffer_snapshot
|
||||
.clip_point_utf16(color_start, Bias::Left),
|
||||
),
|
||||
)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(color_end_anchor) = multi_buffer_snapshot
|
||||
.anchor_in_excerpt(
|
||||
*excerpt_id,
|
||||
buffer_snapshot.anchor_after(
|
||||
buffer_snapshot
|
||||
.clip_point_utf16(color_end, Bias::Right),
|
||||
),
|
||||
)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let (Ok(i) | Err(i)) =
|
||||
new_editor_colors.binary_search_by(|(probe, _)| {
|
||||
probe
|
||||
.start
|
||||
.cmp(&color_start_anchor, &multi_buffer_snapshot)
|
||||
.then_with(|| {
|
||||
probe
|
||||
.end
|
||||
.cmp(&color_end_anchor, &multi_buffer_snapshot)
|
||||
})
|
||||
});
|
||||
new_editor_colors
|
||||
.insert(i, (color_start_anchor..color_end_anchor, color));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => log::error!("Failed to retrieve document colors: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
let mut colors_splice = InlaySplice::default();
|
||||
let mut new_color_inlays = Vec::with_capacity(new_editor_colors.len());
|
||||
let Some(colors) = &mut editor.colors else {
|
||||
return;
|
||||
};
|
||||
let mut existing_colors = colors.colors.iter().peekable();
|
||||
for (new_range, new_color) in new_editor_colors {
|
||||
let rgba_color = Rgba {
|
||||
r: new_color.color.red,
|
||||
g: new_color.color.green,
|
||||
b: new_color.color.blue,
|
||||
a: new_color.color.alpha,
|
||||
};
|
||||
|
||||
loop {
|
||||
match existing_colors.peek() {
|
||||
Some((existing_range, existing_color, existing_inlay_id)) => {
|
||||
match existing_range
|
||||
.start
|
||||
.cmp(&new_range.start, &multi_buffer_snapshot)
|
||||
.then_with(|| {
|
||||
existing_range
|
||||
.end
|
||||
.cmp(&new_range.end, &multi_buffer_snapshot)
|
||||
}) {
|
||||
cmp::Ordering::Less => {
|
||||
colors_splice.to_remove.push(*existing_inlay_id);
|
||||
existing_colors.next();
|
||||
continue;
|
||||
}
|
||||
cmp::Ordering::Equal => {
|
||||
if existing_color == &new_color {
|
||||
new_color_inlays.push((
|
||||
new_range,
|
||||
new_color,
|
||||
*existing_inlay_id,
|
||||
));
|
||||
} else {
|
||||
colors_splice.to_remove.push(*existing_inlay_id);
|
||||
|
||||
let inlay = Inlay::color(
|
||||
post_inc(&mut editor.next_color_inlay_id),
|
||||
new_range.start,
|
||||
rgba_color,
|
||||
);
|
||||
let inlay_id = inlay.id;
|
||||
colors_splice.to_insert.push(inlay);
|
||||
new_color_inlays
|
||||
.push((new_range, new_color, inlay_id));
|
||||
}
|
||||
existing_colors.next();
|
||||
break;
|
||||
}
|
||||
cmp::Ordering::Greater => {
|
||||
let inlay = Inlay::color(
|
||||
post_inc(&mut editor.next_color_inlay_id),
|
||||
new_range.start,
|
||||
rgba_color,
|
||||
);
|
||||
let inlay_id = inlay.id;
|
||||
colors_splice.to_insert.push(inlay);
|
||||
new_color_inlays.push((new_range, new_color, inlay_id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let inlay = Inlay::color(
|
||||
post_inc(&mut editor.next_color_inlay_id),
|
||||
new_range.start,
|
||||
rgba_color,
|
||||
);
|
||||
let inlay_id = inlay.id;
|
||||
colors_splice.to_insert.push(inlay);
|
||||
new_color_inlays.push((new_range, new_color, inlay_id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if existing_colors.peek().is_some() {
|
||||
colors_splice
|
||||
.to_remove
|
||||
.extend(existing_colors.map(|(_, _, id)| *id));
|
||||
}
|
||||
|
||||
let mut updated = colors.set_colors(new_color_inlays);
|
||||
if colors.render_mode == DocumentColorsRenderMode::Inlay
|
||||
&& (!colors_splice.to_insert.is_empty()
|
||||
|| !colors_splice.to_remove.is_empty())
|
||||
{
|
||||
editor.splice_inlays(&colors_splice.to_remove, colors_splice.to_insert, cx);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if updated {
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
|
@ -789,7 +789,7 @@ pub fn split_display_range_by_lines(
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
Buffer, DisplayMap, DisplayRow, ExcerptRange, FoldPlaceholder, InlayId, MultiBuffer,
|
||||
Buffer, DisplayMap, DisplayRow, ExcerptRange, FoldPlaceholder, MultiBuffer,
|
||||
display_map::Inlay,
|
||||
test::{editor_test_context::EditorTestContext, marked_display_snapshot},
|
||||
};
|
||||
|
@ -939,26 +939,26 @@ mod tests {
|
|||
let inlays = (0..buffer_snapshot.len())
|
||||
.flat_map(|offset| {
|
||||
[
|
||||
Inlay {
|
||||
id: InlayId::InlineCompletion(post_inc(&mut id)),
|
||||
position: buffer_snapshot.anchor_at(offset, Bias::Left),
|
||||
text: "test".into(),
|
||||
},
|
||||
Inlay {
|
||||
id: InlayId::InlineCompletion(post_inc(&mut id)),
|
||||
position: buffer_snapshot.anchor_at(offset, Bias::Right),
|
||||
text: "test".into(),
|
||||
},
|
||||
Inlay {
|
||||
id: InlayId::Hint(post_inc(&mut id)),
|
||||
position: buffer_snapshot.anchor_at(offset, Bias::Left),
|
||||
text: "test".into(),
|
||||
},
|
||||
Inlay {
|
||||
id: InlayId::Hint(post_inc(&mut id)),
|
||||
position: buffer_snapshot.anchor_at(offset, Bias::Right),
|
||||
text: "test".into(),
|
||||
},
|
||||
Inlay::inline_completion(
|
||||
post_inc(&mut id),
|
||||
buffer_snapshot.anchor_at(offset, Bias::Left),
|
||||
"test",
|
||||
),
|
||||
Inlay::inline_completion(
|
||||
post_inc(&mut id),
|
||||
buffer_snapshot.anchor_at(offset, Bias::Right),
|
||||
"test",
|
||||
),
|
||||
Inlay::mock_hint(
|
||||
post_inc(&mut id),
|
||||
buffer_snapshot.anchor_at(offset, Bias::Left),
|
||||
"test",
|
||||
),
|
||||
Inlay::mock_hint(
|
||||
post_inc(&mut id),
|
||||
buffer_snapshot.anchor_at(offset, Bias::Right),
|
||||
"test",
|
||||
),
|
||||
]
|
||||
})
|
||||
.collect();
|
||||
|
|
|
@ -522,12 +522,4 @@ impl SemanticsProvider for BranchBufferSemanticsProvider {
|
|||
) -> Option<Task<anyhow::Result<project::ProjectTransaction>>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn pull_diagnostics_for_buffer(
|
||||
&self,
|
||||
_: Entity<Buffer>,
|
||||
_: &mut App,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ use text::*;
|
|||
|
||||
pub use proto::{BufferState, File, Operation};
|
||||
|
||||
use super::{point_from_lsp, point_to_lsp};
|
||||
|
||||
/// Deserializes a `[text::LineEnding]` from the RPC representation.
|
||||
pub fn deserialize_line_ending(message: proto::LineEnding) -> text::LineEnding {
|
||||
match message {
|
||||
|
@ -582,3 +584,33 @@ pub fn serialize_version(version: &clock::Global) -> Vec<proto::VectorClockEntry
|
|||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn serialize_lsp_edit(edit: lsp::TextEdit) -> proto::TextEdit {
|
||||
let start = point_from_lsp(edit.range.start).0;
|
||||
let end = point_from_lsp(edit.range.end).0;
|
||||
proto::TextEdit {
|
||||
new_text: edit.new_text,
|
||||
lsp_range_start: Some(proto::PointUtf16 {
|
||||
row: start.row,
|
||||
column: start.column,
|
||||
}),
|
||||
lsp_range_end: Some(proto::PointUtf16 {
|
||||
row: end.row,
|
||||
column: end.column,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_lsp_edit(edit: proto::TextEdit) -> Option<lsp::TextEdit> {
|
||||
let start = edit.lsp_range_start?;
|
||||
let start = PointUtf16::new(start.row, start.column);
|
||||
let end = edit.lsp_range_end?;
|
||||
let end = PointUtf16::new(end.row, end.column);
|
||||
Some(lsp::TextEdit {
|
||||
range: lsp::Range {
|
||||
start: point_to_lsp(start),
|
||||
end: point_to_lsp(end),
|
||||
},
|
||||
new_text: edit.new_text,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -804,6 +804,9 @@ impl LanguageServer {
|
|||
related_document_support: Some(true),
|
||||
})
|
||||
.filter(|_| pull_diagnostics),
|
||||
color_provider: Some(DocumentColorClientCapabilities {
|
||||
dynamic_registration: Some(false),
|
||||
}),
|
||||
..TextDocumentClientCapabilities::default()
|
||||
}),
|
||||
experimental: Some(json!({
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
mod signature_help;
|
||||
|
||||
use crate::{
|
||||
CodeAction, CompletionSource, CoreCompletion, CoreCompletionResponse, DocumentHighlight,
|
||||
DocumentSymbol, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel,
|
||||
InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink,
|
||||
LspAction, LspPullDiagnostics, MarkupContent, PrepareRenameResponse, ProjectTransaction,
|
||||
PulledDiagnostics, ResolveState,
|
||||
CodeAction, CompletionSource, CoreCompletion, CoreCompletionResponse, DocumentColor,
|
||||
DocumentHighlight, DocumentSymbol, Hover, HoverBlock, HoverBlockKind, InlayHint,
|
||||
InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location,
|
||||
LocationLink, LspAction, LspPullDiagnostics, MarkupContent, PrepareRenameResponse,
|
||||
ProjectTransaction, PulledDiagnostics, ResolveState,
|
||||
lsp_store::{LocalLspStore, LspStore},
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
|
@ -244,6 +244,9 @@ pub(crate) struct InlayHints {
|
|||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) struct GetCodeLens;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) struct GetDocumentColor;
|
||||
|
||||
impl GetCodeLens {
|
||||
pub(crate) fn can_resolve_lens(capabilities: &ServerCapabilities) -> bool {
|
||||
capabilities
|
||||
|
@ -4143,6 +4146,144 @@ impl LspCommand for GetDocumentDiagnostics {
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for GetDocumentColor {
|
||||
type Response = Vec<DocumentColor>;
|
||||
type LspRequest = lsp::request::DocumentColor;
|
||||
type ProtoRequest = proto::GetDocumentColor;
|
||||
|
||||
fn display_name(&self) -> &str {
|
||||
"Document color"
|
||||
}
|
||||
|
||||
fn check_capabilities(&self, server_capabilities: AdapterServerCapabilities) -> bool {
|
||||
server_capabilities
|
||||
.server_capabilities
|
||||
.color_provider
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn to_lsp(
|
||||
&self,
|
||||
path: &Path,
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &App,
|
||||
) -> Result<lsp::DocumentColorParams> {
|
||||
Ok(lsp::DocumentColorParams {
|
||||
text_document: make_text_document_identifier(path)?,
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
self,
|
||||
message: Vec<lsp::ColorInformation>,
|
||||
_: Entity<LspStore>,
|
||||
_: Entity<Buffer>,
|
||||
_: LanguageServerId,
|
||||
_: AsyncApp,
|
||||
) -> Result<Self::Response> {
|
||||
Ok(message
|
||||
.into_iter()
|
||||
.map(|color| DocumentColor {
|
||||
lsp_range: color.range,
|
||||
color: color.color,
|
||||
resolved: false,
|
||||
color_presentations: Vec::new(),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest {
|
||||
proto::GetDocumentColor {
|
||||
project_id,
|
||||
buffer_id: buffer.remote_id().to_proto(),
|
||||
version: serialize_version(&buffer.version()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_proto(
|
||||
_: Self::ProtoRequest,
|
||||
_: Entity<LspStore>,
|
||||
_: Entity<Buffer>,
|
||||
_: AsyncApp,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {})
|
||||
}
|
||||
|
||||
fn response_to_proto(
|
||||
response: Self::Response,
|
||||
_: &mut LspStore,
|
||||
_: PeerId,
|
||||
buffer_version: &clock::Global,
|
||||
_: &mut App,
|
||||
) -> proto::GetDocumentColorResponse {
|
||||
proto::GetDocumentColorResponse {
|
||||
colors: response
|
||||
.into_iter()
|
||||
.map(|color| {
|
||||
let start = point_from_lsp(color.lsp_range.start).0;
|
||||
let end = point_from_lsp(color.lsp_range.end).0;
|
||||
proto::ColorInformation {
|
||||
red: color.color.red,
|
||||
green: color.color.green,
|
||||
blue: color.color.blue,
|
||||
alpha: color.color.alpha,
|
||||
lsp_range_start: Some(proto::PointUtf16 {
|
||||
row: start.row,
|
||||
column: start.column,
|
||||
}),
|
||||
lsp_range_end: Some(proto::PointUtf16 {
|
||||
row: end.row,
|
||||
column: end.column,
|
||||
}),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
version: serialize_version(buffer_version),
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_proto(
|
||||
self,
|
||||
message: proto::GetDocumentColorResponse,
|
||||
_: Entity<LspStore>,
|
||||
_: Entity<Buffer>,
|
||||
_: AsyncApp,
|
||||
) -> Result<Self::Response> {
|
||||
Ok(message
|
||||
.colors
|
||||
.into_iter()
|
||||
.filter_map(|color| {
|
||||
let start = color.lsp_range_start?;
|
||||
let start = PointUtf16::new(start.row, start.column);
|
||||
let end = color.lsp_range_end?;
|
||||
let end = PointUtf16::new(end.row, end.column);
|
||||
Some(DocumentColor {
|
||||
resolved: false,
|
||||
color_presentations: Vec::new(),
|
||||
lsp_range: lsp::Range {
|
||||
start: point_to_lsp(start),
|
||||
end: point_to_lsp(end),
|
||||
},
|
||||
color: lsp::Color {
|
||||
red: color.red,
|
||||
green: color.green,
|
||||
blue: color.blue,
|
||||
alpha: color.alpha,
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn buffer_id_from_proto(message: &Self::ProtoRequest) -> Result<BufferId> {
|
||||
BufferId::new(message.buffer_id)
|
||||
}
|
||||
}
|
||||
|
||||
fn process_related_documents(
|
||||
diagnostics: &mut HashMap<lsp::Url, LspPullDiagnostics>,
|
||||
server_id: LanguageServerId,
|
||||
|
|
|
@ -3,9 +3,9 @@ pub mod lsp_ext_command;
|
|||
pub mod rust_analyzer_ext;
|
||||
|
||||
use crate::{
|
||||
CodeAction, Completion, CompletionResponse, CompletionSource, CoreCompletion, Hover, InlayHint,
|
||||
LspAction, LspPullDiagnostics, ProjectItem, ProjectPath, ProjectTransaction, PulledDiagnostics,
|
||||
ResolveState, Symbol, ToolchainStore,
|
||||
CodeAction, ColorPresentation, Completion, CompletionResponse, CompletionSource,
|
||||
CoreCompletion, DocumentColor, Hover, InlayHint, LspAction, LspPullDiagnostics, ProjectItem,
|
||||
ProjectPath, ProjectTransaction, PulledDiagnostics, ResolveState, Symbol, ToolchainStore,
|
||||
buffer_store::{BufferStore, BufferStoreEvent},
|
||||
environment::ProjectEnvironment,
|
||||
lsp_command::{self, *},
|
||||
|
@ -24,6 +24,7 @@ use crate::{
|
|||
use anyhow::{Context as _, Result, anyhow};
|
||||
use async_trait::async_trait;
|
||||
use client::{TypedEnvelope, proto};
|
||||
use clock::Global;
|
||||
use collections::{BTreeMap, BTreeSet, HashMap, HashSet, btree_map};
|
||||
use futures::{
|
||||
AsyncWriteExt, Future, FutureExt, StreamExt,
|
||||
|
@ -48,7 +49,10 @@ use language::{
|
|||
FormatOnSave, Formatter, LanguageSettings, SelectedFormatter, language_settings,
|
||||
},
|
||||
point_to_lsp,
|
||||
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
||||
proto::{
|
||||
deserialize_anchor, deserialize_lsp_edit, deserialize_version, serialize_anchor,
|
||||
serialize_lsp_edit, serialize_version,
|
||||
},
|
||||
range_from_lsp, range_to_lsp,
|
||||
};
|
||||
use lsp::{
|
||||
|
@ -320,7 +324,7 @@ impl LocalLspStore {
|
|||
if let Some(lsp_store) = this.upgrade() {
|
||||
lsp_store
|
||||
.update(cx, |lsp_store, cx| {
|
||||
lsp_store.remove_result_ids(server_id);
|
||||
lsp_store.cleanup_lsp_data(server_id);
|
||||
cx.emit(LspStoreEvent::LanguageServerRemoved(server_id))
|
||||
})
|
||||
.ok();
|
||||
|
@ -3481,6 +3485,22 @@ pub struct LspStore {
|
|||
_maintain_buffer_languages: Task<()>,
|
||||
diagnostic_summaries:
|
||||
HashMap<WorktreeId, HashMap<Arc<Path>, HashMap<LanguageServerId, DiagnosticSummary>>>,
|
||||
lsp_data: Option<LspData>,
|
||||
}
|
||||
|
||||
type DocumentColorTask = Shared<Task<std::result::Result<Vec<DocumentColor>, Arc<anyhow::Error>>>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct LspData {
|
||||
mtime: MTime,
|
||||
buffer_lsp_data: HashMap<LanguageServerId, HashMap<PathBuf, BufferLspData>>,
|
||||
colors_update: HashMap<PathBuf, DocumentColorTask>,
|
||||
last_version_queried: HashMap<PathBuf, Global>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct BufferLspData {
|
||||
colors: Option<Vec<DocumentColor>>,
|
||||
}
|
||||
|
||||
pub enum LspStoreEvent {
|
||||
|
@ -3553,6 +3573,7 @@ impl LspStore {
|
|||
client.add_entity_request_handler(Self::handle_inlay_hints);
|
||||
client.add_entity_request_handler(Self::handle_get_project_symbols);
|
||||
client.add_entity_request_handler(Self::handle_resolve_inlay_hint);
|
||||
client.add_entity_request_handler(Self::handle_get_color_presentation);
|
||||
client.add_entity_request_handler(Self::handle_open_buffer_for_symbol);
|
||||
client.add_entity_request_handler(Self::handle_refresh_inlay_hints);
|
||||
client.add_entity_request_handler(Self::handle_refresh_code_lens);
|
||||
|
@ -3707,9 +3728,9 @@ impl LspStore {
|
|||
languages: languages.clone(),
|
||||
language_server_statuses: Default::default(),
|
||||
nonce: StdRng::from_entropy().r#gen(),
|
||||
diagnostic_summaries: Default::default(),
|
||||
diagnostic_summaries: HashMap::default(),
|
||||
lsp_data: None,
|
||||
active_entry: None,
|
||||
|
||||
_maintain_workspace_config,
|
||||
_maintain_buffer_languages: Self::maintain_buffer_languages(languages, cx),
|
||||
}
|
||||
|
@ -3763,7 +3784,8 @@ impl LspStore {
|
|||
languages: languages.clone(),
|
||||
language_server_statuses: Default::default(),
|
||||
nonce: StdRng::from_entropy().r#gen(),
|
||||
diagnostic_summaries: Default::default(),
|
||||
diagnostic_summaries: HashMap::default(),
|
||||
lsp_data: None,
|
||||
active_entry: None,
|
||||
toolchain_store,
|
||||
_maintain_workspace_config,
|
||||
|
@ -3890,7 +3912,7 @@ impl LspStore {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
language::BufferEvent::Edited { .. } => {
|
||||
language::BufferEvent::Edited => {
|
||||
self.on_buffer_edited(buffer, cx);
|
||||
}
|
||||
|
||||
|
@ -4835,6 +4857,105 @@ impl LspStore {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn resolve_color_presentation(
|
||||
&mut self,
|
||||
mut color: DocumentColor,
|
||||
buffer: Entity<Buffer>,
|
||||
server_id: LanguageServerId,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<DocumentColor>> {
|
||||
if color.resolved {
|
||||
return Task::ready(Ok(color));
|
||||
}
|
||||
|
||||
if let Some((upstream_client, project_id)) = self.upstream_client() {
|
||||
let start = color.lsp_range.start;
|
||||
let end = color.lsp_range.end;
|
||||
let request = proto::GetColorPresentation {
|
||||
project_id,
|
||||
server_id: server_id.to_proto(),
|
||||
buffer_id: buffer.read(cx).remote_id().into(),
|
||||
color: Some(proto::ColorInformation {
|
||||
red: color.color.red,
|
||||
green: color.color.green,
|
||||
blue: color.color.blue,
|
||||
alpha: color.color.alpha,
|
||||
lsp_range_start: Some(proto::PointUtf16 {
|
||||
row: start.line,
|
||||
column: start.character,
|
||||
}),
|
||||
lsp_range_end: Some(proto::PointUtf16 {
|
||||
row: end.line,
|
||||
column: end.character,
|
||||
}),
|
||||
}),
|
||||
};
|
||||
cx.background_spawn(async move {
|
||||
let response = upstream_client
|
||||
.request(request)
|
||||
.await
|
||||
.context("color presentation proto request")?;
|
||||
color.resolved = true;
|
||||
color.color_presentations = response
|
||||
.presentations
|
||||
.into_iter()
|
||||
.map(|presentation| ColorPresentation {
|
||||
label: presentation.label,
|
||||
text_edit: presentation.text_edit.and_then(deserialize_lsp_edit),
|
||||
additional_text_edits: presentation
|
||||
.additional_text_edits
|
||||
.into_iter()
|
||||
.filter_map(deserialize_lsp_edit)
|
||||
.collect(),
|
||||
})
|
||||
.collect();
|
||||
Ok(color)
|
||||
})
|
||||
} else {
|
||||
let path = match buffer
|
||||
.update(cx, |buffer, cx| {
|
||||
Some(crate::File::from_dyn(buffer.file())?.abs_path(cx))
|
||||
})
|
||||
.context("buffer with the missing path")
|
||||
{
|
||||
Ok(path) => path,
|
||||
Err(e) => return Task::ready(Err(e)),
|
||||
};
|
||||
let Some(lang_server) = buffer.update(cx, |buffer, cx| {
|
||||
self.language_server_for_local_buffer(buffer, server_id, cx)
|
||||
.map(|(_, server)| server.clone())
|
||||
}) else {
|
||||
return Task::ready(Ok(color));
|
||||
};
|
||||
cx.background_spawn(async move {
|
||||
let resolve_task = lang_server.request::<lsp::request::ColorPresentationRequest>(
|
||||
lsp::ColorPresentationParams {
|
||||
text_document: make_text_document_identifier(&path)?,
|
||||
color: color.color,
|
||||
range: color.lsp_range,
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
},
|
||||
);
|
||||
color.color_presentations = resolve_task
|
||||
.await
|
||||
.into_response()
|
||||
.context("color presentation resolve LSP request")?
|
||||
.into_iter()
|
||||
.map(|presentation| ColorPresentation {
|
||||
label: presentation.label,
|
||||
text_edit: presentation.text_edit,
|
||||
additional_text_edits: presentation
|
||||
.additional_text_edits
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
.collect();
|
||||
color.resolved = true;
|
||||
Ok(color)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn linked_edit(
|
||||
&mut self,
|
||||
buffer: &Entity<Buffer>,
|
||||
|
@ -5063,7 +5184,13 @@ impl LspStore {
|
|||
},
|
||||
cx,
|
||||
);
|
||||
cx.spawn(async move |_, _| Ok(all_actions_task.await.into_iter().flatten().collect()))
|
||||
cx.spawn(async move |_, _| {
|
||||
Ok(all_actions_task
|
||||
.await
|
||||
.into_iter()
|
||||
.flat_map(|(_, actions)| actions)
|
||||
.collect())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5123,7 +5250,13 @@ impl LspStore {
|
|||
} else {
|
||||
let code_lens_task =
|
||||
self.request_multiple_lsp_locally(buffer_handle, None::<usize>, GetCodeLens, cx);
|
||||
cx.spawn(async move |_, _| Ok(code_lens_task.await.into_iter().flatten().collect()))
|
||||
cx.spawn(async move |_, _| {
|
||||
Ok(code_lens_task
|
||||
.await
|
||||
.into_iter()
|
||||
.flat_map(|(_, code_lens)| code_lens)
|
||||
.collect())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5870,6 +6003,293 @@ impl LspStore {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn pull_diagnostics_for_buffer(
|
||||
&mut self,
|
||||
buffer: Entity<Buffer>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
let diagnostics = self.pull_diagnostics(buffer, cx);
|
||||
cx.spawn(async move |lsp_store, cx| {
|
||||
let diagnostics = diagnostics.await.context("pulling diagnostics")?;
|
||||
lsp_store.update(cx, |lsp_store, cx| {
|
||||
for diagnostics_set in diagnostics {
|
||||
let LspPullDiagnostics::Response {
|
||||
server_id,
|
||||
uri,
|
||||
diagnostics,
|
||||
} = diagnostics_set
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let adapter = lsp_store.language_server_adapter_for_id(server_id);
|
||||
let disk_based_sources = adapter
|
||||
.as_ref()
|
||||
.map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
|
||||
.unwrap_or(&[]);
|
||||
match diagnostics {
|
||||
PulledDiagnostics::Unchanged { result_id } => {
|
||||
lsp_store
|
||||
.merge_diagnostics(
|
||||
server_id,
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: uri.clone(),
|
||||
diagnostics: Vec::new(),
|
||||
version: None,
|
||||
},
|
||||
Some(result_id),
|
||||
DiagnosticSourceKind::Pulled,
|
||||
disk_based_sources,
|
||||
|_, _| true,
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
}
|
||||
PulledDiagnostics::Changed {
|
||||
diagnostics,
|
||||
result_id,
|
||||
} => {
|
||||
lsp_store
|
||||
.merge_diagnostics(
|
||||
server_id,
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: uri.clone(),
|
||||
diagnostics,
|
||||
version: None,
|
||||
},
|
||||
result_id,
|
||||
DiagnosticSourceKind::Pulled,
|
||||
disk_based_sources,
|
||||
|old_diagnostic, _| match old_diagnostic.source_kind {
|
||||
DiagnosticSourceKind::Pulled => false,
|
||||
DiagnosticSourceKind::Other
|
||||
| DiagnosticSourceKind::Pushed => true,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn document_colors(
|
||||
&mut self,
|
||||
update_on_edit: bool,
|
||||
for_server_id: Option<LanguageServerId>,
|
||||
buffer: Entity<Buffer>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<DocumentColorTask> {
|
||||
let buffer_mtime = buffer.read(cx).saved_mtime()?;
|
||||
let abs_path = crate::File::from_dyn(buffer.read(cx).file())?.abs_path(cx);
|
||||
let buffer_version = buffer.read(cx).version();
|
||||
let ignore_existing_mtime = update_on_edit
|
||||
&& self.lsp_data.as_ref().is_none_or(|lsp_data| {
|
||||
lsp_data.last_version_queried.get(&abs_path) != Some(&buffer_version)
|
||||
});
|
||||
|
||||
let mut has_other_versions = false;
|
||||
let mut received_colors_data = false;
|
||||
let mut outdated_lsp_data = false;
|
||||
let buffer_lsp_data = self
|
||||
.lsp_data
|
||||
.as_ref()
|
||||
.into_iter()
|
||||
.filter(|lsp_data| {
|
||||
if ignore_existing_mtime {
|
||||
return false;
|
||||
}
|
||||
has_other_versions |= lsp_data.mtime != buffer_mtime;
|
||||
lsp_data.mtime == buffer_mtime
|
||||
})
|
||||
.flat_map(|lsp_data| lsp_data.buffer_lsp_data.values())
|
||||
.filter_map(|buffer_data| buffer_data.get(&abs_path))
|
||||
.filter_map(|buffer_data| {
|
||||
let colors = buffer_data.colors.as_deref()?;
|
||||
received_colors_data = true;
|
||||
Some(colors)
|
||||
})
|
||||
.flatten()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if buffer_lsp_data.is_empty() || for_server_id.is_some() {
|
||||
if received_colors_data && for_server_id.is_none() {
|
||||
return None;
|
||||
} else if has_other_versions && !ignore_existing_mtime {
|
||||
return None;
|
||||
}
|
||||
|
||||
if ignore_existing_mtime
|
||||
|| self.lsp_data.is_none()
|
||||
|| self
|
||||
.lsp_data
|
||||
.as_ref()
|
||||
.is_some_and(|lsp_data| buffer_mtime != lsp_data.mtime)
|
||||
{
|
||||
self.lsp_data = Some(LspData {
|
||||
mtime: buffer_mtime,
|
||||
buffer_lsp_data: HashMap::default(),
|
||||
colors_update: HashMap::default(),
|
||||
last_version_queried: HashMap::default(),
|
||||
});
|
||||
outdated_lsp_data = true;
|
||||
}
|
||||
|
||||
{
|
||||
let lsp_data = self.lsp_data.as_mut()?;
|
||||
match for_server_id {
|
||||
Some(for_server_id) if !outdated_lsp_data => {
|
||||
lsp_data.buffer_lsp_data.remove(&for_server_id);
|
||||
}
|
||||
None | Some(_) => {
|
||||
let existing_task = lsp_data.colors_update.get(&abs_path).cloned();
|
||||
if !outdated_lsp_data && existing_task.is_some() {
|
||||
return existing_task;
|
||||
}
|
||||
for buffer_data in lsp_data.buffer_lsp_data.values_mut() {
|
||||
if let Some(buffer_data) = buffer_data.get_mut(&abs_path) {
|
||||
buffer_data.colors = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let task_abs_path = abs_path.clone();
|
||||
let new_task = cx
|
||||
.spawn(async move |lsp_store, cx| {
|
||||
cx.background_executor().timer(Duration::from_millis(50)).await;
|
||||
let fetched_colors = match lsp_store
|
||||
.update(cx, |lsp_store, cx| {
|
||||
lsp_store.fetch_document_colors(buffer, cx)
|
||||
}) {
|
||||
Ok(fetch_task) => fetch_task.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Fetching document colors for buffer with path {task_abs_path:?}"
|
||||
)
|
||||
}),
|
||||
Err(e) => return Err(Arc::new(e)),
|
||||
};
|
||||
let fetched_colors = match fetched_colors {
|
||||
Ok(fetched_colors) => fetched_colors,
|
||||
Err(e) => return Err(Arc::new(e)),
|
||||
};
|
||||
|
||||
let lsp_colors = lsp_store.update(cx, |lsp_store, _| {
|
||||
let lsp_data = lsp_store.lsp_data.as_mut().with_context(|| format!(
|
||||
"Document lsp data got updated between fetch and update for path {task_abs_path:?}"
|
||||
))?;
|
||||
let mut lsp_colors = Vec::new();
|
||||
anyhow::ensure!(lsp_data.mtime == buffer_mtime, "Buffer lsp data got updated between fetch and update for path {task_abs_path:?}");
|
||||
for (server_id, colors) in fetched_colors {
|
||||
let colors_lsp_data = &mut lsp_data.buffer_lsp_data.entry(server_id).or_default().entry(task_abs_path.clone()).or_default().colors;
|
||||
*colors_lsp_data = Some(colors.clone());
|
||||
lsp_colors.extend(colors);
|
||||
}
|
||||
Ok(lsp_colors)
|
||||
});
|
||||
|
||||
match lsp_colors {
|
||||
Ok(Ok(lsp_colors)) => Ok(lsp_colors),
|
||||
Ok(Err(e)) => Err(Arc::new(e)),
|
||||
Err(e) => Err(Arc::new(e)),
|
||||
}
|
||||
})
|
||||
.shared();
|
||||
let lsp_data = self.lsp_data.as_mut()?;
|
||||
lsp_data
|
||||
.colors_update
|
||||
.insert(abs_path.clone(), new_task.clone());
|
||||
lsp_data
|
||||
.last_version_queried
|
||||
.insert(abs_path, buffer_version);
|
||||
Some(new_task)
|
||||
} else {
|
||||
Some(Task::ready(Ok(buffer_lsp_data)).shared())
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_document_colors(
|
||||
&mut self,
|
||||
buffer: Entity<Buffer>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<anyhow::Result<Vec<(LanguageServerId, Vec<DocumentColor>)>>> {
|
||||
if let Some((client, project_id)) = self.upstream_client() {
|
||||
let request_task = client.request(proto::MultiLspQuery {
|
||||
project_id,
|
||||
buffer_id: buffer.read(cx).remote_id().to_proto(),
|
||||
version: serialize_version(&buffer.read(cx).version()),
|
||||
strategy: Some(proto::multi_lsp_query::Strategy::All(
|
||||
proto::AllLanguageServers {},
|
||||
)),
|
||||
request: Some(proto::multi_lsp_query::Request::GetDocumentColor(
|
||||
GetDocumentColor {}.to_proto(project_id, buffer.read(cx)),
|
||||
)),
|
||||
});
|
||||
cx.spawn(async move |project, cx| {
|
||||
let Some(project) = project.upgrade() else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let colors = join_all(
|
||||
request_task
|
||||
.await
|
||||
.log_err()
|
||||
.map(|response| response.responses)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter_map(|lsp_response| match lsp_response.response? {
|
||||
proto::lsp_response::Response::GetDocumentColorResponse(response) => {
|
||||
Some((
|
||||
LanguageServerId::from_proto(lsp_response.server_id),
|
||||
response,
|
||||
))
|
||||
}
|
||||
unexpected => {
|
||||
debug_panic!("Unexpected response: {unexpected:?}");
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|(server_id, color_response)| {
|
||||
let response = GetDocumentColor {}.response_from_proto(
|
||||
color_response,
|
||||
project.clone(),
|
||||
buffer.clone(),
|
||||
cx.clone(),
|
||||
);
|
||||
async move { (server_id, response.await.log_err().unwrap_or_default()) }
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.into_iter()
|
||||
.fold(HashMap::default(), |mut acc, (server_id, colors)| {
|
||||
acc.entry(server_id).or_insert_with(Vec::new).extend(colors);
|
||||
acc
|
||||
})
|
||||
.into_iter()
|
||||
.collect();
|
||||
Ok(colors)
|
||||
})
|
||||
} else {
|
||||
let document_colors_task =
|
||||
self.request_multiple_lsp_locally(&buffer, None::<usize>, GetDocumentColor, cx);
|
||||
cx.spawn(async move |_, _| {
|
||||
Ok(document_colors_task
|
||||
.await
|
||||
.into_iter()
|
||||
.fold(HashMap::default(), |mut acc, (server_id, colors)| {
|
||||
acc.entry(server_id).or_insert_with(Vec::new).extend(colors);
|
||||
acc
|
||||
})
|
||||
.into_iter()
|
||||
.collect())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn signature_help<T: ToPointUtf16>(
|
||||
&mut self,
|
||||
buffer: &Entity<Buffer>,
|
||||
|
@ -5937,7 +6357,7 @@ impl LspStore {
|
|||
all_actions_task
|
||||
.await
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.flat_map(|(_, actions)| actions)
|
||||
.filter(|help| !help.label.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
|
@ -6015,7 +6435,7 @@ impl LspStore {
|
|||
all_actions_task
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|hover| remove_empty_hover_blocks(hover?))
|
||||
.filter_map(|(_, hover)| remove_empty_hover_blocks(hover?))
|
||||
.collect::<Vec<Hover>>()
|
||||
})
|
||||
}
|
||||
|
@ -6948,7 +7368,7 @@ impl LspStore {
|
|||
position: Option<P>,
|
||||
request: R,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Vec<R::Response>>
|
||||
) -> Task<Vec<(LanguageServerId, R::Response)>>
|
||||
where
|
||||
P: ToOffset,
|
||||
R: LspCommand + Clone,
|
||||
|
@ -6978,20 +7398,21 @@ impl LspStore {
|
|||
let mut response_results = server_ids
|
||||
.into_iter()
|
||||
.map(|server_id| {
|
||||
self.request_lsp(
|
||||
let task = self.request_lsp(
|
||||
buffer.clone(),
|
||||
LanguageServerToQuery::Other(server_id),
|
||||
request.clone(),
|
||||
cx,
|
||||
)
|
||||
);
|
||||
async move { (server_id, task.await) }
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
|
||||
cx.spawn(async move |_, _| {
|
||||
let mut responses = Vec::with_capacity(response_results.len());
|
||||
while let Some(response_result) = response_results.next().await {
|
||||
while let Some((server_id, response_result)) = response_results.next().await {
|
||||
if let Some(response) = response_result.log_err() {
|
||||
responses.push(response);
|
||||
responses.push((server_id, response));
|
||||
}
|
||||
}
|
||||
responses
|
||||
|
@ -7079,9 +7500,14 @@ impl LspStore {
|
|||
}
|
||||
}
|
||||
match envelope.payload.request {
|
||||
Some(proto::multi_lsp_query::Request::GetHover(get_hover)) => {
|
||||
Some(proto::multi_lsp_query::Request::GetHover(message)) => {
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| {
|
||||
buffer.wait_for_version(deserialize_version(&message.version))
|
||||
})?
|
||||
.await?;
|
||||
let get_hover =
|
||||
GetHover::from_proto(get_hover, lsp_store.clone(), buffer.clone(), cx.clone())
|
||||
GetHover::from_proto(message, lsp_store.clone(), buffer.clone(), cx.clone())
|
||||
.await?;
|
||||
let all_hovers = lsp_store
|
||||
.update(&mut cx, |this, cx| {
|
||||
|
@ -7094,10 +7520,13 @@ impl LspStore {
|
|||
})?
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|hover| remove_empty_hover_blocks(hover?));
|
||||
.filter_map(|(server_id, hover)| {
|
||||
Some((server_id, remove_empty_hover_blocks(hover?)?))
|
||||
});
|
||||
lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
|
||||
responses: all_hovers
|
||||
.map(|hover| proto::LspResponse {
|
||||
.map(|(server_id, hover)| proto::LspResponse {
|
||||
server_id: server_id.to_proto(),
|
||||
response: Some(proto::lsp_response::Response::GetHoverResponse(
|
||||
GetHover::response_to_proto(
|
||||
Some(hover),
|
||||
|
@ -7111,9 +7540,14 @@ impl LspStore {
|
|||
.collect(),
|
||||
})
|
||||
}
|
||||
Some(proto::multi_lsp_query::Request::GetCodeActions(get_code_actions)) => {
|
||||
Some(proto::multi_lsp_query::Request::GetCodeActions(message)) => {
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| {
|
||||
buffer.wait_for_version(deserialize_version(&message.version))
|
||||
})?
|
||||
.await?;
|
||||
let get_code_actions = GetCodeActions::from_proto(
|
||||
get_code_actions,
|
||||
message,
|
||||
lsp_store.clone(),
|
||||
buffer.clone(),
|
||||
cx.clone(),
|
||||
|
@ -7134,7 +7568,8 @@ impl LspStore {
|
|||
|
||||
lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
|
||||
responses: all_actions
|
||||
.map(|code_actions| proto::LspResponse {
|
||||
.map(|(server_id, code_actions)| proto::LspResponse {
|
||||
server_id: server_id.to_proto(),
|
||||
response: Some(proto::lsp_response::Response::GetCodeActionsResponse(
|
||||
GetCodeActions::response_to_proto(
|
||||
code_actions,
|
||||
|
@ -7148,9 +7583,14 @@ impl LspStore {
|
|||
.collect(),
|
||||
})
|
||||
}
|
||||
Some(proto::multi_lsp_query::Request::GetSignatureHelp(get_signature_help)) => {
|
||||
Some(proto::multi_lsp_query::Request::GetSignatureHelp(message)) => {
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| {
|
||||
buffer.wait_for_version(deserialize_version(&message.version))
|
||||
})?
|
||||
.await?;
|
||||
let get_signature_help = GetSignatureHelp::from_proto(
|
||||
get_signature_help,
|
||||
message,
|
||||
lsp_store.clone(),
|
||||
buffer.clone(),
|
||||
cx.clone(),
|
||||
|
@ -7171,7 +7611,8 @@ impl LspStore {
|
|||
|
||||
lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
|
||||
responses: all_signatures
|
||||
.map(|signature_help| proto::LspResponse {
|
||||
.map(|(server_id, signature_help)| proto::LspResponse {
|
||||
server_id: server_id.to_proto(),
|
||||
response: Some(
|
||||
proto::lsp_response::Response::GetSignatureHelpResponse(
|
||||
GetSignatureHelp::response_to_proto(
|
||||
|
@ -7187,14 +7628,15 @@ impl LspStore {
|
|||
.collect(),
|
||||
})
|
||||
}
|
||||
Some(proto::multi_lsp_query::Request::GetCodeLens(get_code_lens)) => {
|
||||
let get_code_lens = GetCodeLens::from_proto(
|
||||
get_code_lens,
|
||||
lsp_store.clone(),
|
||||
buffer.clone(),
|
||||
cx.clone(),
|
||||
)
|
||||
.await?;
|
||||
Some(proto::multi_lsp_query::Request::GetCodeLens(message)) => {
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| {
|
||||
buffer.wait_for_version(deserialize_version(&message.version))
|
||||
})?
|
||||
.await?;
|
||||
let get_code_lens =
|
||||
GetCodeLens::from_proto(message, lsp_store.clone(), buffer.clone(), cx.clone())
|
||||
.await?;
|
||||
|
||||
let code_lens_actions = lsp_store
|
||||
.update(&mut cx, |project, cx| {
|
||||
|
@ -7210,7 +7652,8 @@ impl LspStore {
|
|||
|
||||
lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
|
||||
responses: code_lens_actions
|
||||
.map(|actions| proto::LspResponse {
|
||||
.map(|(server_id, actions)| proto::LspResponse {
|
||||
server_id: server_id.to_proto(),
|
||||
response: Some(proto::lsp_response::Response::GetCodeLensResponse(
|
||||
GetCodeLens::response_to_proto(
|
||||
actions,
|
||||
|
@ -7242,29 +7685,30 @@ impl LspStore {
|
|||
.into_iter()
|
||||
.map(|server_id| {
|
||||
let result_id = lsp_store.result_id(server_id, buffer_id, cx);
|
||||
lsp_store.request_lsp(
|
||||
let task = lsp_store.request_lsp(
|
||||
buffer.clone(),
|
||||
LanguageServerToQuery::Other(server_id),
|
||||
GetDocumentDiagnostics {
|
||||
previous_result_id: result_id,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
);
|
||||
async move { (server_id, task.await) }
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})?;
|
||||
|
||||
let all_diagnostics_responses = join_all(pull_diagnostics).await;
|
||||
let mut all_diagnostics = Vec::new();
|
||||
for response in all_diagnostics_responses {
|
||||
let response = response?;
|
||||
all_diagnostics.push(response);
|
||||
for (server_id, response) in all_diagnostics_responses {
|
||||
all_diagnostics.push((server_id, response?));
|
||||
}
|
||||
|
||||
lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
|
||||
responses: all_diagnostics
|
||||
.into_iter()
|
||||
.map(|lsp_diagnostic| proto::LspResponse {
|
||||
.map(|(server_id, lsp_diagnostic)| proto::LspResponse {
|
||||
server_id: server_id.to_proto(),
|
||||
response: Some(
|
||||
proto::lsp_response::Response::GetDocumentDiagnosticsResponse(
|
||||
GetDocumentDiagnostics::response_to_proto(
|
||||
|
@ -7280,6 +7724,51 @@ impl LspStore {
|
|||
.collect(),
|
||||
})
|
||||
}
|
||||
Some(proto::multi_lsp_query::Request::GetDocumentColor(message)) => {
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| {
|
||||
buffer.wait_for_version(deserialize_version(&message.version))
|
||||
})?
|
||||
.await?;
|
||||
let get_document_color = GetDocumentColor::from_proto(
|
||||
message,
|
||||
lsp_store.clone(),
|
||||
buffer.clone(),
|
||||
cx.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let all_colors = lsp_store
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.request_multiple_lsp_locally(
|
||||
&buffer,
|
||||
None::<usize>,
|
||||
get_document_color,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
.into_iter();
|
||||
|
||||
lsp_store.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
|
||||
responses: all_colors
|
||||
.map(|(server_id, colors)| proto::LspResponse {
|
||||
server_id: server_id.to_proto(),
|
||||
response: Some(
|
||||
proto::lsp_response::Response::GetDocumentColorResponse(
|
||||
GetDocumentColor::response_to_proto(
|
||||
colors,
|
||||
project,
|
||||
sender_id,
|
||||
&buffer_version,
|
||||
cx,
|
||||
),
|
||||
),
|
||||
),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
None => anyhow::bail!("empty multi lsp query request"),
|
||||
}
|
||||
}
|
||||
|
@ -8263,6 +8752,70 @@ impl LspStore {
|
|||
})
|
||||
}
|
||||
|
||||
async fn handle_get_color_presentation(
|
||||
lsp_store: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::GetColorPresentation>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::GetColorPresentationResponse> {
|
||||
let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
|
||||
let buffer = lsp_store.update(&mut cx, |lsp_store, cx| {
|
||||
lsp_store.buffer_store.read(cx).get_existing(buffer_id)
|
||||
})??;
|
||||
|
||||
let color = envelope
|
||||
.payload
|
||||
.color
|
||||
.context("invalid color resolve request")?;
|
||||
let start = color
|
||||
.lsp_range_start
|
||||
.context("invalid color resolve request")?;
|
||||
let end = color
|
||||
.lsp_range_end
|
||||
.context("invalid color resolve request")?;
|
||||
|
||||
let color = DocumentColor {
|
||||
lsp_range: lsp::Range {
|
||||
start: point_to_lsp(PointUtf16::new(start.row, start.column)),
|
||||
end: point_to_lsp(PointUtf16::new(end.row, end.column)),
|
||||
},
|
||||
color: lsp::Color {
|
||||
red: color.red,
|
||||
green: color.green,
|
||||
blue: color.blue,
|
||||
alpha: color.alpha,
|
||||
},
|
||||
resolved: false,
|
||||
color_presentations: Vec::new(),
|
||||
};
|
||||
let resolved_color = lsp_store
|
||||
.update(&mut cx, |lsp_store, cx| {
|
||||
lsp_store.resolve_color_presentation(
|
||||
color,
|
||||
buffer.clone(),
|
||||
LanguageServerId(envelope.payload.server_id as usize),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
.context("resolving color presentation")?;
|
||||
|
||||
Ok(proto::GetColorPresentationResponse {
|
||||
presentations: resolved_color
|
||||
.color_presentations
|
||||
.into_iter()
|
||||
.map(|presentation| proto::ColorPresentation {
|
||||
label: presentation.label,
|
||||
text_edit: presentation.text_edit.map(serialize_lsp_edit),
|
||||
additional_text_edits: presentation
|
||||
.additional_text_edits
|
||||
.into_iter()
|
||||
.map(serialize_lsp_edit)
|
||||
.collect(),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_resolve_inlay_hint(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::ResolveInlayHint>,
|
||||
|
@ -8829,7 +9382,7 @@ impl LspStore {
|
|||
local.language_server_watched_paths.remove(&server_id);
|
||||
let server_state = local.language_servers.remove(&server_id);
|
||||
cx.notify();
|
||||
self.remove_result_ids(server_id);
|
||||
self.cleanup_lsp_data(server_id);
|
||||
cx.emit(LspStoreEvent::LanguageServerRemoved(server_id));
|
||||
cx.spawn(async move |_, cx| {
|
||||
Self::shutdown_language_server(server_state, name, cx).await;
|
||||
|
@ -9718,7 +10271,10 @@ impl LspStore {
|
|||
}
|
||||
}
|
||||
|
||||
fn remove_result_ids(&mut self, for_server: LanguageServerId) {
|
||||
fn cleanup_lsp_data(&mut self, for_server: LanguageServerId) {
|
||||
if let Some(lsp_data) = &mut self.lsp_data {
|
||||
lsp_data.buffer_lsp_data.remove(&for_server);
|
||||
}
|
||||
if let Some(local) = self.as_local_mut() {
|
||||
local.buffer_pull_diagnostics_result_ids.remove(&for_server);
|
||||
}
|
||||
|
|
|
@ -768,6 +768,21 @@ pub struct DirectoryItem {
|
|||
pub is_dir: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct DocumentColor {
|
||||
pub lsp_range: lsp::Range,
|
||||
pub color: lsp::Color,
|
||||
pub resolved: bool,
|
||||
pub color_presentations: Vec<ColorPresentation>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ColorPresentation {
|
||||
pub label: String,
|
||||
pub text_edit: Option<lsp::TextEdit>,
|
||||
pub additional_text_edits: Vec<lsp::TextEdit>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum DirectoryLister {
|
||||
Project(Entity<Project>),
|
||||
|
@ -3721,16 +3736,6 @@ impl Project {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn document_diagnostics(
|
||||
&mut self,
|
||||
buffer_handle: Entity<Buffer>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Vec<LspPullDiagnostics>>> {
|
||||
self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.pull_diagnostics(buffer_handle, cx)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_diagnostics(
|
||||
&mut self,
|
||||
language_server_id: LanguageServerId,
|
||||
|
|
|
@ -666,6 +666,51 @@ message LanguageServerPromptResponse {
|
|||
optional uint64 action_response = 1;
|
||||
}
|
||||
|
||||
message GetDocumentColor {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
repeated VectorClockEntry version = 3;
|
||||
|
||||
}
|
||||
|
||||
message GetDocumentColorResponse {
|
||||
repeated ColorInformation colors = 1;
|
||||
repeated VectorClockEntry version = 2;
|
||||
|
||||
}
|
||||
|
||||
message ColorInformation {
|
||||
PointUtf16 lsp_range_start = 1;
|
||||
PointUtf16 lsp_range_end = 2;
|
||||
float red = 3;
|
||||
float green = 4;
|
||||
float blue = 5;
|
||||
float alpha = 6;
|
||||
}
|
||||
|
||||
message GetColorPresentation {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
ColorInformation color = 3;
|
||||
uint64 server_id = 4;
|
||||
}
|
||||
|
||||
message GetColorPresentationResponse {
|
||||
repeated ColorPresentation presentations = 1;
|
||||
}
|
||||
|
||||
message ColorPresentation {
|
||||
string label = 1;
|
||||
optional TextEdit text_edit = 2;
|
||||
repeated TextEdit additional_text_edits = 3;
|
||||
}
|
||||
|
||||
message TextEdit {
|
||||
string new_text = 1;
|
||||
PointUtf16 lsp_range_start = 2;
|
||||
PointUtf16 lsp_range_end = 3;
|
||||
}
|
||||
|
||||
message MultiLspQuery {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
|
@ -679,6 +724,7 @@ message MultiLspQuery {
|
|||
GetSignatureHelp get_signature_help = 7;
|
||||
GetCodeLens get_code_lens = 8;
|
||||
GetDocumentDiagnostics get_document_diagnostics = 9;
|
||||
GetDocumentColor get_document_color = 10;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -705,7 +751,9 @@ message LspResponse {
|
|||
GetSignatureHelpResponse get_signature_help_response = 3;
|
||||
GetCodeLensResponse get_code_lens_response = 4;
|
||||
GetDocumentDiagnosticsResponse get_document_diagnostics_response = 5;
|
||||
GetDocumentColorResponse get_document_color_response = 6;
|
||||
}
|
||||
uint64 server_id = 7;
|
||||
}
|
||||
|
||||
message LanguageServerIdForName {
|
||||
|
|
|
@ -391,7 +391,12 @@ message Envelope {
|
|||
|
||||
GetDocumentDiagnostics get_document_diagnostics = 350;
|
||||
GetDocumentDiagnosticsResponse get_document_diagnostics_response = 351;
|
||||
PullWorkspaceDiagnostics pull_workspace_diagnostics = 352; // current max
|
||||
PullWorkspaceDiagnostics pull_workspace_diagnostics = 352;
|
||||
|
||||
GetDocumentColor get_document_color = 353;
|
||||
GetDocumentColorResponse get_document_color_response = 354;
|
||||
GetColorPresentation get_color_presentation = 355;
|
||||
GetColorPresentationResponse get_color_presentation_response = 356; // current max
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -221,6 +221,10 @@ messages!(
|
|||
(ResolveCompletionDocumentationResponse, Background),
|
||||
(ResolveInlayHint, Background),
|
||||
(ResolveInlayHintResponse, Background),
|
||||
(GetDocumentColor, Background),
|
||||
(GetDocumentColorResponse, Background),
|
||||
(GetColorPresentation, Background),
|
||||
(GetColorPresentationResponse, Background),
|
||||
(RefreshCodeLens, Background),
|
||||
(GetCodeLens, Background),
|
||||
(GetCodeLensResponse, Background),
|
||||
|
@ -400,6 +404,8 @@ request_messages!(
|
|||
ResolveCompletionDocumentationResponse
|
||||
),
|
||||
(ResolveInlayHint, ResolveInlayHintResponse),
|
||||
(GetDocumentColor, GetDocumentColorResponse),
|
||||
(GetColorPresentation, GetColorPresentationResponse),
|
||||
(RespondToChannelInvite, Ack),
|
||||
(RespondToContactRequest, Ack),
|
||||
(SaveBuffer, BufferSaved),
|
||||
|
@ -487,9 +493,11 @@ entity_messages!(
|
|||
BufferSaved,
|
||||
CloseBuffer,
|
||||
Commit,
|
||||
GetColorPresentation,
|
||||
CopyProjectEntry,
|
||||
CreateBufferForPeer,
|
||||
CreateProjectEntry,
|
||||
GetDocumentColor,
|
||||
DeleteProjectEntry,
|
||||
ExpandProjectEntry,
|
||||
ExpandAllForProjectEntry,
|
||||
|
|
|
@ -1072,7 +1072,7 @@ impl Element for TerminalElement {
|
|||
color: *color,
|
||||
corner_radius: 0.15 * layout.dimensions.line_height,
|
||||
};
|
||||
hr.paint(bounds, window);
|
||||
hr.paint(true, bounds, window);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue