Skip to content

Commit

Permalink
Merge branch 'debugger' into breakpoint-prompt-editor
Browse files Browse the repository at this point in the history
  • Loading branch information
Anthony-Eid committed Oct 4, 2024
2 parents d6d7ceb + 842bf02 commit b857dc0
Show file tree
Hide file tree
Showing 7 changed files with 584 additions and 220 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/debugger_ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ anyhow.workspace = true
dap.workspace = true
editor.workspace = true
futures.workspace = true
fuzzy.workspace = true
gpui.workspace = true
language.workspace = true
log.workspace = true
menu.workspace = true
parking_lot.workspace = true
project.workspace = true
serde.workspace = true
settings.workspace = true
Expand Down
291 changes: 278 additions & 13 deletions crates/debugger_ui/src/console.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,121 @@
use editor::{Editor, EditorElement, EditorStyle};
use gpui::{Render, TextStyle, View, ViewContext};
use crate::variable_list::VariableList;
use dap::client::DebugAdapterClientId;
use editor::{CompletionProvider, Editor, EditorElement, EditorStyle};
use fuzzy::StringMatchCandidate;
use gpui::{Model, Render, Task, TextStyle, View, ViewContext, WeakView};
use language::{Buffer, CodeLabel, LanguageServerId, ToOffsetUtf16};
use menu::Confirm;
use parking_lot::RwLock;
use project::{dap_store::DapStore, Completion};
use settings::Settings;
use std::{collections::HashMap, sync::Arc};
use theme::ThemeSettings;
use ui::prelude::*;

pub struct Console {
console: View<Editor>,
query_bar: View<Editor>,
dap_store: Model<DapStore>,
current_stack_frame_id: u64,
client_id: DebugAdapterClientId,
variable_list: View<VariableList>,
}

impl Console {
pub fn new(cx: &mut ViewContext<Self>) -> Self {
pub fn new(
client_id: &DebugAdapterClientId,
current_stack_frame_id: u64,
variable_list: View<VariableList>,
dap_store: Model<DapStore>,
cx: &mut ViewContext<Self>,
) -> Self {
let console = cx.new_view(|cx| {
let mut editor = Editor::multi_line(cx);
editor.move_to_end(&editor::actions::MoveToEnd, cx);
editor.set_read_only(true);
editor.set_show_gutter(false, cx);
editor.set_use_autoclose(false);
editor.set_show_wrap_guides(false, cx);
editor.set_show_indent_guides(false, cx);
editor.set_show_inline_completions(Some(false), cx);
editor
});

let query_bar = cx.new_view(Editor::single_line);
let this = cx.view().downgrade();
let query_bar = cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
editor.set_placeholder_text("Evaluate an expression", cx);
editor.set_use_autoclose(false);
editor.set_show_gutter(false, cx);
editor.set_show_wrap_guides(false, cx);
editor.set_show_indent_guides(false, cx);
editor.set_completion_provider(Box::new(ConsoleQueryBarCompletionProvider(this)));

editor
});

Self {
console,
dap_store,
query_bar,
variable_list,
client_id: *client_id,
current_stack_frame_id,
}
}

pub fn update_current_stack_frame_id(
&mut self,
stack_frame_id: u64,
cx: &mut ViewContext<Self>,
) {
self.current_stack_frame_id = stack_frame_id;

Self { console, query_bar }
cx.notify();
}

pub fn add_message(&mut self, message: &str, cx: &mut ViewContext<Self>) {
self.console.update(cx, |console, cx| {
console.set_read_only(false);
console.move_to_end(&editor::actions::MoveToEnd, cx);
console.insert(format!("{}\n", message).as_str(), cx);
console.insert(format!("{}\n", message.trim_end()).as_str(), cx);
console.set_read_only(true);
});
}

cx.notify();
fn evaluate(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
let expession = self.query_bar.update(cx, |editor, cx| {
let expession = editor.text(cx);

editor.clear(cx);

expession
});

let evaluate_task = self.dap_store.update(cx, |store, cx| {
store.evaluate(
&self.client_id,
self.current_stack_frame_id,
expession,
dap::EvaluateArgumentsContext::Variables,
cx,
)
});

cx.spawn(|this, mut cx| async move {
let response = evaluate_task.await?;

this.update(&mut cx, |console, cx| {
console.add_message(&response.result, cx);

console.variable_list.update(cx, |variable_list, cx| {
variable_list
.refetch_existing_variables(cx)
.detach_and_log_err(cx);
})
})
})
.detach_and_log_err(cx);
}

fn render_console(&self, cx: &ViewContext<Self>) -> impl IntoElement {
Expand All @@ -46,9 +128,9 @@ impl Console {
},
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_size: rems(0.875).into(),
font_size: settings.buffer_font_size.into(),
font_weight: settings.buffer_font.weight,
line_height: relative(1.3),
line_height: relative(settings.buffer_line_height.value()),
..Default::default()
};

