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