hover_popover.rs

   1use crate::{
   2    display_map::{InlayOffset, ToDisplayPoint},
   3    link_go_to_definition::{InlayHighlight, RangeInEditor},
   4    Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle,
   5    ExcerptId, RangeToAnchorExt,
   6};
   7use futures::FutureExt;
   8use gpui::{
   9    AnyElement, AppContext, CursorStyle, Element, Model, MouseButton, Task, ViewContext, WeakView,
  10};
  11use language::{
  12    markdown, Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, ParsedMarkdown,
  13};
  14use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};
  15use std::{ops::Range, sync::Arc, time::Duration};
  16use util::TryFutureExt;
  17use workspace::Workspace;
  18
  19pub const HOVER_DELAY_MILLIS: u64 = 350;
  20pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
  21
  22pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.;
  23pub const MIN_POPOVER_LINE_HEIGHT: f32 = 4.;
  24pub const HOVER_POPOVER_GAP: f32 = 10.;
  25
  26// actions!(editor, [Hover]);
  27
  28pub fn init(cx: &mut AppContext) {
  29    // cx.add_action(hover);
  30}
  31
  32// todo!()
  33// /// Bindable action which uses the most recent selection head to trigger a hover
  34// pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
  35//     let head = editor.selections.newest_display(cx).head();
  36//     show_hover(editor, head, true, cx);
  37// }
  38
  39/// The internal hover action dispatches between `show_hover` or `hide_hover`
  40/// depending on whether a point to hover over is provided.
  41pub fn hover_at(editor: &mut Editor, point: Option<DisplayPoint>, cx: &mut ViewContext<Editor>) {
  42    if EditorSettings::get_global(cx).hover_popover_enabled {
  43        if let Some(point) = point {
  44            show_hover(editor, point, false, cx);
  45        } else {
  46            hide_hover(editor, cx);
  47        }
  48    }
  49}
  50
  51pub struct InlayHover {
  52    pub excerpt: ExcerptId,
  53    pub range: InlayHighlight,
  54    pub tooltip: HoverBlock,
  55}
  56
  57pub fn find_hovered_hint_part(
  58    label_parts: Vec<InlayHintLabelPart>,
  59    hint_start: InlayOffset,
  60    hovered_offset: InlayOffset,
  61) -> Option<(InlayHintLabelPart, Range<InlayOffset>)> {
  62    if hovered_offset >= hint_start {
  63        let mut hovered_character = (hovered_offset - hint_start).0;
  64        let mut part_start = hint_start;
  65        for part in label_parts {
  66            let part_len = part.value.chars().count();
  67            if hovered_character > part_len {
  68                hovered_character -= part_len;
  69                part_start.0 += part_len;
  70            } else {
  71                let part_end = InlayOffset(part_start.0 + part_len);
  72                return Some((part, part_start..part_end));
  73            }
  74        }
  75    }
  76    None
  77}
  78
  79pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut ViewContext<Editor>) {
  80    if EditorSettings::get_global(cx).hover_popover_enabled {
  81        if editor.pending_rename.is_some() {
  82            return;
  83        }
  84
  85        let Some(project) = editor.project.clone() else {
  86            return;
  87        };
  88
  89        if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover {
  90            if let RangeInEditor::Inlay(range) = symbol_range {
  91                if range == &inlay_hover.range {
  92                    // Hover triggered from same location as last time. Don't show again.
  93                    return;
  94                }
  95            }
  96            hide_hover(editor, cx);
  97        }
  98
  99        let task = cx.spawn(|this, mut cx| {
 100            async move {
 101                cx.background()
 102                    .timer(Duration::from_millis(HOVER_DELAY_MILLIS))
 103                    .await;
 104                this.update(&mut cx, |this, _| {
 105                    this.hover_state.diagnostic_popover = None;
 106                })?;
 107
 108                let language_registry = project.update(&mut cx, |p, _| p.languages().clone());
 109                let blocks = vec![inlay_hover.tooltip];
 110                let parsed_content = parse_blocks(&blocks, &language_registry, None).await;
 111
 112                let hover_popover = InfoPopover {
 113                    project: project.clone(),
 114                    symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
 115                    blocks,
 116                    parsed_content,
 117                };
 118
 119                this.update(&mut cx, |this, cx| {
 120                    // Highlight the selected symbol using a background highlight
 121                    this.highlight_inlay_background::<HoverState>(
 122                        vec![inlay_hover.range],
 123                        |theme| theme.editor.hover_popover.highlight,
 124                        cx,
 125                    );
 126                    this.hover_state.info_popover = Some(hover_popover);
 127                    cx.notify();
 128                })?;
 129
 130                anyhow::Ok(())
 131            }
 132            .log_err()
 133        });
 134
 135        editor.hover_state.info_task = Some(task);
 136    }
 137}
 138
 139/// Hides the type information popup.
 140/// Triggered by the `Hover` action when the cursor is not over a symbol or when the
 141/// selections changed.
 142pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) -> bool {
 143    let did_hide = editor.hover_state.info_popover.take().is_some()
 144        | editor.hover_state.diagnostic_popover.take().is_some();
 145
 146    editor.hover_state.info_task = None;
 147    editor.hover_state.triggered_from = None;
 148
 149    editor.clear_background_highlights::<HoverState>(cx);
 150
 151    if did_hide {
 152        cx.notify();
 153    }
 154
 155    did_hide
 156}
 157
 158/// Queries the LSP and shows type info and documentation
 159/// about the symbol the mouse is currently hovering over.
 160/// Triggered by the `Hover` action when the cursor may be over a symbol.
 161fn show_hover(
 162    editor: &mut Editor,
 163    point: DisplayPoint,
 164    ignore_timeout: bool,
 165    cx: &mut ViewContext<Editor>,
 166) {
 167    if editor.pending_rename.is_some() {
 168        return;
 169    }
 170
 171    let snapshot = editor.snapshot(cx);
 172    let multibuffer_offset = point.to_offset(&snapshot.display_snapshot, Bias::Left);
 173
 174    let (buffer, buffer_position) = if let Some(output) = editor
 175        .buffer
 176        .read(cx)
 177        .text_anchor_for_position(multibuffer_offset, cx)
 178    {
 179        output
 180    } else {
 181        return;
 182    };
 183
 184    let excerpt_id = if let Some((excerpt_id, _, _)) = editor
 185        .buffer()
 186        .read(cx)
 187        .excerpt_containing(multibuffer_offset, cx)
 188    {
 189        excerpt_id
 190    } else {
 191        return;
 192    };
 193
 194    let project = if let Some(project) = editor.project.clone() {
 195        project
 196    } else {
 197        return;
 198    };
 199
 200    if !ignore_timeout {
 201        if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover {
 202            if symbol_range
 203                .as_text_range()
 204                .map(|range| {
 205                    range
 206                        .to_offset(&snapshot.buffer_snapshot)
 207                        .contains(&multibuffer_offset)
 208                })
 209                .unwrap_or(false)
 210            {
 211                // Hover triggered from same location as last time. Don't show again.
 212                return;
 213            } else {
 214                hide_hover(editor, cx);
 215            }
 216        }
 217    }
 218
 219    // Get input anchor
 220    let anchor = snapshot
 221        .buffer_snapshot
 222        .anchor_at(multibuffer_offset, Bias::Left);
 223
 224    // Don't request again if the location is the same as the previous request
 225    if let Some(triggered_from) = &editor.hover_state.triggered_from {
 226        if triggered_from
 227            .cmp(&anchor, &snapshot.buffer_snapshot)
 228            .is_eq()
 229        {
 230            return;
 231        }
 232    }
 233
 234    let task = cx.spawn(|this, mut cx| {
 235        async move {
 236            // If we need to delay, delay a set amount initially before making the lsp request
 237            let delay = if !ignore_timeout {
 238                // Construct delay task to wait for later
 239                let total_delay = Some(
 240                    cx.background()
 241                        .timer(Duration::from_millis(HOVER_DELAY_MILLIS)),
 242                );
 243
 244                cx.background()
 245                    .timer(Duration::from_millis(HOVER_REQUEST_DELAY_MILLIS))
 246                    .await;
 247                total_delay
 248            } else {
 249                None
 250            };
 251
 252            // query the LSP for hover info
 253            let hover_request = cx.update(|cx| {
 254                project.update(cx, |project, cx| {
 255                    project.hover(&buffer, buffer_position, cx)
 256                })
 257            });
 258
 259            if let Some(delay) = delay {
 260                delay.await;
 261            }
 262
 263            // If there's a diagnostic, assign it on the hover state and notify
 264            let local_diagnostic = snapshot
 265                .buffer_snapshot
 266                .diagnostics_in_range::<_, usize>(multibuffer_offset..multibuffer_offset, false)
 267                // Find the entry with the most specific range
 268                .min_by_key(|entry| entry.range.end - entry.range.start)
 269                .map(|entry| DiagnosticEntry {
 270                    diagnostic: entry.diagnostic,
 271                    range: entry.range.to_anchors(&snapshot.buffer_snapshot),
 272                });
 273
 274            // Pull the primary diagnostic out so we can jump to it if the popover is clicked
 275            let primary_diagnostic = local_diagnostic.as_ref().and_then(|local_diagnostic| {
 276                snapshot
 277                    .buffer_snapshot
 278                    .diagnostic_group::<usize>(local_diagnostic.diagnostic.group_id)
 279                    .find(|diagnostic| diagnostic.diagnostic.is_primary)
 280                    .map(|entry| DiagnosticEntry {
 281                        diagnostic: entry.diagnostic,
 282                        range: entry.range.to_anchors(&snapshot.buffer_snapshot),
 283                    })
 284            });
 285
 286            this.update(&mut cx, |this, _| {
 287                this.hover_state.diagnostic_popover =
 288                    local_diagnostic.map(|local_diagnostic| DiagnosticPopover {
 289                        local_diagnostic,
 290                        primary_diagnostic,
 291                    });
 292            })?;
 293
 294            let hover_result = hover_request.await.ok().flatten();
 295            let hover_popover = match hover_result {
 296                Some(hover_result) if !hover_result.is_empty() => {
 297                    // Create symbol range of anchors for highlighting and filtering of future requests.
 298                    let range = if let Some(range) = hover_result.range {
 299                        let start = snapshot
 300                            .buffer_snapshot
 301                            .anchor_in_excerpt(excerpt_id.clone(), range.start);
 302                        let end = snapshot
 303                            .buffer_snapshot
 304                            .anchor_in_excerpt(excerpt_id.clone(), range.end);
 305
 306                        start..end
 307                    } else {
 308                        anchor..anchor
 309                    };
 310
 311                    let language_registry = project.update(&mut cx, |p, _| p.languages().clone());
 312                    let blocks = hover_result.contents;
 313                    let language = hover_result.language;
 314                    let parsed_content = parse_blocks(&blocks, &language_registry, language).await;
 315
 316                    Some(InfoPopover {
 317                        project: project.clone(),
 318                        symbol_range: RangeInEditor::Text(range),
 319                        blocks,
 320                        parsed_content,
 321                    })
 322                }
 323
 324                _ => None,
 325            };
 326
 327            this.update(&mut cx, |this, cx| {
 328                if let Some(symbol_range) = hover_popover
 329                    .as_ref()
 330                    .and_then(|hover_popover| hover_popover.symbol_range.as_text_range())
 331                {
 332                    // Highlight the selected symbol using a background highlight
 333                    this.highlight_background::<HoverState>(
 334                        vec![symbol_range],
 335                        |theme| theme.editor.hover_popover.highlight,
 336                        cx,
 337                    );
 338                } else {
 339                    this.clear_background_highlights::<HoverState>(cx);
 340                }
 341
 342                this.hover_state.info_popover = hover_popover;
 343                cx.notify();
 344            })?;
 345
 346            Ok::<_, anyhow::Error>(())
 347        }
 348        .log_err()
 349    });
 350
 351    editor.hover_state.info_task = Some(task);
 352}
 353
 354async fn parse_blocks(
 355    blocks: &[HoverBlock],
 356    language_registry: &Arc<LanguageRegistry>,
 357    language: Option<Arc<Language>>,
 358) -> markdown::ParsedMarkdown {
 359    let mut text = String::new();
 360    let mut highlights = Vec::new();
 361    let mut region_ranges = Vec::new();
 362    let mut regions = Vec::new();
 363
 364    for block in blocks {
 365        match &block.kind {
 366            HoverBlockKind::PlainText => {
 367                markdown::new_paragraph(&mut text, &mut Vec::new());
 368                text.push_str(&block.text);
 369            }
 370
 371            HoverBlockKind::Markdown => {
 372                markdown::parse_markdown_block(
 373                    &block.text,
 374                    language_registry,
 375                    language.clone(),
 376                    &mut text,
 377                    &mut highlights,
 378                    &mut region_ranges,
 379                    &mut regions,
 380                )
 381                .await
 382            }
 383
 384            HoverBlockKind::Code { language } => {
 385                if let Some(language) = language_registry
 386                    .language_for_name(language)
 387                    .now_or_never()
 388                    .and_then(Result::ok)
 389                {
 390                    markdown::highlight_code(&mut text, &mut highlights, &block.text, &language);
 391                } else {
 392                    text.push_str(&block.text);
 393                }
 394            }
 395        }
 396    }
 397
 398    ParsedMarkdown {
 399        text: text.trim().to_string(),
 400        highlights,
 401        region_ranges,
 402        regions,
 403    }
 404}
 405
 406#[derive(Default)]
 407pub struct HoverState {
 408    pub info_popover: Option<InfoPopover>,
 409    pub diagnostic_popover: Option<DiagnosticPopover>,
 410    pub triggered_from: Option<Anchor>,
 411    pub info_task: Option<Task<Option<()>>>,
 412}
 413
 414impl HoverState {
 415    pub fn visible(&self) -> bool {
 416        self.info_popover.is_some() || self.diagnostic_popover.is_some()
 417    }
 418
 419    pub fn render(
 420        &mut self,
 421        snapshot: &EditorSnapshot,
 422        style: &EditorStyle,
 423        visible_rows: Range<u32>,
 424        workspace: Option<WeakView<Workspace>>,
 425        cx: &mut ViewContext<Editor>,
 426    ) -> Option<(DisplayPoint, Vec<AnyElement<Editor>>)> {
 427        // If there is a diagnostic, position the popovers based on that.
 428        // Otherwise use the start of the hover range
 429        let anchor = self
 430            .diagnostic_popover
 431            .as_ref()
 432            .map(|diagnostic_popover| &diagnostic_popover.local_diagnostic.range.start)
 433            .or_else(|| {
 434                self.info_popover
 435                    .as_ref()
 436                    .map(|info_popover| match &info_popover.symbol_range {
 437                        RangeInEditor::Text(range) => &range.start,
 438                        RangeInEditor::Inlay(range) => &range.inlay_position,
 439                    })
 440            })?;
 441        let point = anchor.to_display_point(&snapshot.display_snapshot);
 442
 443        // Don't render if the relevant point isn't on screen
 444        if !self.visible() || !visible_rows.contains(&point.row()) {
 445            return None;
 446        }
 447
 448        let mut elements = Vec::new();
 449
 450        if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() {
 451            elements.push(diagnostic_popover.render(style, cx));
 452        }
 453        if let Some(info_popover) = self.info_popover.as_mut() {
 454            elements.push(info_popover.render(style, workspace, cx));
 455        }
 456
 457        Some((point, elements))
 458    }
 459}
 460
 461#[derive(Debug, Clone)]
 462pub struct InfoPopover {
 463    pub project: Model<Project>,
 464    symbol_range: RangeInEditor,
 465    pub blocks: Vec<HoverBlock>,
 466    parsed_content: ParsedMarkdown,
 467}
 468
 469// impl InfoPopover {
 470//     pub fn render(
 471//         &mut self,
 472//         style: &EditorStyle,
 473//         workspace: Option<WeakView<Workspace>>,
 474//         cx: &mut ViewContext<Editor>,
 475//     ) -> AnyElement<Editor> {
 476//         MouseEventHandler::new::<InfoPopover, _>(0, cx, |_, cx| {
 477//             Flex::column()
 478//                 .scrollable::<HoverBlock>(0, None, cx)
 479//                 .with_child(crate::render_parsed_markdown::<HoverBlock>(
 480//                     &self.parsed_content,
 481//                     style,
 482//                     workspace,
 483//                     cx,
 484//                 ))
 485//                 .contained()
 486//                 .with_style(style.hover_popover.container)
 487//         })
 488//         .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath.
 489//         .with_cursor_style(CursorStyle::Arrow)
 490//         .with_padding(Padding {
 491//             bottom: HOVER_POPOVER_GAP,
 492//             top: HOVER_POPOVER_GAP,
 493//             ..Default::default()
 494//         })
 495//         .into_any()
 496//     }
 497// }
 498
 499#[derive(Debug, Clone)]
 500pub struct DiagnosticPopover {
 501    local_diagnostic: DiagnosticEntry<Anchor>,
 502    primary_diagnostic: Option<DiagnosticEntry<Anchor>>,
 503}
 504
 505impl DiagnosticPopover {
 506    pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext<Editor>) -> AnyElement<Editor> {
 507        todo!()
 508        // enum PrimaryDiagnostic {}
 509
 510        // let mut text_style = style.hover_popover.prose.clone();
 511        // text_style.font_size = style.text.font_size;
 512        // let diagnostic_source_style = style.hover_popover.diagnostic_source_highlight.clone();
 513
 514        // let text = match &self.local_diagnostic.diagnostic.source {
 515        //     Some(source) => Text::new(
 516        //         format!("{source}: {}", self.local_diagnostic.diagnostic.message),
 517        //         text_style,
 518        //     )
 519        //     .with_highlights(vec![(0..source.len(), diagnostic_source_style)]),
 520
 521        //     None => Text::new(self.local_diagnostic.diagnostic.message.clone(), text_style),
 522        // };
 523
 524        // let container_style = match self.local_diagnostic.diagnostic.severity {
 525        //     DiagnosticSeverity::HINT => style.hover_popover.info_container,
 526        //     DiagnosticSeverity::INFORMATION => style.hover_popover.info_container,
 527        //     DiagnosticSeverity::WARNING => style.hover_popover.warning_container,
 528        //     DiagnosticSeverity::ERROR => style.hover_popover.error_container,
 529        //     _ => style.hover_popover.container,
 530        // };
 531
 532        // let tooltip_style = theme::current(cx).tooltip.clone();
 533
 534        // MouseEventHandler::new::<DiagnosticPopover, _>(0, cx, |_, _| {
 535        //     text.with_soft_wrap(true)
 536        //         .contained()
 537        //         .with_style(container_style)
 538        // })
 539        // .with_padding(Padding {
 540        //     top: HOVER_POPOVER_GAP,
 541        //     bottom: HOVER_POPOVER_GAP,
 542        //     ..Default::default()
 543        // })
 544        // .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath.
 545        // .on_click(MouseButton::Left, |_, this, cx| {
 546        //     this.go_to_diagnostic(&Default::default(), cx)
 547        // })
 548        // .with_cursor_style(CursorStyle::PointingHand)
 549        // .with_tooltip::<PrimaryDiagnostic>(
 550        //     0,
 551        //     "Go To Diagnostic".to_string(),
 552        //     Some(Box::new(crate::GoToDiagnostic)),
 553        //     tooltip_style,
 554        //     cx,
 555        // )
 556        // .into_any()
 557    }
 558
 559    pub fn activation_info(&self) -> (usize, Anchor) {
 560        let entry = self
 561            .primary_diagnostic
 562            .as_ref()
 563            .unwrap_or(&self.local_diagnostic);
 564
 565        (entry.diagnostic.group_id, entry.range.start.clone())
 566    }
 567}
 568
 569// #[cfg(test)]
 570// mod tests {
 571//     use super::*;
 572//     use crate::{
 573//         editor_tests::init_test,
 574//         element::PointForPosition,
 575//         inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
 576//         link_go_to_definition::update_inlay_link_and_hover_points,
 577//         test::editor_lsp_test_context::EditorLspTestContext,
 578//         InlayId,
 579//     };
 580//     use collections::BTreeSet;
 581//     use gpui::fonts::{HighlightStyle, Underline, Weight};
 582//     use indoc::indoc;
 583//     use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet};
 584//     use lsp::LanguageServerId;
 585//     use project::{HoverBlock, HoverBlockKind};
 586//     use smol::stream::StreamExt;
 587//     use unindent::Unindent;
 588//     use util::test::marked_text_ranges;
 589
 590//     #[gpui::test]
 591//     async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) {
 592//         init_test(cx, |_| {});
 593
 594//         let mut cx = EditorLspTestContext::new_rust(
 595//             lsp::ServerCapabilities {
 596//                 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
 597//                 ..Default::default()
 598//             },
 599//             cx,
 600//         )
 601//         .await;
 602
 603//         // Basic hover delays and then pops without moving the mouse
 604//         cx.set_state(indoc! {"
 605//             fn ˇtest() { println!(); }
 606//         "});
 607//         let hover_point = cx.display_point(indoc! {"
 608//             fn test() { printˇln!(); }
 609//         "});
 610
 611//         cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx));
 612//         assert!(!cx.editor(|editor, _| editor.hover_state.visible()));
 613
 614//         // After delay, hover should be visible.
 615//         let symbol_range = cx.lsp_range(indoc! {"
 616//             fn test() { «println!»(); }
 617//         "});
 618//         let mut requests =
 619//             cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
 620//                 Ok(Some(lsp::Hover {
 621//                     contents: lsp::HoverContents::Markup(lsp::MarkupContent {
 622//                         kind: lsp::MarkupKind::Markdown,
 623//                         value: "some basic docs".to_string(),
 624//                     }),
 625//                     range: Some(symbol_range),
 626//                 }))
 627//             });
 628//         cx.foreground()
 629//             .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
 630//         requests.next().await;
 631
 632//         cx.editor(|editor, _| {
 633//             assert!(editor.hover_state.visible());
 634//             assert_eq!(
 635//                 editor.hover_state.info_popover.clone().unwrap().blocks,
 636//                 vec![HoverBlock {
 637//                     text: "some basic docs".to_string(),
 638//                     kind: HoverBlockKind::Markdown,
 639//                 },]
 640//             )
 641//         });
 642
 643//         // Mouse moved with no hover response dismisses
 644//         let hover_point = cx.display_point(indoc! {"
 645//             fn teˇst() { println!(); }
 646//         "});
 647//         let mut request = cx
 648//             .lsp
 649//             .handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) });
 650//         cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx));
 651//         cx.foreground()
 652//             .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
 653//         request.next().await;
 654//         cx.editor(|editor, _| {
 655//             assert!(!editor.hover_state.visible());
 656//         });
 657//     }
 658
 659//     #[gpui::test]
 660//     async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) {
 661//         init_test(cx, |_| {});
 662
 663//         let mut cx = EditorLspTestContext::new_rust(
 664//             lsp::ServerCapabilities {
 665//                 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
 666//                 ..Default::default()
 667//             },
 668//             cx,
 669//         )
 670//         .await;
 671
 672//         // Hover with keyboard has no delay
 673//         cx.set_state(indoc! {"
 674//             fˇn test() { println!(); }
 675//         "});
 676//         cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
 677//         let symbol_range = cx.lsp_range(indoc! {"
 678//             «fn» test() { println!(); }
 679//         "});
 680//         cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
 681//             Ok(Some(lsp::Hover {
 682//                 contents: lsp::HoverContents::Markup(lsp::MarkupContent {
 683//                     kind: lsp::MarkupKind::Markdown,
 684//                     value: "some other basic docs".to_string(),
 685//                 }),
 686//                 range: Some(symbol_range),
 687//             }))
 688//         })
 689//         .next()
 690//         .await;
 691
 692//         cx.condition(|editor, _| editor.hover_state.visible()).await;
 693//         cx.editor(|editor, _| {
 694//             assert_eq!(
 695//                 editor.hover_state.info_popover.clone().unwrap().blocks,
 696//                 vec![HoverBlock {
 697//                     text: "some other basic docs".to_string(),
 698//                     kind: HoverBlockKind::Markdown,
 699//                 }]
 700//             )
 701//         });
 702//     }
 703
 704//     #[gpui::test]
 705//     async fn test_empty_hovers_filtered(cx: &mut gpui::TestAppContext) {
 706//         init_test(cx, |_| {});
 707
 708//         let mut cx = EditorLspTestContext::new_rust(
 709//             lsp::ServerCapabilities {
 710//                 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
 711//                 ..Default::default()
 712//             },
 713//             cx,
 714//         )
 715//         .await;
 716
 717//         // Hover with keyboard has no delay
 718//         cx.set_state(indoc! {"
 719//             fˇn test() { println!(); }
 720//         "});
 721//         cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
 722//         let symbol_range = cx.lsp_range(indoc! {"
 723//             «fn» test() { println!(); }
 724//         "});
 725//         cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
 726//             Ok(Some(lsp::Hover {
 727//                 contents: lsp::HoverContents::Array(vec![
 728//                     lsp::MarkedString::String("regular text for hover to show".to_string()),
 729//                     lsp::MarkedString::String("".to_string()),
 730//                     lsp::MarkedString::LanguageString(lsp::LanguageString {
 731//                         language: "Rust".to_string(),
 732//                         value: "".to_string(),
 733//                     }),
 734//                 ]),
 735//                 range: Some(symbol_range),
 736//             }))
 737//         })
 738//         .next()
 739//         .await;
 740
 741//         cx.condition(|editor, _| editor.hover_state.visible()).await;
 742//         cx.editor(|editor, _| {
 743//             assert_eq!(
 744//                 editor.hover_state.info_popover.clone().unwrap().blocks,
 745//                 vec![HoverBlock {
 746//                     text: "regular text for hover to show".to_string(),
 747//                     kind: HoverBlockKind::Markdown,
 748//                 }],
 749//                 "No empty string hovers should be shown"
 750//             );
 751//         });
 752//     }
 753
 754//     #[gpui::test]
 755//     async fn test_line_ends_trimmed(cx: &mut gpui::TestAppContext) {
 756//         init_test(cx, |_| {});
 757
 758//         let mut cx = EditorLspTestContext::new_rust(
 759//             lsp::ServerCapabilities {
 760//                 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
 761//                 ..Default::default()
 762//             },
 763//             cx,
 764//         )
 765//         .await;
 766
 767//         // Hover with keyboard has no delay
 768//         cx.set_state(indoc! {"
 769//             fˇn test() { println!(); }
 770//         "});
 771//         cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
 772//         let symbol_range = cx.lsp_range(indoc! {"
 773//             «fn» test() { println!(); }
 774//         "});
 775
 776//         let code_str = "\nlet hovered_point: Vector2F // size = 8, align = 0x4\n";
 777//         let markdown_string = format!("\n```rust\n{code_str}```");
 778
 779//         let closure_markdown_string = markdown_string.clone();
 780//         cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| {
 781//             let future_markdown_string = closure_markdown_string.clone();
 782//             async move {
 783//                 Ok(Some(lsp::Hover {
 784//                     contents: lsp::HoverContents::Markup(lsp::MarkupContent {
 785//                         kind: lsp::MarkupKind::Markdown,
 786//                         value: future_markdown_string,
 787//                     }),
 788//                     range: Some(symbol_range),
 789//                 }))
 790//             }
 791//         })
 792//         .next()
 793//         .await;
 794
 795//         cx.condition(|editor, _| editor.hover_state.visible()).await;
 796//         cx.editor(|editor, _| {
 797//             let blocks = editor.hover_state.info_popover.clone().unwrap().blocks;
 798//             assert_eq!(
 799//                 blocks,
 800//                 vec![HoverBlock {
 801//                     text: markdown_string,
 802//                     kind: HoverBlockKind::Markdown,
 803//                 }],
 804//             );
 805
 806//             let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));
 807//             assert_eq!(
 808//                 rendered.text,
 809//                 code_str.trim(),
 810//                 "Should not have extra line breaks at end of rendered hover"
 811//             );
 812//         });
 813//     }
 814
 815//     #[gpui::test]
 816//     async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) {
 817//         init_test(cx, |_| {});
 818
 819//         let mut cx = EditorLspTestContext::new_rust(
 820//             lsp::ServerCapabilities {
 821//                 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
 822//                 ..Default::default()
 823//             },
 824//             cx,
 825//         )
 826//         .await;
 827
 828//         // Hover with just diagnostic, pops DiagnosticPopover immediately and then
 829//         // info popover once request completes
 830//         cx.set_state(indoc! {"
 831//             fn teˇst() { println!(); }
 832//         "});
 833
 834//         // Send diagnostic to client
 835//         let range = cx.text_anchor_range(indoc! {"
 836//             fn «test»() { println!(); }
 837//         "});
 838//         cx.update_buffer(|buffer, cx| {
 839//             let snapshot = buffer.text_snapshot();
 840//             let set = DiagnosticSet::from_sorted_entries(
 841//                 vec![DiagnosticEntry {
 842//                     range,
 843//                     diagnostic: Diagnostic {
 844//                         message: "A test diagnostic message.".to_string(),
 845//                         ..Default::default()
 846//                     },
 847//                 }],
 848//                 &snapshot,
 849//             );
 850//             buffer.update_diagnostics(LanguageServerId(0), set, cx);
 851//         });
 852
 853//         // Hover pops diagnostic immediately
 854//         cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
 855//         cx.foreground().run_until_parked();
 856
 857//         cx.editor(|Editor { hover_state, .. }, _| {
 858//             assert!(hover_state.diagnostic_popover.is_some() && hover_state.info_popover.is_none())
 859//         });
 860
 861//         // Info Popover shows after request responded to
 862//         let range = cx.lsp_range(indoc! {"
 863//             fn «test»() { println!(); }
 864//         "});
 865//         cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
 866//             Ok(Some(lsp::Hover {
 867//                 contents: lsp::HoverContents::Markup(lsp::MarkupContent {
 868//                     kind: lsp::MarkupKind::Markdown,
 869//                     value: "some new docs".to_string(),
 870//                 }),
 871//                 range: Some(range),
 872//             }))
 873//         });
 874//         cx.foreground()
 875//             .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
 876
 877//         cx.foreground().run_until_parked();
 878//         cx.editor(|Editor { hover_state, .. }, _| {
 879//             hover_state.diagnostic_popover.is_some() && hover_state.info_task.is_some()
 880//         });
 881//     }
 882
 883//     #[gpui::test]
 884//     fn test_render_blocks(cx: &mut gpui::TestAppContext) {
 885//         init_test(cx, |_| {});
 886
 887//         cx.add_window(|cx| {
 888//             let editor = Editor::single_line(None, cx);
 889//             let style = editor.style(cx);
 890
 891//             struct Row {
 892//                 blocks: Vec<HoverBlock>,
 893//                 expected_marked_text: String,
 894//                 expected_styles: Vec<HighlightStyle>,
 895//             }
 896
 897//             let rows = &[
 898//                 // Strong emphasis
 899//                 Row {
 900//                     blocks: vec![HoverBlock {
 901//                         text: "one **two** three".to_string(),
 902//                         kind: HoverBlockKind::Markdown,
 903//                     }],
 904//                     expected_marked_text: "one «two» three".to_string(),
 905//                     expected_styles: vec![HighlightStyle {
 906//                         weight: Some(Weight::BOLD),
 907//                         ..Default::default()
 908//                     }],
 909//                 },
 910//                 // Links
 911//                 Row {
 912//                     blocks: vec![HoverBlock {
 913//                         text: "one [two](https://the-url) three".to_string(),
 914//                         kind: HoverBlockKind::Markdown,
 915//                     }],
 916//                     expected_marked_text: "one «two» three".to_string(),
 917//                     expected_styles: vec![HighlightStyle {
 918//                         underline: Some(Underline {
 919//                             thickness: 1.0.into(),
 920//                             ..Default::default()
 921//                         }),
 922//                         ..Default::default()
 923//                     }],
 924//                 },
 925//                 // Lists
 926//                 Row {
 927//                     blocks: vec![HoverBlock {
 928//                         text: "
 929//                             lists:
 930//                             * one
 931//                                 - a
 932//                                 - b
 933//                             * two
 934//                                 - [c](https://the-url)
 935//                                 - d"
 936//                         .unindent(),
 937//                         kind: HoverBlockKind::Markdown,
 938//                     }],
 939//                     expected_marked_text: "
 940//                         lists:
 941//                         - one
 942//                           - a
 943//                           - b
 944//                         - two
 945//                           - «c»
 946//                           - d"
 947//                     .unindent(),
 948//                     expected_styles: vec![HighlightStyle {
 949//                         underline: Some(Underline {
 950//                             thickness: 1.0.into(),
 951//                             ..Default::default()
 952//                         }),
 953//                         ..Default::default()
 954//                     }],
 955//                 },
 956//                 // Multi-paragraph list items
 957//                 Row {
 958//                     blocks: vec![HoverBlock {
 959//                         text: "
 960//                             * one two
 961//                               three
 962
 963//                             * four five
 964//                                 * six seven
 965//                                   eight
 966
 967//                                   nine
 968//                                 * ten
 969//                             * six"
 970//                             .unindent(),
 971//                         kind: HoverBlockKind::Markdown,
 972//                     }],
 973//                     expected_marked_text: "
 974//                         - one two three
 975//                         - four five
 976//                           - six seven eight
 977
 978//                             nine
 979//                           - ten
 980//                         - six"
 981//                         .unindent(),
 982//                     expected_styles: vec![HighlightStyle {
 983//                         underline: Some(Underline {
 984//                             thickness: 1.0.into(),
 985//                             ..Default::default()
 986//                         }),
 987//                         ..Default::default()
 988//                     }],
 989//                 },
 990//             ];
 991
 992//             for Row {
 993//                 blocks,
 994//                 expected_marked_text,
 995//                 expected_styles,
 996//             } in &rows[0..]
 997//             {
 998//                 let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));
 999
