Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions book/src/editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ The following statusline elements can be configured:
| `display-progress-messages` | Display LSP progress messages below statusline[^1] | `false` |
| `auto-signature-help` | Enable automatic popup of signature help (parameter hints) | `true` |
| `display-inlay-hints` | Display inlay hints[^2] | `false` |
| `display-color-swatches` | Show color swatches next to colors | `true` |
| `display-signature-help-docs` | Display docs under signature help popup | `true` |
| `snippets` | Enables snippet completions. Requires a server restart (`:lsp-restart`) to take effect after `:config-reload`/`:set`. | `true` |
| `goto-reference-include-declaration` | Include declaration in the goto references popup. | `true` |
Expand Down
2 changes: 2 additions & 0 deletions helix-core/src/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ pub enum LanguageServerFeature {
Diagnostics,
RenameSymbol,
InlayHints,
DocumentColors,
}

impl Display for LanguageServerFeature {
Expand All @@ -357,6 +358,7 @@ impl Display for LanguageServerFeature {
Diagnostics => "diagnostics",
RenameSymbol => "rename-symbol",
InlayHints => "inlay-hints",
DocumentColors => "document-colors",
};
write!(f, "{feature}",)
}
Expand Down
20 changes: 20 additions & 0 deletions helix-lsp/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ impl Client {
capabilities.inlay_hint_provider,
Some(OneOf::Left(true) | OneOf::Right(InlayHintServerCapabilities::Options(_)))
),
LanguageServerFeature::DocumentColors => capabilities.color_provider.is_some(),
}
}

Expand Down Expand Up @@ -1095,6 +1096,25 @@ impl Client {
Some(self.call::<lsp::request::InlayHintRequest>(params))
}

pub fn text_document_document_color(
&self,
text_document: lsp::TextDocumentIdentifier,
work_done_token: Option<lsp::ProgressToken>,
) -> Option<impl Future<Output = Result<Vec<lsp::ColorInformation>>>> {
self.capabilities.get().unwrap().color_provider.as_ref()?;
let params = lsp::DocumentColorParams {
text_document,
work_done_progress_params: lsp::WorkDoneProgressParams {
work_done_token: work_done_token.clone(),
},
partial_result_params: helix_lsp_types::PartialResultParams {
partial_result_token: work_done_token,
},
};

Some(self.call::<lsp::request::DocumentColor>(params))
}

pub fn text_document_hover(
&self,
text_document: lsp::TextDocumentIdentifier,
Expand Down
6 changes: 6 additions & 0 deletions helix-term/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ use crate::handlers::signature_help::SignatureHelpHandler;

pub use helix_view::handlers::Handlers;

use self::document_colors::DocumentColorsHandler;

mod auto_save;
pub mod completion;
mod diagnostics;
mod document_colors;
mod signature_help;
mod snippet;

Expand All @@ -22,11 +25,13 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
let event_tx = completion::CompletionHandler::new(config).spawn();
let signature_hints = SignatureHelpHandler::new().spawn();
let auto_save = AutoSaveHandler::new().spawn();
let document_colors = DocumentColorsHandler::default().spawn();

let handlers = Handlers {
completions: helix_view::handlers::completion::CompletionHandler::new(event_tx),
signature_hints,
auto_save,
document_colors,
};

helix_view::handlers::register_hooks(&handlers);
Expand All @@ -35,5 +40,6 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
auto_save::register_hooks(&handlers);
diagnostics::register_hooks(&handlers);
snippet::register_hooks(&handlers);
document_colors::register_hooks(&handlers);
handlers
}
204 changes: 204 additions & 0 deletions helix-term/src/handlers/document_colors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
use std::{collections::HashSet, time::Duration};

use futures_util::{stream::FuturesOrdered, StreamExt};
use helix_core::{syntax::LanguageServerFeature, text_annotations::InlineAnnotation};
use helix_event::{cancelable_future, register_hook};
use helix_lsp::lsp;
use helix_view::{
document::DocumentColorSwatches,
events::{DocumentDidChange, DocumentDidOpen, LanguageServerExited, LanguageServerInitialized},
handlers::{lsp::DocumentColorsEvent, Handlers},
DocumentId, Editor, Theme,
};
use tokio::time::Instant;

use crate::job;

#[derive(Default)]
pub(super) struct DocumentColorsHandler {
docs: HashSet<DocumentId>,
}

const DOCUMENT_CHANGE_DEBOUNCE: Duration = Duration::from_millis(250);

impl helix_event::AsyncHook for DocumentColorsHandler {
type Event = DocumentColorsEvent;

fn handle_event(&mut self, event: Self::Event, _timeout: Option<Instant>) -> Option<Instant> {
let DocumentColorsEvent(doc_id) = event;
self.docs.insert(doc_id);
Some(Instant::now() + DOCUMENT_CHANGE_DEBOUNCE)
}

fn finish_debounce(&mut self) {
let docs = std::mem::take(&mut self.docs);

job::dispatch_blocking(move |editor, _compositor| {
for doc in docs {
request_document_colors(editor, doc);
}
});
}
}

fn request_document_colors(editor: &mut Editor, doc_id: DocumentId) {
if !editor.config().lsp.display_color_swatches {
return;
}

let Some(doc) = editor.document_mut(doc_id) else {
return;
};

let cancel = doc.color_swatch_controller.restart();

let mut seen_language_servers = HashSet::new();
let mut futures: FuturesOrdered<_> = doc
.language_servers_with_feature(LanguageServerFeature::DocumentColors)
.filter(|ls| seen_language_servers.insert(ls.id()))
.map(|language_server| {
let text = doc.text().clone();
let offset_encoding = language_server.offset_encoding();
let future = language_server
.text_document_document_color(doc.identifier(), None)
.unwrap();

async move {
let colors: Vec<_> = future
.await?
.into_iter()
.filter_map(|color_info| {
let pos = helix_lsp::util::lsp_pos_to_pos(
&text,
color_info.range.start,
offset_encoding,
)?;
Some((pos, color_info.color))
})
.collect();
anyhow::Ok(colors)
}
})
.collect();

tokio::spawn(async move {
let mut all_colors = Vec::new();
loop {
match cancelable_future(futures.next(), &cancel).await {
Some(Some(Ok(items))) => all_colors.extend(items),
Some(Some(Err(err))) => log::error!("document color request failed: {err}"),
Some(None) => break,
// The request was cancelled.
None => return,
}
}
job::dispatch(move |editor, _| attach_document_colors(editor, doc_id, all_colors)).await;
});
}

fn attach_document_colors(
editor: &mut Editor,
doc_id: DocumentId,
mut doc_colors: Vec<(usize, lsp::Color)>,
) {
if !editor.config().lsp.display_color_swatches {
return;
}

let Some(doc) = editor.documents.get_mut(&doc_id) else {
return;
};

if doc_colors.is_empty() {
doc.color_swatches.take();
return;
}

doc_colors.sort_by_key(|(pos, _)| *pos);

let mut color_swatches = Vec::with_capacity(doc_colors.len());
let mut color_swatches_padding = Vec::with_capacity(doc_colors.len());
let mut colors = Vec::with_capacity(doc_colors.len());

for (pos, color) in doc_colors {
color_swatches_padding.push(InlineAnnotation::new(pos, " "));
color_swatches.push(InlineAnnotation::new(pos, "■"));
colors.push(Theme::rgb_highlight(
(color.red * 255.) as u8,
(color.green * 255.) as u8,
(color.blue * 255.) as u8,
));
}

doc.color_swatches = Some(DocumentColorSwatches {
color_swatches,
colors,
color_swatches_padding,
});
}

pub(super) fn register_hooks(handlers: &Handlers) {
register_hook!(move |event: &mut DocumentDidOpen<'_>| {
// when a document is initially opened, request colors for it
request_document_colors(event.editor, event.doc);

Ok(())
});

let tx = handlers.document_colors.clone();
register_hook!(move |event: &mut DocumentDidChange<'_>| {
// Update the color swatch' positions, helping ensure they are displayed in the
// proper place.
let apply_color_swatch_changes = |annotations: &mut Vec<InlineAnnotation>| {
event.changes.update_positions(
annotations
.iter_mut()
.map(|annotation| (&mut annotation.char_idx, helix_core::Assoc::After)),
);
};

if let Some(DocumentColorSwatches {
color_swatches,
colors: _colors,
color_swatches_padding,
}) = &mut event.doc.color_swatches
{
apply_color_swatch_changes(color_swatches);
apply_color_swatch_changes(color_swatches_padding);
}

// Cancel the ongoing request, if present.
event.doc.color_swatch_controller.cancel();

helix_event::send_blocking(&tx, DocumentColorsEvent(event.doc.id()));

Ok(())
});

register_hook!(move |event: &mut LanguageServerInitialized<'_>| {
let doc_ids: Vec<_> = event.editor.documents().map(|doc| doc.id()).collect();

for doc_id in doc_ids {
request_document_colors(event.editor, doc_id);
}

Ok(())
});

register_hook!(move |event: &mut LanguageServerExited<'_>| {
// Clear and re-request all color swatches when a server exits.
for doc in event.editor.documents_mut() {
if doc.supports_language_server(event.server_id) {
doc.color_swatches.take();
}
}

let doc_ids: Vec<_> = event.editor.documents().map(|doc| doc.id()).collect();

for doc_id in doc_ids {
request_document_colors(event.editor, doc_id);
}

Ok(())
});
}
16 changes: 16 additions & 0 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use helix_core::encoding::Encoding;
use helix_core::snippets::{ActiveSnippet, SnippetRenderCtx};
use helix_core::syntax::{Highlight, LanguageServerFeature};
use helix_core::text_annotations::{InlineAnnotation, Overlay};
use helix_event::TaskController;
use helix_lsp::util::lsp_pos_to_pos;
use helix_stdx::faccess::{copy_metadata, readonly};
use helix_vcs::{DiffHandle, DiffProviderRegistry};
Expand Down Expand Up @@ -200,6 +201,19 @@ pub struct Document {
pub focused_at: std::time::Instant,

pub readonly: bool,

/// Annotations for LSP document color swatches
pub color_swatches: Option<DocumentColorSwatches>,
// NOTE: ideally this would live on the handler for color swatches. This is blocked on a
// large refactor that would make `&mut Editor` available on the `DocumentDidChange` event.
pub color_swatch_controller: TaskController,
}

#[derive(Debug, Clone, Default)]
pub struct DocumentColorSwatches {
pub color_swatches: Vec<InlineAnnotation>,
pub colors: Vec<Highlight>,
pub color_swatches_padding: Vec<InlineAnnotation>,
}

/// Inlay hints for a single `(Document, View)` combo.
Expand Down Expand Up @@ -703,6 +717,8 @@ impl Document {
focused_at: std::time::Instant::now(),
readonly: false,
jump_labels: HashMap::new(),
color_swatches: None,
color_swatch_controller: TaskController::new(),
}
}

Expand Down
3 changes: 3 additions & 0 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,8 @@ pub struct LspConfig {
pub display_signature_help_docs: bool,
/// Display inlay hints
pub display_inlay_hints: bool,
/// Display document color swatches
pub display_color_swatches: bool,
/// Whether to enable snippet support
pub snippets: bool,
/// Whether to include declaration in the goto reference query
Expand All @@ -473,6 +475,7 @@ impl Default for LspConfig {
display_inlay_hints: false,
snippets: true,
goto_reference_include_declaration: true,
display_color_swatches: true,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions helix-view/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub struct Handlers {
pub completions: CompletionHandler,
pub signature_hints: Sender<lsp::SignatureHelpEvent>,
pub auto_save: Sender<AutoSaveEvent>,
pub document_colors: Sender<lsp::DocumentColorsEvent>,
}

impl Handlers {
Expand Down
4 changes: 3 additions & 1 deletion helix-view/src/handlers/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::editor::Action;
use crate::events::{
DiagnosticsDidChange, DocumentDidChange, DocumentDidClose, LanguageServerInitialized,
};
use crate::Editor;
use crate::{DocumentId, Editor};
use helix_core::diagnostic::DiagnosticProvider;
use helix_core::Uri;
use helix_event::register_hook;
Expand All @@ -14,6 +14,8 @@ use helix_lsp::{lsp, LanguageServerId, OffsetEncoding};

use super::Handlers;

pub struct DocumentColorsEvent(pub DocumentId);

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum SignatureHelpInvoked {
Automatic,
Expand Down
Loading