Expand All @@ -71,10 +153,11 @@ impl Console {
} else {
cx.theme().colors().text
},
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_size: rems(0.875).into(),
font_weight: settings.buffer_font.weight,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: TextSize::Editor.rems(cx).into(),
font_weight: settings.ui_font.weight,
line_height: relative(1.3),
..Default::default()
};
Expand All @@ -94,6 +177,8 @@ impl Console {
impl Render for Console {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.key_context("DebugConsole")
.on_action(cx.listener(Self::evaluate))
.size_full()
.child(self.render_console(cx))
.child(
Expand All @@ -104,3 +189,183 @@ impl Render for Console {
.border_2()
}
}

struct ConsoleQueryBarCompletionProvider(WeakView<Console>);

impl CompletionProvider for ConsoleQueryBarCompletionProvider {
fn completions(
&self,
buffer: &Model<Buffer>,
buffer_position: language::Anchor,
_trigger: editor::CompletionContext,
cx: &mut ViewContext<Editor>,
) -> gpui::Task<gpui::Result<Vec<project::Completion>>> {
let Some(console) = self.0.upgrade() else {
return Task::ready(Ok(Vec::new()));
};

let support_completions = console.update(cx, |this, cx| {
this.dap_store
.read(cx)
.capabilities_by_id(&this.client_id)
.supports_completions_request
.unwrap_or_default()
});

if support_completions {
self.client_completions(&console, buffer, buffer_position, cx)
} else {
self.variable_list_completions(&console, buffer, buffer_position, cx)
}
}

fn resolve_completions(
&self,
_buffer: Model<Buffer>,
_completion_indices: Vec<usize>,
_completions: Arc<RwLock<Box<[Completion]>>>,
_cx: &mut ViewContext<Editor>,
) -> gpui::Task<gpui::Result<bool>> {
Task::ready(Ok(false))
}

fn apply_additional_edits_for_completion(
&self,
_buffer: Model<Buffer>,
_completion: project::Completion,
_push_to_history: bool,
_cx: &mut ViewContext<Editor>,
) -> gpui::Task<gpui::Result<Option<language::Transaction>>> {
Task::ready(Ok(None))
}

fn is_completion_trigger(
&self,
_buffer: &Model<Buffer>,
_position: language::Anchor,
_text: &str,
_trigger_in_words: bool,
_cx: &mut ViewContext<Editor>,
) -> bool {
true
}
}

impl ConsoleQueryBarCompletionProvider {
fn variable_list_completions(
&self,
console: &View<Console>,
buffer: &Model<Buffer>,
buffer_position: language::Anchor,
cx: &mut ViewContext<Editor>,
) -> gpui::Task<gpui::Result<Vec<project::Completion>>> {
let (variables, string_matches) = console.update(cx, |console, cx| {
let mut variables = HashMap::new();
let mut string_matches = Vec::new();

for variable in console.variable_list.read(cx).variables() {
if let Some(evaluate_name) = &variable.variable.evaluate_name {
variables.insert(evaluate_name.clone(), variable.variable.value.clone());
string_matches.push(StringMatchCandidate {
id: 0,
string: evaluate_name.clone(),
char_bag: evaluate_name.chars().collect(),
});
}

variables.insert(
variable.variable.name.clone(),
variable.variable.value.clone(),
);

string_matches.push(StringMatchCandidate {
id: 0,
string: variable.variable.name.clone(),
char_bag: variable.variable.name.chars().collect(),
});
}

(variables, string_matches)
});

let query = buffer.read(cx).text();
let start_position = buffer.read(cx).anchor_before(0);

cx.spawn(|_, cx| async move {
let matches = fuzzy::match_strings(
&string_matches,
&query,
true,
10,
&Default::default(),
cx.background_executor().clone(),
)
.await;

Ok(matches
.iter()
.filter_map(|string_match| {
let variable_value = variables.get(&string_match.string)?;

Some(project::Completion {
old_range: start_position..buffer_position,
new_text: string_match.string.clone(),
label: CodeLabel {
filter_range: 0..string_match.string.len(),
text: format!("{} {}", string_match.string.clone(), variable_value),
runs: Vec::new(),
},
server_id: LanguageServerId(0), // TODO debugger: read from client
documentation: None,
lsp_completion: Default::default(),
confirm: None,
})
})
.collect())
})
}

fn client_completions(
&self,
console: &View<Console>,
buffer: &Model<Buffer>,
buffer_position: language::Anchor,
cx: &mut ViewContext<Editor>,
) -> gpui::Task<gpui::Result<Vec<project::Completion>>> {
let text = buffer.read(cx).text();
let start_position = buffer.read(cx).anchor_before(0);
let snapshot = buffer.read(cx).snapshot();

let completion_task = console.update(cx, |console, cx| {
console.dap_store.update(cx, |store, cx| {
store.completions(
&console.client_id,
console.current_stack_frame_id,
text,
buffer_position.to_offset_utf16(&snapshot).0 as u64,
cx,
)
})
});

cx.background_executor().spawn(async move {
Ok(completion_task
.await?
.iter()
.map(|completion| project::Completion {
old_range: start_position..buffer_position,
new_text: completion.text.clone().unwrap_or(completion.label.clone()),
label: CodeLabel {
filter_range: 0..completion.label.len(),
text: completion.label.clone(),
runs: Vec::new(),
},
server_id: LanguageServerId(0), // TODO debugger: read from client
documentation: None,
lsp_completion: Default::default(),
confirm: None,
})
.collect())
})
}
}
Loading

0 comments on commit b857dc0

Please sign in to comment.