console.rs

  1use super::{
  2    stack_frame_list::{StackFrameList, StackFrameListEvent},
  3    variable_list::VariableList,
  4};
  5use alacritty_terminal::vte::ansi;
  6use anyhow::Result;
  7use collections::HashMap;
  8use dap::OutputEvent;
  9use editor::{Bias, CompletionProvider, Editor, EditorElement, EditorStyle, ExcerptId};
 10use fuzzy::StringMatchCandidate;
 11use gpui::{
 12    Action as _, AppContext, Context, Corner, Entity, FocusHandle, Focusable, HighlightStyle, Hsla,
 13    Render, Subscription, Task, TextStyle, WeakEntity, actions,
 14};
 15use language::{Buffer, CodeLabel, ToOffset};
 16use menu::Confirm;
 17use project::{
 18    Completion, CompletionResponse,
 19    debugger::session::{CompletionsQuery, OutputToken, Session, SessionEvent},
 20};
 21use settings::Settings;
 22use std::{cell::RefCell, ops::Range, rc::Rc, usize};
 23use theme::{Theme, ThemeSettings};
 24use ui::{ContextMenu, Divider, PopoverMenu, SplitButton, Tooltip, prelude::*};
 25
 26actions!(console, [WatchExpression]);
 27
 28pub struct Console {
 29    console: Entity<Editor>,
 30    query_bar: Entity<Editor>,
 31    session: Entity<Session>,
 32    _subscriptions: Vec<Subscription>,
 33    variable_list: Entity<VariableList>,
 34    stack_frame_list: Entity<StackFrameList>,
 35    last_token: OutputToken,
 36    update_output_task: Task<()>,
 37    focus_handle: FocusHandle,
 38}
 39
 40impl Console {
 41    pub fn new(
 42        session: Entity<Session>,
 43        stack_frame_list: Entity<StackFrameList>,
 44        variable_list: Entity<VariableList>,
 45        window: &mut Window,
 46        cx: &mut Context<Self>,
 47    ) -> Self {
 48        let console = cx.new(|cx| {
 49            let mut editor = Editor::multi_line(window, cx);
 50            editor.move_to_end(&editor::actions::MoveToEnd, window, cx);
 51            editor.set_read_only(true);
 52            editor.disable_scrollbars_and_minimap(window, cx);
 53            editor.set_show_gutter(false, cx);
 54            editor.set_show_runnables(false, cx);
 55            editor.set_show_breakpoints(false, cx);
 56            editor.set_show_code_actions(false, cx);
 57            editor.set_show_line_numbers(false, cx);
 58            editor.set_show_git_diff_gutter(false, cx);
 59            editor.set_autoindent(false);
 60            editor.set_input_enabled(false);
 61            editor.set_use_autoclose(false);
 62            editor.set_show_wrap_guides(false, cx);
 63            editor.set_show_indent_guides(false, cx);
 64            editor.set_show_edit_predictions(Some(false), window, cx);
 65            editor.set_use_modal_editing(false);
 66            editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
 67            editor
 68        });
 69        let focus_handle = cx.focus_handle();
 70
 71        let this = cx.weak_entity();
 72        let query_bar = cx.new(|cx| {
 73            let mut editor = Editor::single_line(window, cx);
 74            editor.set_placeholder_text("Evaluate an expression", cx);
 75            editor.set_use_autoclose(false);
 76            editor.set_show_gutter(false, cx);
 77            editor.set_show_wrap_guides(false, cx);
 78            editor.set_show_indent_guides(false, cx);
 79            editor.set_completion_provider(Some(Rc::new(ConsoleQueryBarCompletionProvider(this))));
 80
 81            editor
 82        });
 83
 84        let _subscriptions = vec![
 85            cx.subscribe(&stack_frame_list, Self::handle_stack_frame_list_events),
 86            cx.subscribe_in(&session, window, |this, _, event, window, cx| {
 87                if let SessionEvent::ConsoleOutput = event {
 88                    this.update_output(window, cx)
 89                }
 90            }),
 91            cx.on_focus(&focus_handle, window, |console, window, cx| {
 92                if console.is_running(cx) {
 93                    console.query_bar.focus_handle(cx).focus(window);
 94                }
 95            }),
 96        ];
 97
 98        Self {
 99            session,
100            console,
101            query_bar,
102            variable_list,
103            _subscriptions,
104            stack_frame_list,
105            update_output_task: Task::ready(()),
106            last_token: OutputToken(0),
107            focus_handle,
108        }
109    }
110
111    #[cfg(test)]
112    pub(crate) fn editor(&self) -> &Entity<Editor> {
113        &self.console
114    }
115
116    fn is_running(&self, cx: &Context<Self>) -> bool {
117        self.session.read(cx).is_running()
118    }
119
120    fn handle_stack_frame_list_events(
121        &mut self,
122        _: Entity<StackFrameList>,
123        event: &StackFrameListEvent,
124        cx: &mut Context<Self>,
125    ) {
126        match event {
127            StackFrameListEvent::SelectedStackFrameChanged(_) => cx.notify(),
128            StackFrameListEvent::BuiltEntries => {}
129        }
130    }
131
132    pub(crate) fn show_indicator(&self, cx: &App) -> bool {
133        self.session.read(cx).has_new_output(self.last_token)
134    }
135
136    pub fn add_messages<'a>(
137        &mut self,
138        events: impl Iterator<Item = &'a OutputEvent>,
139        window: &mut Window,
140        cx: &mut App,
141    ) {
142        self.console.update(cx, |console, cx| {
143            console.set_read_only(false);
144
145            for event in events {
146                let to_insert = format!("{}\n", event.output.trim_end());
147
148                let mut ansi_handler = ConsoleHandler::default();
149                let mut ansi_processor = ansi::Processor::<ansi::StdSyncHandler>::default();
150
151                let len = console.buffer().read(cx).len(cx);
152                ansi_processor.advance(&mut ansi_handler, to_insert.as_bytes());
153                let output = std::mem::take(&mut ansi_handler.output);
154                let mut spans = std::mem::take(&mut ansi_handler.spans);
155                let mut background_spans = std::mem::take(&mut ansi_handler.background_spans);
156                if ansi_handler.current_range_start < output.len() {
157                    spans.push((
158                        ansi_handler.current_range_start..output.len(),
159                        ansi_handler.current_color,
160                    ));
161                }
162                if ansi_handler.current_background_range_start < output.len() {
163                    background_spans.push((
164                        ansi_handler.current_background_range_start..output.len(),
165                        ansi_handler.current_background_color,
166                    ));
167                }
168                console.move_to_end(&editor::actions::MoveToEnd, window, cx);
169                console.insert(&output, window, cx);
170                let buffer = console.buffer().read(cx).snapshot(cx);
171
172                struct ConsoleAnsiHighlight;
173
174                for (range, color) in spans {
175                    let Some(color) = color else { continue };
176                    let start_offset = len + range.start;
177                    let range = start_offset..len + range.end;
178                    let range = buffer.anchor_after(range.start)..buffer.anchor_before(range.end);
179                    let style = HighlightStyle {
180                        color: Some(terminal_view::terminal_element::convert_color(
181                            &color,
182                            cx.theme(),
183                        )),
184                        ..Default::default()
185                    };
186                    console.highlight_text_key::<ConsoleAnsiHighlight>(
187                        start_offset,
188                        vec![range],
189                        style,
190                        cx,
191                    );
192                }
193
194                for (range, color) in background_spans {
195                    let Some(color) = color else { continue };
196                    let start_offset = len + range.start;
197                    let range = start_offset..len + range.end;
198                    let range = buffer.anchor_after(range.start)..buffer.anchor_before(range.end);
199
200                    let color_fetcher: fn(&Theme) -> Hsla = match color {
201                        // Named and theme defined colors
202                        ansi::Color::Named(n) => match n {
203                            ansi::NamedColor::Black => |theme| theme.colors().terminal_ansi_black,
204                            ansi::NamedColor::Red => |theme| theme.colors().terminal_ansi_red,
205                            ansi::NamedColor::Green => |theme| theme.colors().terminal_ansi_green,
206                            ansi::NamedColor::Yellow => |theme| theme.colors().terminal_ansi_yellow,
207                            ansi::NamedColor::Blue => |theme| theme.colors().terminal_ansi_blue,
208                            ansi::NamedColor::Magenta => {
209                                |theme| theme.colors().terminal_ansi_magenta
210                            }
211                            ansi::NamedColor::Cyan => |theme| theme.colors().terminal_ansi_cyan,
212                            ansi::NamedColor::White => |theme| theme.colors().terminal_ansi_white,
213                            ansi::NamedColor::BrightBlack => {
214                                |theme| theme.colors().terminal_ansi_bright_black
215                            }
216                            ansi::NamedColor::BrightRed => {
217                                |theme| theme.colors().terminal_ansi_bright_red
218                            }
219                            ansi::NamedColor::BrightGreen => {
220                                |theme| theme.colors().terminal_ansi_bright_green
221                            }
222                            ansi::NamedColor::BrightYellow => {
223                                |theme| theme.colors().terminal_ansi_bright_yellow
224                            }
225                            ansi::NamedColor::BrightBlue => {
226                                |theme| theme.colors().terminal_ansi_bright_blue
227                            }
228                            ansi::NamedColor::BrightMagenta => {
229                                |theme| theme.colors().terminal_ansi_bright_magenta
230                            }
231                            ansi::NamedColor::BrightCyan => {
232                                |theme| theme.colors().terminal_ansi_bright_cyan
233                            }
234                            ansi::NamedColor::BrightWhite => {
235                                |theme| theme.colors().terminal_ansi_bright_white
236                            }
237                            ansi::NamedColor::Foreground => {
238                                |theme| theme.colors().terminal_foreground
239                            }
240                            ansi::NamedColor::Background => {
241                                |theme| theme.colors().terminal_background
242                            }
243                            ansi::NamedColor::Cursor => |theme| theme.players().local().cursor,
244                            ansi::NamedColor::DimBlack => {
245                                |theme| theme.colors().terminal_ansi_dim_black
246                            }
247                            ansi::NamedColor::DimRed => {
248                                |theme| theme.colors().terminal_ansi_dim_red
249                            }
250                            ansi::NamedColor::DimGreen => {
251                                |theme| theme.colors().terminal_ansi_dim_green
252                            }
253                            ansi::NamedColor::DimYellow => {
254                                |theme| theme.colors().terminal_ansi_dim_yellow
255                            }
256                            ansi::NamedColor::DimBlue => {
257                                |theme| theme.colors().terminal_ansi_dim_blue
258                            }
259                            ansi::NamedColor::DimMagenta => {
260                                |theme| theme.colors().terminal_ansi_dim_magenta
261                            }
262                            ansi::NamedColor::DimCyan => {
263                                |theme| theme.colors().terminal_ansi_dim_cyan
264                            }
265                            ansi::NamedColor::DimWhite => {
266                                |theme| theme.colors().terminal_ansi_dim_white
267                            }
268                            ansi::NamedColor::BrightForeground => {
269                                |theme| theme.colors().terminal_bright_foreground
270                            }
271                            ansi::NamedColor::DimForeground => {
272                                |theme| theme.colors().terminal_dim_foreground
273                            }
274                        },
275                        // 'True' colors
276                        ansi::Color::Spec(_) => |theme| theme.colors().editor_background,
277                        // 8 bit, indexed colors
278                        ansi::Color::Indexed(i) => {
279                            match i {
280                                // 0-15 are the same as the named colors above
281                                0 => |theme| theme.colors().terminal_ansi_black,
282                                1 => |theme| theme.colors().terminal_ansi_red,
283                                2 => |theme| theme.colors().terminal_ansi_green,
284                                3 => |theme| theme.colors().terminal_ansi_yellow,
285                                4 => |theme| theme.colors().terminal_ansi_blue,
286                                5 => |theme| theme.colors().terminal_ansi_magenta,
287                                6 => |theme| theme.colors().terminal_ansi_cyan,
288                                7 => |theme| theme.colors().terminal_ansi_white,
289                                8 => |theme| theme.colors().terminal_ansi_bright_black,
290                                9 => |theme| theme.colors().terminal_ansi_bright_red,
291                                10 => |theme| theme.colors().terminal_ansi_bright_green,
292                                11 => |theme| theme.colors().terminal_ansi_bright_yellow,
293                                12 => |theme| theme.colors().terminal_ansi_bright_blue,
294                                13 => |theme| theme.colors().terminal_ansi_bright_magenta,
295                                14 => |theme| theme.colors().terminal_ansi_bright_cyan,
296                                15 => |theme| theme.colors().terminal_ansi_bright_white,
297                                // 16-231 are a 6x6x6 RGB color cube, mapped to 0-255 using steps defined by XTerm.
298                                // See: https://github.com/xterm-x11/xterm-snapshots/blob/master/256colres.pl
299                                // 16..=231 => {
300                                //     let (r, g, b) = rgb_for_index(index as u8);
301                                //     rgba_color(
302                                //         if r == 0 { 0 } else { r * 40 + 55 },
303                                //         if g == 0 { 0 } else { g * 40 + 55 },
304                                //         if b == 0 { 0 } else { b * 40 + 55 },
305                                //     )
306                                // }
307                                // 232-255 are a 24-step grayscale ramp from (8, 8, 8) to (238, 238, 238).
308                                // 232..=255 => {
309                                //     let i = index as u8 - 232; // Align index to 0..24
310                                //     let value = i * 10 + 8;
311                                //     rgba_color(value, value, value)
312                                // }
313                                // For compatibility with the alacritty::Colors interface
314                                // See: https://github.com/alacritty/alacritty/blob/master/alacritty_terminal/src/term/color.rs
315                                _ => |_| gpui::black(),
316                            }
317                        }
318                    };
319
320                    console.highlight_background_key::<ConsoleAnsiHighlight>(
321                        start_offset,
322                        &[range],
323                        color_fetcher,
324                        cx,
325                    );
326                }
327            }
328
329            console.set_read_only(true);
330            cx.notify();
331        });
332    }
333
334    pub fn watch_expression(
335        &mut self,
336        _: &WatchExpression,
337        window: &mut Window,
338        cx: &mut Context<Self>,
339    ) {
340        let expression = self.query_bar.update(cx, |editor, cx| {
341            let expression = editor.text(cx);
342            cx.defer_in(window, |editor, window, cx| {
343                editor.clear(window, cx);
344            });
345
346            expression
347        });
348
349        self.session.update(cx, |session, cx| {
350            session
351                .evaluate(
352                    expression.clone(),
353                    Some(dap::EvaluateArgumentsContext::Repl),
354                    self.stack_frame_list.read(cx).opened_stack_frame_id(),
355                    None,
356                    cx,
357                )
358                .detach();
359
360            if let Some(stack_frame_id) = self.stack_frame_list.read(cx).opened_stack_frame_id() {
361                session
362                    .add_watcher(expression.into(), stack_frame_id, cx)
363                    .detach();
364            }
365        });
366    }
367
368    pub fn evaluate(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {
369        let expression = self.query_bar.update(cx, |editor, cx| {
370            let expression = editor.text(cx);
371            cx.defer_in(window, |editor, window, cx| {
372                editor.clear(window, cx);
373            });
374
375            expression
376        });
377
378        self.session.update(cx, |session, cx| {
379            session
380                .evaluate(
381                    expression,
382                    Some(dap::EvaluateArgumentsContext::Repl),
383                    self.stack_frame_list.read(cx).opened_stack_frame_id(),
384                    None,
385                    cx,
386                )
387                .detach();
388        });
389    }
390
391    fn render_submit_menu(
392        &self,
393        id: impl Into<ElementId>,
394        keybinding_target: Option<FocusHandle>,
395        cx: &App,
396    ) -> impl IntoElement {
397        PopoverMenu::new(id.into())
398            .trigger(
399                ui::ButtonLike::new_rounded_right("console-confirm-split-button-right")
400                    .layer(ui::ElevationIndex::ModalSurface)
401                    .size(ui::ButtonSize::None)
402                    .child(
403                        div()
404                            .px_1()
405                            .child(Icon::new(IconName::ChevronDownSmall).size(IconSize::XSmall)),
406                    ),
407            )
408            .when(
409                self.stack_frame_list
410                    .read(cx)
411                    .opened_stack_frame_id()
412                    .is_some(),
413                |this| {
414                    this.menu(move |window, cx| {
415                        Some(ContextMenu::build(window, cx, |context_menu, _, _| {
416                            context_menu
417                                .when_some(keybinding_target.clone(), |el, keybinding_target| {
418                                    el.context(keybinding_target.clone())
419                                })
420                                .action("Watch expression", WatchExpression.boxed_clone())
421                        }))
422                    })
423                },
424            )
425            .anchor(Corner::TopRight)
426    }
427
428    fn render_console(&self, cx: &Context<Self>) -> impl IntoElement {
429        EditorElement::new(&self.console, Self::editor_style(&self.console, cx))
430    }
431
432    fn editor_style(editor: &Entity<Editor>, cx: &Context<Self>) -> EditorStyle {
433        let is_read_only = editor.read(cx).read_only(cx);
434        let settings = ThemeSettings::get_global(cx);
435        let theme = cx.theme();
436        let text_style = TextStyle {
437            color: if is_read_only {
438                theme.colors().text_muted
439            } else {
440                theme.colors().text
441            },
442            font_family: settings.buffer_font.family.clone(),
443            font_features: settings.buffer_font.features.clone(),
444            font_size: settings.buffer_font_size(cx).into(),
445            font_weight: settings.buffer_font.weight,
446            line_height: relative(settings.buffer_line_height.value()),
447            ..Default::default()
448        };
449        EditorStyle {
450            background: theme.colors().editor_background,
451            local_player: theme.players().local(),
452            text: text_style,
453            ..Default::default()
454        }
455    }
456
457    fn render_query_bar(&self, cx: &Context<Self>) -> impl IntoElement {
458        EditorElement::new(&self.query_bar, Self::editor_style(&self.query_bar, cx))
459    }
460
461    fn update_output(&mut self, window: &mut Window, cx: &mut Context<Self>) {
462        let session = self.session.clone();
463        let token = self.last_token;
464
465        self.update_output_task = cx.spawn_in(window, async move |this, cx| {
466            _ = session.update_in(cx, move |session, window, cx| {
467                let (output, last_processed_token) = session.output(token);
468
469                _ = this.update(cx, |this, cx| {
470                    if last_processed_token == this.last_token {
471                        return;
472                    }
473                    this.add_messages(output, window, cx);
474
475                    this.last_token = last_processed_token;
476                });
477            });
478        });
479    }
480}
481
482impl Render for Console {
483    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
484        let query_focus_handle = self.query_bar.focus_handle(cx);
485
486        v_flex()
487            .track_focus(&self.focus_handle)
488            .key_context("DebugConsole")
489            .on_action(cx.listener(Self::evaluate))
490            .on_action(cx.listener(Self::watch_expression))
491            .size_full()
492            .child(self.render_console(cx))
493            .when(self.is_running(cx), |this| {
494                this.child(Divider::horizontal()).child(
495                    h_flex()
496                        .gap_1()
497                        .bg(cx.theme().colors().editor_background)
498                        .child(self.render_query_bar(cx))
499                        .child(SplitButton::new(
500                            ui::ButtonLike::new_rounded_all(ElementId::Name(
501                                "split-button-left-confirm-button".into(),
502                            ))
503                            .on_click(move |_, window, cx| {
504                                window.dispatch_action(Box::new(Confirm), cx)
505                            })
506                            .tooltip({
507                                let query_focus_handle = query_focus_handle.clone();
508
509                                move |window, cx| {
510                                    Tooltip::for_action_in(
511                                        "Evaluate",
512                                        &Confirm,
513                                        &query_focus_handle,
514                                        window,
515                                        cx,
516                                    )
517                                }
518                            })
519                            .layer(ui::ElevationIndex::ModalSurface)
520                            .size(ui::ButtonSize::Compact)
521                            .child(Label::new("Evaluate")),
522                            self.render_submit_menu(
523                                ElementId::Name("split-button-right-confirm-button".into()),
524                                Some(query_focus_handle.clone()),
525                                cx,
526                            )
527                            .into_any_element(),
528                        )),
529                )
530            })
531            .border_2()
532    }
533}
534
535impl Focusable for Console {
536    fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
537        self.focus_handle.clone()
538    }
539}
540
541struct ConsoleQueryBarCompletionProvider(WeakEntity<Console>);
542
543impl CompletionProvider for ConsoleQueryBarCompletionProvider {
544    fn completions(
545        &self,
546        _excerpt_id: ExcerptId,
547        buffer: &Entity<Buffer>,
548        buffer_position: language::Anchor,
549        _trigger: editor::CompletionContext,
550        _window: &mut Window,
551        cx: &mut Context<Editor>,
552    ) -> Task<Result<Vec<CompletionResponse>>> {
553        let Some(console) = self.0.upgrade() else {
554            return Task::ready(Ok(Vec::new()));
555        };
556
557        let support_completions = console
558            .read(cx)
559            .session
560            .read(cx)
561            .capabilities()
562            .supports_completions_request
563            .unwrap_or_default();
564
565        if support_completions {
566            self.client_completions(&console, buffer, buffer_position, cx)
567        } else {
568            self.variable_list_completions(&console, buffer, buffer_position, cx)
569        }
570    }
571
572    fn apply_additional_edits_for_completion(
573        &self,
574        _buffer: Entity<Buffer>,
575        _completions: Rc<RefCell<Box<[Completion]>>>,
576        _completion_index: usize,
577        _push_to_history: bool,
578        _cx: &mut Context<Editor>,
579    ) -> gpui::Task<anyhow::Result<Option<language::Transaction>>> {
580        Task::ready(Ok(None))
581    }
582
583    fn is_completion_trigger(
584        &self,
585        buffer: &Entity<Buffer>,
586        position: language::Anchor,
587        text: &str,
588        _trigger_in_words: bool,
589        menu_is_open: bool,
590        cx: &mut Context<Editor>,
591    ) -> bool {
592        let snapshot = buffer.read(cx).snapshot();
593        if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
594            return false;
595        }
596
597        self.0
598            .read_with(cx, |console, cx| {
599                console
600                    .session
601                    .read(cx)
602                    .capabilities()
603                    .completion_trigger_characters
604                    .as_ref()
605                    .map(|triggers| triggers.contains(&text.to_string()))
606            })
607            .ok()
608            .flatten()
609            .unwrap_or(true)
610    }
611}
612
613impl ConsoleQueryBarCompletionProvider {
614    fn variable_list_completions(
615        &self,
616        console: &Entity<Console>,
617        buffer: &Entity<Buffer>,
618        buffer_position: language::Anchor,
619        cx: &mut Context<Editor>,
620    ) -> Task<Result<Vec<CompletionResponse>>> {
621        let (variables, string_matches) = console.update(cx, |console, cx| {
622            let mut variables = HashMap::default();
623            let mut string_matches = Vec::default();
624
625            for variable in console.variable_list.update(cx, |variable_list, cx| {
626                variable_list.completion_variables(cx)
627            }) {
628                if let Some(evaluate_name) = &variable.evaluate_name {
629                    variables.insert(evaluate_name.clone(), variable.value.clone());
630                    string_matches.push(StringMatchCandidate {
631                        id: 0,
632                        string: evaluate_name.clone(),
633                        char_bag: evaluate_name.chars().collect(),
634                    });
635                }
636
637                variables.insert(variable.name.clone(), variable.value.clone());
638
639                string_matches.push(StringMatchCandidate {
640                    id: 0,
641                    string: variable.name.clone(),
642                    char_bag: variable.name.chars().collect(),
643                });
644            }
645
646            (variables, string_matches)
647        });
648
649        let snapshot = buffer.read(cx).text_snapshot();
650        let query = snapshot.text();
651        let replace_range = {
652            let buffer_offset = buffer_position.to_offset(&snapshot);
653            let reversed_chars = snapshot.reversed_chars_for_range(0..buffer_offset);
654            let mut word_len = 0;
655            for ch in reversed_chars {
656                if ch.is_alphanumeric() || ch == '_' {
657                    word_len += 1;
658                } else {
659                    break;
660                }
661            }
662            let word_start_offset = buffer_offset - word_len;
663            let start_anchor = snapshot.anchor_at(word_start_offset, Bias::Left);
664            start_anchor..buffer_position
665        };
666        cx.spawn(async move |_, cx| {
667            const LIMIT: usize = 10;
668            let matches = fuzzy::match_strings(
669                &string_matches,
670                &query,
671                true,
672                true,
673                LIMIT,
674                &Default::default(),
675                cx.background_executor().clone(),
676            )
677            .await;
678
679            let completions = matches
680                .iter()
681                .filter_map(|string_match| {
682                    let variable_value = variables.get(&string_match.string)?;
683
684                    Some(project::Completion {
685                        replace_range: replace_range.clone(),
686                        new_text: string_match.string.clone(),
687                        label: CodeLabel {
688                            filter_range: 0..string_match.string.len(),
689                            text: format!("{} {}", string_match.string, variable_value),
690                            runs: Vec::new(),
691                        },
692                        icon_path: None,
693                        documentation: None,
694                        confirm: None,
695                        source: project::CompletionSource::Custom,
696                        insert_text_mode: None,
697                    })
698                })
699                .collect::<Vec<_>>();
700
701            Ok(vec![project::CompletionResponse {
702                is_incomplete: completions.len() >= LIMIT,
703                completions,
704            }])
705        })
706    }
707
708    fn client_completions(
709        &self,
710        console: &Entity<Console>,
711        buffer: &Entity<Buffer>,
712        buffer_position: language::Anchor,
713        cx: &mut Context<Editor>,
714    ) -> Task<Result<Vec<CompletionResponse>>> {
715        let completion_task = console.update(cx, |console, cx| {
716            console.session.update(cx, |state, cx| {
717                let frame_id = console.stack_frame_list.read(cx).opened_stack_frame_id();
718
719                state.completions(
720                    CompletionsQuery::new(buffer.read(cx), buffer_position, frame_id),
721                    cx,
722                )
723            })
724        });
725        let snapshot = buffer.read(cx).text_snapshot();
726        cx.background_executor().spawn(async move {
727            let completions = completion_task.await?;
728
729            let completions = completions
730                .into_iter()
731                .map(|completion| {
732                    let new_text = completion
733                        .text
734                        .as_ref()
735                        .unwrap_or(&completion.label)
736                        .to_owned();
737                    let buffer_text = snapshot.text();
738                    let buffer_bytes = buffer_text.as_bytes();
739                    let new_bytes = new_text.as_bytes();
740
741                    let mut prefix_len = 0;
742                    for i in (0..new_bytes.len()).rev() {
743                        if buffer_bytes.ends_with(&new_bytes[0..i]) {
744                            prefix_len = i;
745                            break;
746                        }
747                    }
748
749                    let buffer_offset = buffer_position.to_offset(&snapshot);
750                    let start = buffer_offset - prefix_len;
751                    let start = snapshot.clip_offset(start, Bias::Left);
752                    let start = snapshot.anchor_before(start);
753                    let replace_range = start..buffer_position;
754
755                    project::Completion {
756                        replace_range,
757                        new_text,
758                        label: CodeLabel {
759                            filter_range: 0..completion.label.len(),
760                            text: completion.label,
761                            runs: Vec::new(),
762                        },
763                        icon_path: None,
764                        documentation: None,
765                        confirm: None,
766                        source: project::CompletionSource::BufferWord {
767                            word_range: buffer_position..language::Anchor::MAX,
768                            resolved: false,
769                        },
770                        insert_text_mode: None,
771                    }
772                })
773                .collect();
774
775            Ok(vec![project::CompletionResponse {
776                completions,
777                is_incomplete: false,
778            }])
779        })
780    }
781}
782
783#[derive(Default)]
784struct ConsoleHandler {
785    output: String,
786    spans: Vec<(Range<usize>, Option<ansi::Color>)>,
787    background_spans: Vec<(Range<usize>, Option<ansi::Color>)>,
788    current_range_start: usize,
789    current_background_range_start: usize,
790    current_color: Option<ansi::Color>,
791    current_background_color: Option<ansi::Color>,
792    pos: usize,
793}
794
795impl ConsoleHandler {
796    fn break_span(&mut self, color: Option<ansi::Color>) {
797        self.spans.push((
798            self.current_range_start..self.output.len(),
799            self.current_color,
800        ));
801        self.current_color = color;
802        self.current_range_start = self.pos;
803    }
804
805    fn break_background_span(&mut self, color: Option<ansi::Color>) {
806        self.background_spans.push((
807            self.current_background_range_start..self.output.len(),
808            self.current_background_color,
809        ));
810        self.current_background_color = color;
811        self.current_background_range_start = self.pos;
812    }
813}
814
815impl ansi::Handler for ConsoleHandler {
816    fn input(&mut self, c: char) {
817        self.output.push(c);
818        self.pos += c.len_utf8();
819    }
820
821    fn linefeed(&mut self) {
822        self.output.push('\n');
823        self.pos += 1;
824    }
825
826    fn put_tab(&mut self, count: u16) {
827        self.output
828            .extend(std::iter::repeat('\t').take(count as usize));
829        self.pos += count as usize;
830    }
831
832    fn terminal_attribute(&mut self, attr: ansi::Attr) {
833        match attr {
834            ansi::Attr::Foreground(color) => {
835                self.break_span(Some(color));
836            }
837            ansi::Attr::Background(color) => {
838                self.break_background_span(Some(color));
839            }
840            ansi::Attr::Reset => {
841                self.break_span(None);
842                self.break_background_span(None);
843            }
844            _ => {}
845        }
846    }
847}