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