1000//                 let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);
1001//                 let expected_highlights = ranges
1002//                     .into_iter()
1003//                     .zip(expected_styles.iter().cloned())
1004//                     .collect::<Vec<_>>();
1005//                 assert_eq!(
1006//                     rendered.text, expected_text,
1007//                     "wrong text for input {blocks:?}"
1008//                 );
1009
1010//                 let rendered_highlights: Vec<_> = rendered
1011//                     .highlights
1012//                     .iter()
1013//                     .filter_map(|(range, highlight)| {
1014//                         let highlight = highlight.to_highlight_style(&style.syntax)?;
1015//                         Some((range.clone(), highlight))
1016//                     })
1017//                     .collect();
1018
1019//                 assert_eq!(
1020//                     rendered_highlights, expected_highlights,
1021//                     "wrong highlights for input {blocks:?}"
1022//                 );
1023//             }
1024
1025//             editor
1026//         });
1027//     }
1028
1029//     #[gpui::test]
1030//     async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) {
1031//         init_test(cx, |settings| {
1032//             settings.defaults.inlay_hints = Some(InlayHintSettings {
1033//                 enabled: true,
1034//                 show_type_hints: true,
1035//                 show_parameter_hints: true,
1036//                 show_other_hints: true,
1037//             })
1038//         });
1039
1040//         let mut cx = EditorLspTestContext::new_rust(
1041//             lsp::ServerCapabilities {
1042//                 inlay_hint_provider: Some(lsp::OneOf::Right(
1043//                     lsp::InlayHintServerCapabilities::Options(lsp::InlayHintOptions {
1044//                         resolve_provider: Some(true),
1045//                         ..Default::default()
1046//                     }),
1047//                 )),
1048//                 ..Default::default()
1049//             },
1050//             cx,
1051//         )
1052//         .await;
1053
1054//         cx.set_state(indoc! {"
1055//             struct TestStruct;
1056
1057//             // ==================
1058
1059//             struct TestNewType<T>(T);
1060
1061//             fn main() {
1062//                 let variableˇ = TestNewType(TestStruct);
1063//             }
1064//         "});
1065
1066//         let hint_start_offset = cx.ranges(indoc! {"
1067//             struct TestStruct;
1068
1069//             // ==================
1070
1071//             struct TestNewType<T>(T);
1072
1073//             fn main() {
1074//                 let variableˇ = TestNewType(TestStruct);
1075//             }
1076//         "})[0]
1077//             .start;
1078//         let hint_position = cx.to_lsp(hint_start_offset);
1079//         let new_type_target_range = cx.lsp_range(indoc! {"
1080//             struct TestStruct;
1081
1082//             // ==================
1083
1084//             struct «TestNewType»<T>(T);
1085
1086//             fn main() {
1087//                 let variable = TestNewType(TestStruct);
1088//             }
1089//         "});
1090//         let struct_target_range = cx.lsp_range(indoc! {"
1091//             struct «TestStruct»;
1092
1093//             // ==================
1094
1095//             struct TestNewType<T>(T);
1096
1097//             fn main() {
1098//                 let variable = TestNewType(TestStruct);
1099//             }
1100//         "});
1101
1102//         let uri = cx.buffer_lsp_url.clone();
1103//         let new_type_label = "TestNewType";
1104//         let struct_label = "TestStruct";
1105//         let entire_hint_label = ": TestNewType<TestStruct>";
1106//         let closure_uri = uri.clone();
1107//         cx.lsp
1108//             .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1109//                 let task_uri = closure_uri.clone();
1110//                 async move {
1111//                     assert_eq!(params.text_document.uri, task_uri);
1112//                     Ok(Some(vec![lsp::InlayHint {
1113//                         position: hint_position,
1114//                         label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart {
1115//                             value: entire_hint_label.to_string(),
1116//                             ..Default::default()
1117//                         }]),
1118//                         kind: Some(lsp::InlayHintKind::TYPE),
1119//                         text_edits: None,
1120//                         tooltip: None,
1121//                         padding_left: Some(false),
1122//                         padding_right: Some(false),
1123//                         data: None,
1124//                     }]))
1125//                 }
1126//             })
1127//             .next()
1128//             .await;
1129//         cx.foreground().run_until_parked();
1130//         cx.update_editor(|editor, cx| {
1131//             let expected_layers = vec![entire_hint_label.to_string()];
1132//             assert_eq!(expected_layers, cached_hint_labels(editor));
1133//             assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1134//         });
1135
1136//         let inlay_range = cx
1137//             .ranges(indoc! {"
1138//                 struct TestStruct;
1139
1140//                 // ==================
1141
1142//                 struct TestNewType<T>(T);
1143
1144//                 fn main() {
1145//                     let variable« »= TestNewType(TestStruct);
1146//                 }
1147//         "})
1148//             .get(0)
1149//             .cloned()
1150//             .unwrap();
1151//         let new_type_hint_part_hover_position = cx.update_editor(|editor, cx| {
1152//             let snapshot = editor.snapshot(cx);
1153//             let previous_valid = inlay_range.start.to_display_point(&snapshot);
1154//             let next_valid = inlay_range.end.to_display_point(&snapshot);
1155//             assert_eq!(previous_valid.row(), next_valid.row());
1156//             assert!(previous_valid.column() < next_valid.column());
1157//             let exact_unclipped = DisplayPoint::new(
1158//                 previous_valid.row(),
1159//                 previous_valid.column()
1160//                     + (entire_hint_label.find(new_type_label).unwrap() + new_type_label.len() / 2)
1161//                         as u32,
1162//             );
1163//             PointForPosition {
1164//                 previous_valid,
1165//                 next_valid,
1166//                 exact_unclipped,
1167//                 column_overshoot_after_line_end: 0,
1168//             }
1169//         });
1170//         cx.update_editor(|editor, cx| {
1171//             update_inlay_link_and_hover_points(
1172//                 &editor.snapshot(cx),
1173//                 new_type_hint_part_hover_position,
1174//                 editor,
1175//                 true,
1176//                 false,
1177//                 cx,
1178//             );
1179//         });
1180
1181//         let resolve_closure_uri = uri.clone();
1182//         cx.lsp
1183//             .handle_request::<lsp::request::InlayHintResolveRequest, _, _>(
1184//                 move |mut hint_to_resolve, _| {
1185//                     let mut resolved_hint_positions = BTreeSet::new();
1186//                     let task_uri = resolve_closure_uri.clone();
1187//                     async move {
1188//                         let inserted = resolved_hint_positions.insert(hint_to_resolve.position);
1189//                         assert!(inserted, "Hint {hint_to_resolve:?} was resolved twice");
1190
1191//                         // `: TestNewType<TestStruct>`
1192//                         hint_to_resolve.label = lsp::InlayHintLabel::LabelParts(vec![
1193//                             lsp::InlayHintLabelPart {
1194//                                 value: ": ".to_string(),
1195//                                 ..Default::default()
1196//                             },
1197//                             lsp::InlayHintLabelPart {
1198//                                 value: new_type_label.to_string(),
1199//                                 location: Some(lsp::Location {
1200//                                     uri: task_uri.clone(),
1201//                                     range: new_type_target_range,
1202//                                 }),
1203//                                 tooltip: Some(lsp::InlayHintLabelPartTooltip::String(format!(
1204//                                     "A tooltip for `{new_type_label}`"
1205//                                 ))),
1206//                                 ..Default::default()
1207//                             },
1208//                             lsp::InlayHintLabelPart {
1209//                                 value: "<".to_string(),
1210//                                 ..Default::default()
1211//                             },
1212//                             lsp::InlayHintLabelPart {
1213//                                 value: struct_label.to_string(),
1214//                                 location: Some(lsp::Location {
1215//                                     uri: task_uri,
1216//                                     range: struct_target_range,
1217//                                 }),
1218//                                 tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent(
1219//                                     lsp::MarkupContent {
1220//                                         kind: lsp::MarkupKind::Markdown,
1221//                                         value: format!("A tooltip for `{struct_label}`"),
1222//                                     },
1223//                                 )),
1224//                                 ..Default::default()
1225//                             },
1226//                             lsp::InlayHintLabelPart {
1227//                                 value: ">".to_string(),
1228//                                 ..Default::default()
1229//                             },
1230//                         ]);
1231
1232//                         Ok(hint_to_resolve)
1233//                     }
1234//                 },
1235//             )
1236//             .next()
1237//             .await;
1238//         cx.foreground().run_until_parked();
1239
1240//         cx.update_editor(|editor, cx| {
1241//             update_inlay_link_and_hover_points(
1242//                 &editor.snapshot(cx),
1243//                 new_type_hint_part_hover_position,
1244//                 editor,
1245//                 true,
1246//                 false,
1247//                 cx,
1248//             );
1249//         });
1250//         cx.foreground()
1251//             .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
1252//         cx.foreground().run_until_parked();
1253//         cx.update_editor(|editor, cx| {
1254//             let hover_state = &editor.hover_state;
1255//             assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());
1256//             let popover = hover_state.info_popover.as_ref().unwrap();
1257//             let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
1258//             assert_eq!(
1259//                 popover.symbol_range,
1260//                 RangeInEditor::Inlay(InlayHighlight {
1261//                     inlay: InlayId::Hint(0),
1262//                     inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
1263//                     range: ": ".len()..": ".len() + new_type_label.len(),
1264//                 }),
1265//                 "Popover range should match the new type label part"
1266//             );
1267//             assert_eq!(
1268//                 popover.parsed_content.text,
1269//                 format!("A tooltip for `{new_type_label}`"),
1270//                 "Rendered text should not anyhow alter backticks"
1271//             );
1272//         });
1273
1274//         let struct_hint_part_hover_position = cx.update_editor(|editor, cx| {
1275//             let snapshot = editor.snapshot(cx);
1276//             let previous_valid = inlay_range.start.to_display_point(&snapshot);
1277//             let next_valid = inlay_range.end.to_display_point(&snapshot);
1278//             assert_eq!(previous_valid.row(), next_valid.row());
1279//             assert!(previous_valid.column() < next_valid.column());
1280//             let exact_unclipped = DisplayPoint::new(
1281//                 previous_valid.row(),
1282//                 previous_valid.column()
1283//                     + (entire_hint_label.find(struct_label).unwrap() + struct_label.len() / 2)
1284//                         as u32,
1285//             );
1286//             PointForPosition {
1287//                 previous_valid,
1288//                 next_valid,
1289//                 exact_unclipped,
1290//                 column_overshoot_after_line_end: 0,
1291//             }
1292//         });
1293//         cx.update_editor(|editor, cx| {
1294//             update_inlay_link_and_hover_points(
1295//                 &editor.snapshot(cx),
1296//                 struct_hint_part_hover_position,
1297//                 editor,
1298//                 true,
1299//                 false,
1300//                 cx,
1301//             );
1302//         });
1303//         cx.foreground()
1304//             .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
1305//         cx.foreground().run_until_parked();
1306//         cx.update_editor(|editor, cx| {
1307//             let hover_state = &editor.hover_state;
1308//             assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());
1309//             let popover = hover_state.info_popover.as_ref().unwrap();
1310//             let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
1311//             assert_eq!(
1312//                 popover.symbol_range,
1313//                 RangeInEditor::Inlay(InlayHighlight {
1314//                     inlay: InlayId::Hint(0),
1315//                     inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
1316//                     range: ": ".len() + new_type_label.len() + "<".len()
1317//                         ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(),
1318//                 }),
1319//                 "Popover range should match the struct label part"
1320//             );
1321//             assert_eq!(
1322//                 popover.parsed_content.text,
1323//                 format!("A tooltip for {struct_label}"),
1324//                 "Rendered markdown element should remove backticks from text"
1325//             );
1326//         });
1327//     }
1328// }