Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debug console #43

Merged
merged 25 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
dd26a58
Use buffer settings for font, size etc.
RemcoSmitsDev Sep 28, 2024
ae1ce5f
Trim end of message
RemcoSmitsDev Sep 28, 2024
2965360
By default send the output values to the output editor
RemcoSmitsDev Sep 28, 2024
5e98b62
WIP send evaluate request
RemcoSmitsDev Sep 28, 2024
e69d990
Rename variable
RemcoSmitsDev Sep 28, 2024
de4e00a
Add result to console from evaluate response
RemcoSmitsDev Sep 28, 2024
d54d91e
Remove not needed arc
RemcoSmitsDev Sep 28, 2024
7bd79a2
Remove store capabilities on variable_list
RemcoSmitsDev Sep 28, 2024
577c4ee
Specify the capacity for the task vec
RemcoSmitsDev Sep 28, 2024
be948be
Add placeholder
RemcoSmitsDev Sep 28, 2024
5a5dd2e
WIP add completion provider for existing variables
RemcoSmitsDev Sep 29, 2024
a9d5d63
Add value to auto completion label
RemcoSmitsDev Sep 29, 2024
5e3b8ae
Make todo for debugger
RemcoSmitsDev Sep 29, 2024
f2cd22b
Specify the capacity of the vec's
RemcoSmitsDev Sep 29, 2024
032b9b1
Make clippy happy
RemcoSmitsDev Sep 29, 2024
b1b1deb
Remove not needed notifies and add missing one
RemcoSmitsDev Sep 29, 2024
7a8db9d
WIP move scopes and variables to variable_list
RemcoSmitsDev Oct 2, 2024
4a7a439
Rename configuration done method
RemcoSmitsDev Oct 2, 2024
71266e2
Add support for adapter completions and manual variable completions
RemcoSmitsDev Oct 2, 2024
6efb522
Move type to variabel_list
RemcoSmitsDev Oct 2, 2024
1d9b087
Change update to read
RemcoSmitsDev Oct 2, 2024
7e4269f
Show debug panel when debug session stops
RemcoSmitsDev Oct 2, 2024
38cdde8
Also use scope reference to determine to which where the set value ed…
RemcoSmitsDev Oct 3, 2024
5f1b873
Refetch existing variables after
RemcoSmitsDev Oct 3, 2024
fae40d6
Rebuild entries after refetching the variables
RemcoSmitsDev Oct 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading