Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:00:58.824Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

document_colors.rs

495 lines · 19.8 KB · rust
1use std::{sync::Arc, time::Duration};
2
3use anyhow::{Context as _, Result};
4use collections::{HashMap, HashSet};
5use futures::{
6    FutureExt as _,
7    future::{Shared, join_all},
8};
9use gpui::{AppContext as _, AsyncApp, Context, Entity, SharedString, Task};
10use language::{
11    Buffer, LocalFile as _, PointUtf16, point_to_lsp,
12    proto::{deserialize_lsp_edit, serialize_lsp_edit},
13};
14use lsp::LanguageServerId;
15use rpc::{TypedEnvelope, proto};
16use settings::Settings as _;
17use text::BufferId;
18use util::ResultExt as _;
19use worktree::File;
20
21use crate::{
22    ColorPresentation, DocumentColor, LspStore,
23    lsp_command::{GetDocumentColor, LspCommand as _, make_text_document_identifier},
24    lsp_store::{
25        LspStoreEvent, RunningFetch, missing_servers_to_query, next_lsp_fetch_id,
26        upstream_lsp_query_server_filter,
27    },
28    project_settings::ProjectSettings,
29};
30
31#[derive(Debug, Default, Clone)]
32pub struct DocumentColors {
33    pub colors: HashSet<DocumentColor>,
34}
35
36pub(super) type DocumentColorTask =
37    Shared<Task<std::result::Result<DocumentColors, Arc<anyhow::Error>>>>;
38
39#[derive(Debug, Default)]
40pub(super) struct DocumentColorData {
41    pub(super) colors: HashMap<LanguageServerId, HashSet<DocumentColor>>,
42    fetched_servers: HashSet<LanguageServerId>,
43    pub(super) colors_update: Option<RunningFetch<DocumentColorTask>>,
44}
45
46impl DocumentColorData {
47    pub(super) fn remove_server_data(&mut self, server_id: LanguageServerId) {
48        self.colors.remove(&server_id);
49        self.fetched_servers.remove(&server_id);
50        RunningFetch::discard_if_queried(&mut self.colors_update, server_id);
51    }
52
53    fn evict(&mut self, for_server: Option<LanguageServerId>) {
54        match for_server {
55            Some(server_id) => self.remove_server_data(server_id),
56            None => {
57                self.colors.clear();
58                self.fetched_servers.clear();
59            }
60        }
61        self.colors_update = None;
62    }
63}
64
65impl LspStore {
66    pub(super) fn refresh_document_colors(
67        &mut self,
68        for_server: Option<LanguageServerId>,
69        cx: &mut Context<Self>,
70    ) {
71        for lsp_data in self.lsp_data.values_mut() {
72            if let Some(document_colors) = &mut lsp_data.document_colors {
73                document_colors.evict(for_server);
74            }
75        }
76
77        cx.emit(LspStoreEvent::RefreshDocumentColors {
78            server_id: for_server,
79        });
80        if let Some((downstream_client, project_id)) = self.downstream_client.as_ref() {
81            downstream_client
82                .send(proto::RefreshDocumentColors {
83                    project_id: *project_id,
84                    server_id: for_server.map(|server_id| server_id.to_proto()),
85                })
86                .context("sending refresh document colors downstream")
87                .log_err();
88        }
89    }
90
91    pub(super) async fn handle_refresh_document_colors(
92        lsp_store: Entity<Self>,
93        envelope: TypedEnvelope<proto::RefreshDocumentColors>,
94        mut cx: AsyncApp,
95    ) -> Result<proto::Ack> {
96        lsp_store.update(&mut cx, |lsp_store, cx| {
97            let server_id = envelope.payload.server_id.map(LanguageServerId::from_proto);
98            lsp_store.refresh_document_colors(server_id, cx);
99        });
100        Ok(proto::Ack {})
101    }
102
103    pub fn document_colors(
104        &mut self,
105        buffer: Entity<Buffer>,
106        cx: &mut Context<Self>,
107    ) -> Option<DocumentColorTask> {
108        let version_queried_for = buffer.read(cx).version();
109        let buffer_id = buffer.read(cx).remote_id();
110
111        let current_servers = self.relevant_server_ids_for_capability_check(&buffer, cx);
112
113        let mut servers_to_query = None;
114        if let Some(lsp_data) = self.current_lsp_data(buffer_id) {
115            if !version_queried_for.changed_since(&lsp_data.buffer_version)
116                && let Some(cached_colors) = &mut lsp_data.document_colors
117            {
118                match missing_servers_to_query(
119                    &mut cached_colors.colors,
120                    &mut cached_colors.fetched_servers,
121                    &current_servers,
122                ) {
123                    Some(missing_servers) => servers_to_query = Some(missing_servers),
124                    None => {
125                        return Some(
126                            Task::ready(Ok(DocumentColors {
127                                colors: cached_colors.colors.values().flatten().cloned().collect(),
128                            }))
129                            .shared(),
130                        );
131                    }
132                }
133            }
134            if let Some(document_colors) = &lsp_data.document_colors
135                && let Some(running) = &document_colors.colors_update
136                && !version_queried_for.changed_since(&running.version)
137                && servers_to_query
138                    .as_ref()
139                    .is_none_or(|missing| missing.is_subset(&running.servers))
140            {
141                return Some(running.task.clone());
142            }
143        }
144
145        let color_lsp_data = self
146            .latest_lsp_data(&buffer, cx)
147            .document_colors
148            .get_or_insert_default();
149        let fetch_id = next_lsp_fetch_id();
150        let queried_servers = servers_to_query
151            .clone()
152            .unwrap_or_else(|| current_servers.clone());
153        let buffer_version_queried_for = version_queried_for.clone();
154        let new_task = cx
155            .spawn({
156                let queried_servers = queried_servers.clone();
157                async move |lsp_store, cx| {
158                    cx.background_executor()
159                        .timer(Duration::from_millis(30))
160                        .await;
161                    let fetched_colors = lsp_store
162                        .update(cx, |lsp_store, cx| {
163                            lsp_store.fetch_document_colors_for_buffer(
164                                &buffer,
165                                servers_to_query,
166                                cx,
167                            )
168                        })?
169                        .await
170                        .context("fetching document colors")
171                        .map_err(Arc::new);
172                    let fetched_colors = match fetched_colors {
173                        Ok(fetched_colors) => {
174                            if buffer.update(cx, |buffer, _| {
175                                buffer.version() != buffer_version_queried_for
176                            }) {
177                                return Ok(DocumentColors::default());
178                            }
179                            fetched_colors
180                        }
181                        Err(e) => {
182                            lsp_store
183                                .update(cx, |lsp_store, _| {
184                                    if let Some(lsp_data) = lsp_store.lsp_data.get_mut(&buffer_id)
185                                        && let Some(document_colors) = &mut lsp_data.document_colors
186                                    {
187                                        RunningFetch::take_finished(
188                                            &mut document_colors.colors_update,
189                                            fetch_id,
190                                        );
191                                    }
192                                })
193                                .ok();
194                            return Err(e);
195                        }
196                    };
197
198                    lsp_store
199                        .update(cx, |lsp_store, cx| {
200                            let lsp_data = lsp_store.latest_lsp_data(&buffer, cx);
201                            let lsp_colors = lsp_data.document_colors.get_or_insert_default();
202
203                            if RunningFetch::take_finished(&mut lsp_colors.colors_update, fetch_id)
204                                && let Some(fetched_colors) = fetched_colors
205                            {
206                                if lsp_data.buffer_version == buffer_version_queried_for {
207                                    lsp_colors.colors.extend(fetched_colors);
208                                    lsp_colors.fetched_servers.extend(queried_servers);
209                                } else if !lsp_data
210                                    .buffer_version
211                                    .changed_since(&buffer_version_queried_for)
212                                {
213                                    lsp_data.buffer_version = buffer_version_queried_for;
214                                    lsp_colors.colors = fetched_colors;
215                                    lsp_colors.fetched_servers = queried_servers;
216                                }
217                            }
218                            let colors = lsp_colors
219                                .colors
220                                .values()
221                                .flatten()
222                                .cloned()
223                                .collect::<HashSet<_>>();
224                            DocumentColors { colors }
225                        })
226                        .map_err(Arc::new)
227                }
228            })
229            .shared();
230        color_lsp_data.colors_update = Some(RunningFetch {
231            id: fetch_id,
232            version: version_queried_for,
233            servers: queried_servers,
234            task: new_task.clone(),
235        });
236        Some(new_task)
237    }
238
239    pub fn resolve_color_presentation(
240        &mut self,
241        mut color: DocumentColor,
242        buffer: Entity<Buffer>,
243        server_id: LanguageServerId,
244        cx: &mut Context<Self>,
245    ) -> Task<Result<DocumentColor>> {
246        if color.resolved {
247            return Task::ready(Ok(color));
248        }
249
250        if let Some((upstream_client, project_id)) = self.upstream_client() {
251            let start = color.lsp_range.start;
252            let end = color.lsp_range.end;
253            let request = proto::GetColorPresentation {
254                project_id,
255                server_id: server_id.to_proto(),
256                buffer_id: buffer.read(cx).remote_id().into(),
257                color: Some(proto::ColorInformation {
258                    red: color.color.red,
259                    green: color.color.green,
260                    blue: color.color.blue,
261                    alpha: color.color.alpha,
262                    lsp_range_start: Some(proto::PointUtf16 {
263                        row: start.line,
264                        column: start.character,
265                    }),
266                    lsp_range_end: Some(proto::PointUtf16 {
267                        row: end.line,
268                        column: end.character,
269                    }),
270                }),
271            };
272            cx.background_spawn(async move {
273                let response = upstream_client
274                    .request(request)
275                    .await
276                    .context("color presentation proto request")?;
277                color.resolved = true;
278                color.color_presentations = response
279                    .presentations
280                    .into_iter()
281                    .map(|presentation| ColorPresentation {
282                        label: SharedString::from(presentation.label),
283                        text_edit: presentation.text_edit.and_then(deserialize_lsp_edit),
284                        additional_text_edits: presentation
285                            .additional_text_edits
286                            .into_iter()
287                            .filter_map(deserialize_lsp_edit)
288                            .collect(),
289                    })
290                    .collect();
291                Ok(color)
292            })
293        } else {
294            let path = match buffer
295                .update(cx, |buffer, cx| {
296                    Some(File::from_dyn(buffer.file())?.abs_path(cx))
297                })
298                .context("buffer with the missing path")
299            {
300                Ok(path) => path,
301                Err(e) => return Task::ready(Err(e)),
302            };
303            let Some(lang_server) = buffer.update(cx, |buffer, cx| {
304                self.language_server_for_local_buffer(buffer, server_id, cx)
305                    .map(|(_, server)| server.clone())
306            }) else {
307                return Task::ready(Ok(color));
308            };
309
310            let request_timeout = ProjectSettings::get_global(cx)
311                .global_lsp_settings
312                .get_request_timeout();
313            cx.background_spawn(async move {
314                let resolve_task = lang_server.request::<lsp::request::ColorPresentationRequest>(
315                    lsp::ColorPresentationParams {
316                        text_document: make_text_document_identifier(&path)?,
317                        color: color.color,
318                        range: color.lsp_range,
319                        work_done_progress_params: Default::default(),
320                        partial_result_params: Default::default(),
321                    },
322                    request_timeout,
323                );
324                color.color_presentations = resolve_task
325                    .await
326                    .into_response()
327                    .context("color presentation resolve LSP request")?
328                    .into_iter()
329                    .map(|presentation| ColorPresentation {
330                        label: SharedString::from(presentation.label),
331                        text_edit: presentation.text_edit,
332                        additional_text_edits: presentation
333                            .additional_text_edits
334                            .unwrap_or_default(),
335                    })
336                    .collect();
337                color.resolved = true;
338                Ok(color)
339            })
340        }
341    }
342
343    pub(super) fn fetch_document_colors_for_buffer(
344        &mut self,
345        buffer: &Entity<Buffer>,
346        for_servers: Option<HashSet<LanguageServerId>>,
347        cx: &mut Context<Self>,
348    ) -> Task<anyhow::Result<Option<HashMap<LanguageServerId, HashSet<DocumentColor>>>>> {
349        if let Some((client, project_id)) = self.upstream_client() {
350            let request = GetDocumentColor {};
351            if !self.is_capable_for_proto_request(buffer, &request, cx) {
352                return Task::ready(Ok(None));
353            }
354
355            let request_timeout = ProjectSettings::get_global(cx)
356                .global_lsp_settings
357                .get_request_timeout();
358            let request_task = client.request_lsp(
359                project_id,
360                upstream_lsp_query_server_filter(for_servers.as_ref()),
361                request_timeout,
362                cx.background_executor().clone(),
363                request.to_proto(project_id, buffer.read(cx)),
364            );
365            let buffer = buffer.clone();
366            cx.spawn(async move |lsp_store, cx| {
367                let Some(lsp_store) = lsp_store.upgrade() else {
368                    return Ok(None);
369                };
370                let colors: HashMap<LanguageServerId, HashSet<DocumentColor>> = join_all(
371                    request_task
372                        .await
373                        .log_err()
374                        .flatten()
375                        .map(|response| response.payload)
376                        .unwrap_or_default()
377                        .into_iter()
378                        .map(|color_response| {
379                            let response = request.response_from_proto(
380                                color_response.response,
381                                lsp_store.clone(),
382                                buffer.clone(),
383                                cx.clone(),
384                            );
385                            async move {
386                                let colors: Vec<DocumentColor> =
387                                    response.await.log_err().unwrap_or_default();
388                                (
389                                    LanguageServerId::from_proto(color_response.server_id),
390                                    colors,
391                                )
392                            }
393                        }),
394                )
395                .await
396                .into_iter()
397                .fold(HashMap::default(), |mut acc, (server_id, colors)| {
398                    acc.entry(server_id)
399                        .or_insert_with(HashSet::default)
400                        .extend(colors);
401                    acc
402                });
403                Ok(Some(colors))
404            })
405        } else {
406            let document_colors_task = self.request_filtered_lsp_locally(
407                buffer,
408                None::<usize>,
409                GetDocumentColor,
410                for_servers.as_ref(),
411                cx,
412            );
413            cx.background_spawn(async move {
414                Ok(Some(
415                    document_colors_task
416                        .await
417                        .into_iter()
418                        .fold(HashMap::default(), |mut acc, (server_id, colors)| {
419                            acc.entry(server_id)
420                                .or_insert_with(HashSet::default)
421                                .extend(colors);
422                            acc
423                        })
424                        .into_iter()
425                        .collect(),
426                ))
427            })
428        }
429    }
430
431    pub(super) async fn handle_get_color_presentation(
432        lsp_store: Entity<Self>,
433        envelope: TypedEnvelope<proto::GetColorPresentation>,
434        mut cx: AsyncApp,
435    ) -> Result<proto::GetColorPresentationResponse> {
436        let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
437        let buffer = lsp_store.update(&mut cx, |lsp_store, cx| {
438            lsp_store.buffer_store.read(cx).get_existing(buffer_id)
439        })?;
440
441        let color = envelope
442            .payload
443            .color
444            .context("invalid color resolve request")?;
445        let start = color
446            .lsp_range_start
447            .context("invalid color resolve request")?;
448        let end = color
449            .lsp_range_end
450            .context("invalid color resolve request")?;
451
452        let color = DocumentColor {
453            lsp_range: lsp::Range {
454                start: point_to_lsp(PointUtf16::new(start.row, start.column)),
455                end: point_to_lsp(PointUtf16::new(end.row, end.column)),
456            },
457            color: lsp::Color {
458                red: color.red,
459                green: color.green,
460                blue: color.blue,
461                alpha: color.alpha,
462            },
463            resolved: false,
464            color_presentations: Vec::new(),
465        };
466        let resolved_color = lsp_store
467            .update(&mut cx, |lsp_store, cx| {
468                lsp_store.resolve_color_presentation(
469                    color,
470                    buffer.clone(),
471                    LanguageServerId(envelope.payload.server_id as usize),
472                    cx,
473                )
474            })
475            .await
476            .context("resolving color presentation")?;
477
478        Ok(proto::GetColorPresentationResponse {
479            presentations: resolved_color
480                .color_presentations
481                .into_iter()
482                .map(|presentation| proto::ColorPresentation {
483                    label: presentation.label.to_string(),
484                    text_edit: presentation.text_edit.map(serialize_lsp_edit),
485                    additional_text_edits: presentation
486                        .additional_text_edits
487                        .into_iter()
488                        .map(serialize_lsp_edit)
489                        .collect(),
490                })
491                .collect(),
492        })
493    }
494}
495
Served at tenant.openagents/omega Member data and write actions are omitted.