on master help sign in
Split off dap event handlers into helix-view to allow reuse
9a6ee88Blaž Hrastnik
Cargo.lock+1
@@ -497,6 +497,7 @@ dependencies = [
497 "log",
497 "log",
498 "once_cell",
498 "once_cell",
499 "serde",
499 "serde",
500+ "serde_json",
500 "slotmap",
501 "slotmap",
501 "tokio",
502 "tokio",
502 "tokio-stream",
503 "tokio-stream",
helix-term/src/application.rs+6−185
@@ -3,17 +3,13 @@ use helix_core::{
3 config::{default_syntax_loader, user_syntax_loader},
3 config::{default_syntax_loader, user_syntax_loader},
4 pos_at_coords, syntax, Selection,
4 pos_at_coords, syntax, Selection,
5};
5};
6−use helix_dap::{self as dap, Payload, Request};
7use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
6use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
8−use helix_view::{
9− editor::{Breakpoint, ConfigEvent},
7+use helix_view::{align_view, editor::ConfigEvent, theme, Align, Editor};
10− theme, Editor,
11−};
12use serde_json::json;
8use serde_json::json;
13
9
14use crate::{
10use crate::{
15 args::Args,
11 args::Args,
16− commands::{align_view, apply_workspace_edit, fetch_stack_trace, Align},
12+ commands::apply_workspace_edit,
17 compositor::Compositor,
13 compositor::Compositor,
18 config::Config,
14 config::Config,
19 job::Jobs,
15 job::Jobs,
@@ -233,7 +229,10 @@ impl Application {
233 }
229 }
234 }
230 }
235 Some(payload) = self.editor.debugger_events.next() => {
231 Some(payload) = self.editor.debugger_events.next() => {
236− self.handle_debugger_message(payload).await;
232+ let needs_render = self.editor.handle_debugger_message(payload).await;
233+ if needs_render {
234+ self.render();
235+ }
237 }
236 }
238 Some(config_event) = self.editor.config_events.1.recv() => {
237 Some(config_event) = self.editor.config_events.1.recv() => {
239 self.handle_config_events(config_event);
238 self.handle_config_events(config_event);
@@ -368,184 +367,6 @@ impl Application {
368 }
367 }
369 }
368 }
370
369
371− pub async fn handle_debugger_message(&mut self, payload: helix_dap::Payload) {
372− use crate::commands::dap::{breakpoints_changed, select_thread_id};
373− use dap::requests::RunInTerminal;
374− use helix_dap::{events, Event};
375−
376− let debugger = match self.editor.debugger.as_mut() {
377− Some(debugger) => debugger,
378− None => return,
379− };
380− match payload {
381− Payload::Event(ev) => match *ev {
382− Event::Stopped(events::Stopped {
383− thread_id,
384− description,
385− text,
386− reason,
387− all_threads_stopped,
388− ..
389− }) => {
390− let all_threads_stopped = all_threads_stopped.unwrap_or_default();
391−
392− if all_threads_stopped {
393− if let Ok(response) = debugger.request::<dap::requests::Threads>(()).await {
394− for thread in response.threads {
395− fetch_stack_trace(debugger, thread.id).await;
396− }
397− select_thread_id(
398− &mut self.editor,
399− thread_id.unwrap_or_default(),
400− false,
401− )
402− .await;
403− }
404− } else if let Some(thread_id) = thread_id {
405− debugger.thread_states.insert(thread_id, reason.clone()); // TODO: dap uses "type" || "reason" here
406−
407− // whichever thread stops is made "current" (if no previously selected thread).
408− select_thread_id(&mut self.editor, thread_id, false).await;
409− }
410−
411− let scope = match thread_id {
412− Some(id) => format!("Thread {}", id),
413− None => "Target".to_owned(),
414− };
415−
416− let mut status = format!("{} stopped because of {}", scope, reason);
417− if let Some(desc) = description {
418− status.push_str(&format!(" {}", desc));
419− }
420− if let Some(text) = text {
421− status.push_str(&format!(" {}", text));
422− }
423− if all_threads_stopped {
424− status.push_str(" (all threads stopped)");
425− }
426−
427− self.editor.set_status(status);
428− }
429− Event::Continued(events::Continued { thread_id, .. }) => {
430− debugger
431− .thread_states
432− .insert(thread_id, "running".to_owned());
433− if debugger.thread_id == Some(thread_id) {
434− debugger.resume_application();
435− }
436− }
437− Event::Thread(_) => {
438− // TODO: update thread_states, make threads request
439− }
440− Event::Breakpoint(events::Breakpoint { reason, breakpoint }) => {
441− match &reason[..] {
442− "new" => {
443− if let Some(source) = breakpoint.source {
444− self.editor
445− .breakpoints
446− .entry(source.path.unwrap()) // TODO: no unwraps
447− .or_default()
448− .push(Breakpoint {
449− id: breakpoint.id,
450− verified: breakpoint.verified,
451− message: breakpoint.message,
452− line: breakpoint.line.unwrap().saturating_sub(1), // TODO: no unwrap
453− column: breakpoint.column,
454− ..Default::default()
455− });
456− }
457− }
458− "changed" => {
459− for breakpoints in self.editor.breakpoints.values_mut() {
460− if let Some(i) =
461− breakpoints.iter().position(|b| b.id == breakpoint.id)
462− {
463− breakpoints[i].verified = breakpoint.verified;
464− breakpoints[i].message = breakpoint.message.clone();
465− breakpoints[i].line =
466− breakpoint.line.unwrap().saturating_sub(1); // TODO: no unwrap
467− breakpoints[i].column = breakpoint.column;
468− }
469− }
470− }
471− "removed" => {
472− for breakpoints in self.editor.breakpoints.values_mut() {
473− if let Some(i) =
474− breakpoints.iter().position(|b| b.id == breakpoint.id)
475− {
476− breakpoints.remove(i);
477− }
478− }
479− }
480− reason => {
481− warn!("Unknown breakpoint event: {}", reason);
482− }
483− }
484− }
485− Event::Output(events::Output {
486− category, output, ..
487− }) => {
488− let prefix = match category {
489− Some(category) => {
490− if &category == "telemetry" {
491− return;
492− }
493− format!("Debug ({}):", category)
494− }
495− None => "Debug:".to_owned(),
496− };
497−
498− log::info!("{}", output);
499− self.editor.set_status(format!("{} {}", prefix, output));
500− }
501− Event::Initialized => {
502− // send existing breakpoints
503− for (path, breakpoints) in &mut self.editor.breakpoints {
504− // TODO: call futures in parallel, await all
505− let _ = breakpoints_changed(debugger, path.clone(), breakpoints);
506− }
507− // TODO: fetch breakpoints (in case we're attaching)
508−
509− if debugger.configuration_done().await.is_ok() {
510− self.editor.set_status("Debugged application started");
511− }; // TODO: do we need to handle error?
512− }
513− ev => {
514− log::warn!("Unhandled event {:?}", ev);
515− return; // return early to skip render
516− }
517− },
518− Payload::Response(_) => unreachable!(),
519− Payload::Request(request) => match request.command.as_str() {
520− RunInTerminal::COMMAND => {
521− let arguments: dap::requests::RunInTerminalArguments =
522− serde_json::from_value(request.arguments.unwrap_or_default()).unwrap();
523− // TODO: no unwrap
524−
525− let process = std::process::Command::new("tmux")
526− .arg("split-window")
527− .arg(arguments.args.join(" "))
528− .spawn()
529− .unwrap();
530−
531− let _ = debugger
532− .reply(
533− request.seq,
534− dap::requests::RunInTerminal::COMMAND,
535− serde_json::to_value(dap::requests::RunInTerminalResponse {
536− process_id: Some(process.id()),
537− shell_process_id: None,
538− })
539− .map_err(|e| e.into()),
540− )
541− .await;
542− }
543− _ => log::error!("DAP reverse request not implemented: {:?}", request),
544− },
545− }
546− self.render();
547− }
548−
549 pub async fn handle_language_server_message(
370 pub async fn handle_language_server_message(
550 &mut self,
371 &mut self,
551 call: helix_lsp::Call,
372 call: helix_lsp::Call,
helix-term/src/commands.rs+1−23
@@ -118,29 +118,7 @@ impl<'a> Context<'a> {
118 }
118 }
119}
119}
120
120
121−pub enum Align {
122− Top,
123− Center,
124− Bottom,
125−}
126−
127−pub fn align_view(doc: &Document, view: &mut View, align: Align) {
121+use helix_view::{align_view, Align};
128− let pos = doc
129− .selection(view.id)
130− .primary()
131− .cursor(doc.text().slice(..));
132− let line = doc.text().char_to_line(pos);
133−
134− let height = view.inner_area().height as usize;
135−
136− let relative = match align {
137− Align::Center => height / 2,
138− Align::Top => 0,
139− Align::Bottom => height,
140− };
141−
142− view.offset.row = line.saturating_sub(relative);
143−}
144
122
145/// A MappableCommand is either a static command like "jump_view_up" or a Typable command like
123/// A MappableCommand is either a static command like "jump_view_up" or a Typable command like
146/// :format. It causes a side-effect on the state (usually by creating and applying a transaction).
124/// :format. It causes a side-effect on the state (usually by creating and applying a transaction).
helix-term/src/commands/dap.rs+4−136
@@ -1,14 +1,11 @@
1−use super::{align_view, Align, Context, Editor};
1+use super::{Context, Editor};
2use crate::{
2use crate::{
3 compositor::{self, Compositor},
3 compositor::{self, Compositor},
4 job::{Callback, Jobs},
4 job::{Callback, Jobs},
5 ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent, Text},
5 ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent, Text},
6};
6};
7−use helix_core::{
8− syntax::{DebugArgumentValue, DebugConfigCompletion},
7+use helix_core::syntax::{DebugArgumentValue, DebugConfigCompletion};
9− Selection,
10−};
11−use helix_dap::{self as dap, Client, ThreadId};
8+use helix_dap::{self as dap, Client};
12use helix_lsp::block_on;
9use helix_lsp::block_on;
13use helix_view::editor::Breakpoint;
10use helix_view::editor::Breakpoint;
14
11
@@ -21,79 +18,7 @@ use std::path::PathBuf;
21
18
22use anyhow::{anyhow, bail};
19use anyhow::{anyhow, bail};
23
20
24−#[macro_export]
21+use helix_view::handlers::dap::{breakpoints_changed, jump_to_stack_frame, select_thread_id};
25−macro_rules! debugger {
26− ($editor:expr) => {{
27− match &mut $editor.debugger {
28− Some(debugger) => debugger,
29− None => return,
30− }
31− }};
32−}
33−
34−// general utils:
35−pub fn dap_pos_to_pos(doc: &helix_core::Rope, line: usize, column: usize) -> Option<usize> {
36− // 1-indexing to 0 indexing
37− let line = doc.try_line_to_char(line - 1).ok()?;
38− let pos = line + column.saturating_sub(1);
39− // TODO: this is probably utf-16 offsets
40− Some(pos)
41−}
42−
43−pub async fn select_thread_id(editor: &mut Editor, thread_id: ThreadId, force: bool) {
44− let debugger = debugger!(editor);
45−
46− if !force && debugger.thread_id.is_some() {
47− return;
48− }
49−
50− debugger.thread_id = Some(thread_id);
51− fetch_stack_trace(debugger, thread_id).await;
52−
53− let frame = debugger.stack_frames[&thread_id].get(0).cloned();
54− if let Some(frame) = &frame {
55− jump_to_stack_frame(editor, frame);
56− }
57−}
58−
59−pub async fn fetch_stack_trace(debugger: &mut Client, thread_id: ThreadId) {
60− let (frames, _) = match debugger.stack_trace(thread_id).await {
61− Ok(frames) => frames,
62− Err(_) => return,
63− };
64− debugger.stack_frames.insert(thread_id, frames);
65− debugger.active_frame = Some(0);
66−}
67−
68−pub fn jump_to_stack_frame(editor: &mut Editor, frame: &helix_dap::StackFrame) {
69− let path = if let Some(helix_dap::Source {
70− path: Some(ref path),
71− ..
72− }) = frame.source
73− {
74− path.clone()
75− } else {
76− return;
77− };
78−
79− if let Err(e) = editor.open(path, helix_view::editor::Action::Replace) {
80− editor.set_error(format!("Unable to jump to stack frame: {}", e));
81− return;
82− }
83−
84− let (view, doc) = current!(editor);
85−
86− let text_end = doc.text().len_chars().saturating_sub(1);
87− let start = dap_pos_to_pos(doc.text(), frame.line, frame.column).unwrap_or(0);
88− let end = frame
89− .end_line
90− .and_then(|end_line| dap_pos_to_pos(doc.text(), end_line, frame.end_column.unwrap_or(0)))
91− .unwrap_or(start);
92−
93− let selection = Selection::single(start.min(text_end), end.min(text_end));
94− doc.set_selection(view.id, selection);
95− align_view(doc, view, Align::Center);
96−}
97
22
98fn thread_picker(
23fn thread_picker(
99 cx: &mut Context,
24 cx: &mut Context,
@@ -419,63 +344,6 @@ pub fn dap_toggle_breakpoint(cx: &mut Context) {
419 dap_toggle_breakpoint_impl(cx, path, line);
344 dap_toggle_breakpoint_impl(cx, path, line);
420}
345}
421
346
422−pub fn breakpoints_changed(
423− debugger: &mut dap::Client,
424− path: PathBuf,
425− breakpoints: &mut [Breakpoint],
426−) -> Result<(), anyhow::Error> {
427− // TODO: handle capabilities correctly again, by filterin breakpoints when emitting
428− // if breakpoint.condition.is_some()
429− // && !debugger
430− // .caps
431− // .as_ref()
432− // .unwrap()
433− // .supports_conditional_breakpoints
434− // .unwrap_or_default()
435− // {
436− // bail!(
437− // "Can't edit breakpoint: debugger does not support conditional breakpoints"
438− // )
439− // }
440− // if breakpoint.log_message.is_some()
441− // && !debugger
442− // .caps
443− // .as_ref()
444− // .unwrap()
445− // .supports_log_points
446− // .unwrap_or_default()
447− // {
448− // bail!("Can't edit breakpoint: debugger does not support logpoints")
449− // }
450− let source_breakpoints = breakpoints
451− .iter()
452− .map(|breakpoint| helix_dap::SourceBreakpoint {
453− line: breakpoint.line + 1, // convert from 0-indexing to 1-indexing (TODO: could set debugger to 0-indexing on init)
454− ..Default::default()
455− })
456− .collect::<Vec<_>>();
457−
458− let request = debugger.set_breakpoints(path, source_breakpoints);
459− match block_on(request) {
460− Ok(Some(dap_breakpoints)) => {
461− for (breakpoint, dap_breakpoint) in breakpoints.iter_mut().zip(dap_breakpoints) {
462− breakpoint.id = dap_breakpoint.id;
463− breakpoint.verified = dap_breakpoint.verified;
464− breakpoint.message = dap_breakpoint.message;
465− // TODO: handle breakpoint.message
466− // TODO: verify source matches
467− breakpoint.line = dap_breakpoint.line.unwrap_or(0).saturating_sub(1); // convert to 0-indexing
468− // TODO: no unwrap
469− breakpoint.column = dap_breakpoint.column;
470− // TODO: verify end_linef/col instruction reference, offset
471− }
472− }
473− Err(e) => anyhow::bail!("Failed to set breakpoints: {}", e),
474− _ => {}
475− };
476− Ok(())
477−}
478−
479pub fn dap_toggle_breakpoint_impl(cx: &mut Context, path: PathBuf, line: usize) {
347pub fn dap_toggle_breakpoint_impl(cx: &mut Context, path: PathBuf, line: usize) {
480 // TODO: need to map breakpoints over edits and update them?
348 // TODO: need to map breakpoints over edits and update them?
481 // we shouldn't really allow editing while debug is running though
349 // we shouldn't really allow editing while debug is running though
helix-view/Cargo.toml+1
@@ -36,6 +36,7 @@ slotmap = "1"
36chardetng = "0.1"
36chardetng = "0.1"
37
37
38serde = { version = "1.0", features = ["derive"] }
38serde = { version = "1.0", features = ["derive"] }
39+serde_json = "1.0"
39toml = "0.5"
40toml = "0.5"
40log = "~0.4"
41log = "~0.4"
41
42
helix-view/src/handlers/dap.rsnew file+311
@@ -0,0 +1,311 @@
1+use crate::editor::{Action, Breakpoint};
2+use crate::{align_view, Align, Editor};
3+use helix_core::Selection;
4+use helix_dap::{self as dap, Client, Payload, Request, ThreadId};
5+use helix_lsp::block_on;
6+use log::warn;
7+use std::path::PathBuf;
8+
9+#[macro_export]
10+macro_rules! debugger {
11+ ($editor:expr) => {{
12+ match &mut $editor.debugger {
13+ Some(debugger) => debugger,
14+ None => return,
15+ }
16+ }};
17+}
18+
19+// general utils:
20+pub fn dap_pos_to_pos(doc: &helix_core::Rope, line: usize, column: usize) -> Option<usize> {
21+ // 1-indexing to 0 indexing
22+ let line = doc.try_line_to_char(line - 1).ok()?;
23+ let pos = line + column.saturating_sub(1);
24+ // TODO: this is probably utf-16 offsets
25+ Some(pos)
26+}
27+
28+pub async fn select_thread_id(editor: &mut Editor, thread_id: ThreadId, force: bool) {
29+ let debugger = debugger!(editor);
30+
31+ if !force && debugger.thread_id.is_some() {
32+ return;
33+ }
34+
35+ debugger.thread_id = Some(thread_id);
36+ fetch_stack_trace(debugger, thread_id).await;
37+
38+ let frame = debugger.stack_frames[&thread_id].get(0).cloned();
39+ if let Some(frame) = &frame {
40+ jump_to_stack_frame(editor, frame);
41+ }
42+}
43+
44+pub async fn fetch_stack_trace(debugger: &mut Client, thread_id: ThreadId) {
45+ let (frames, _) = match debugger.stack_trace(thread_id).await {
46+ Ok(frames) => frames,
47+ Err(_) => return,
48+ };
49+ debugger.stack_frames.insert(thread_id, frames);
50+ debugger.active_frame = Some(0);
51+}
52+
53+pub fn jump_to_stack_frame(editor: &mut Editor, frame: &helix_dap::StackFrame) {
54+ let path = if let Some(helix_dap::Source {
55+ path: Some(ref path),
56+ ..
57+ }) = frame.source
58+ {
59+ path.clone()
60+ } else {
61+ return;
62+ };
63+
64+ if let Err(e) = editor.open(path, Action::Replace) {
65+ editor.set_error(format!("Unable to jump to stack frame: {}", e));
66+ return;
67+ }
68+
69+ let (view, doc) = current!(editor);
70+
71+ let text_end = doc.text().len_chars().saturating_sub(1);
72+ let start = dap_pos_to_pos(doc.text(), frame.line, frame.column).unwrap_or(0);
73+ let end = frame
74+ .end_line
75+ .and_then(|end_line| dap_pos_to_pos(doc.text(), end_line, frame.end_column.unwrap_or(0)))
76+ .unwrap_or(start);
77+
78+ let selection = Selection::single(start.min(text_end), end.min(text_end));
79+ doc.set_selection(view.id, selection);
80+ align_view(doc, view, Align::Center);
81+}
82+
83+pub fn breakpoints_changed(
84+ debugger: &mut dap::Client,
85+ path: PathBuf,
86+ breakpoints: &mut [Breakpoint],
87+) -> Result<(), anyhow::Error> {
88+ // TODO: handle capabilities correctly again, by filterin breakpoints when emitting
89+ // if breakpoint.condition.is_some()
90+ // && !debugger
91+ // .caps
92+ // .as_ref()
93+ // .unwrap()
94+ // .supports_conditional_breakpoints
95+ // .unwrap_or_default()
96+ // {
97+ // bail!(
98+ // "Can't edit breakpoint: debugger does not support conditional breakpoints"
99+ // )
100+ // }
101+ // if breakpoint.log_message.is_some()
102+ // && !debugger
103+ // .caps
104+ // .as_ref()
105+ // .unwrap()
106+ // .supports_log_points
107+ // .unwrap_or_default()
108+ // {
109+ // bail!("Can't edit breakpoint: debugger does not support logpoints")
110+ // }
111+ let source_breakpoints = breakpoints
112+ .iter()
113+ .map(|breakpoint| helix_dap::SourceBreakpoint {
114+ line: breakpoint.line + 1, // convert from 0-indexing to 1-indexing (TODO: could set debugger to 0-indexing on init)
115+ ..Default::default()
116+ })
117+ .collect::<Vec<_>>();
118+
119+ let request = debugger.set_breakpoints(path, source_breakpoints);
120+ match block_on(request) {
121+ Ok(Some(dap_breakpoints)) => {
122+ for (breakpoint, dap_breakpoint) in breakpoints.iter_mut().zip(dap_breakpoints) {
123+ breakpoint.id = dap_breakpoint.id;
124+ breakpoint.verified = dap_breakpoint.verified;
125+ breakpoint.message = dap_breakpoint.message;
126+ // TODO: handle breakpoint.message
127+ // TODO: verify source matches
128+ breakpoint.line = dap_breakpoint.line.unwrap_or(0).saturating_sub(1); // convert to 0-indexing
129+ // TODO: no unwrap
130+ breakpoint.column = dap_breakpoint.column;
131+ // TODO: verify end_linef/col instruction reference, offset
132+ }
133+ }
134+ Err(e) => anyhow::bail!("Failed to set breakpoints: {}", e),
135+ _ => {}
136+ };
137+ Ok(())
138+}
139+
140+impl Editor {
141+ pub async fn handle_debugger_message(&mut self, payload: helix_dap::Payload) -> bool {
142+ use dap::requests::RunInTerminal;
143+ use helix_dap::{events, Event};
144+
145+ let debugger = match self.debugger.as_mut() {
146+ Some(debugger) => debugger,
147+ None => return false,
148+ };
149+ match payload {
150+ Payload::Event(ev) => match *ev {
151+ Event::Stopped(events::Stopped {
152+ thread_id,
153+ description,
154+ text,
155+ reason,
156+ all_threads_stopped,
157+ ..
158+ }) => {
159+ let all_threads_stopped = all_threads_stopped.unwrap_or_default();
160+
161+ if all_threads_stopped {
162+ if let Ok(response) = debugger.request::<dap::requests::Threads>(()).await {
163+ for thread in response.threads {
164+ fetch_stack_trace(debugger, thread.id).await;
165+ }
166+ select_thread_id(self, thread_id.unwrap_or_default(), false).await;
167+ }
168+ } else if let Some(thread_id) = thread_id {
169+ debugger.thread_states.insert(thread_id, reason.clone()); // TODO: dap uses "type" || "reason" here
170+
171+ // whichever thread stops is made "current" (if no previously selected thread).
172+ select_thread_id(self, thread_id, false).await;
173+ }
174+
175+ let scope = match thread_id {
176+ Some(id) => format!("Thread {}", id),
177+ None => "Target".to_owned(),
178+ };
179+
180+ let mut status = format!("{} stopped because of {}", scope, reason);
181+ if let Some(desc) = description {
182+ status.push_str(&format!(" {}", desc));
183+ }
184+ if let Some(text) = text {
185+ status.push_str(&format!(" {}", text));
186+ }
187+ if all_threads_stopped {
188+ status.push_str(" (all threads stopped)");
189+ }
190+
191+ self.set_status(status);
192+ }
193+ Event::Continued(events::Continued { thread_id, .. }) => {
194+ debugger
195+ .thread_states
196+ .insert(thread_id, "running".to_owned());
197+ if debugger.thread_id == Some(thread_id) {
198+ debugger.resume_application();
199+ }
200+ }
201+ Event::Thread(_) => {
202+ // TODO: update thread_states, make threads request
203+ }
204+ Event::Breakpoint(events::Breakpoint { reason, breakpoint }) => {
205+ match &reason[..] {
206+ "new" => {
207+ if let Some(source) = breakpoint.source {
208+ self.breakpoints
209+ .entry(source.path.unwrap()) // TODO: no unwraps
210+ .or_default()
211+ .push(Breakpoint {
212+ id: breakpoint.id,
213+ verified: breakpoint.verified,
214+ message: breakpoint.message,
215+ line: breakpoint.line.unwrap().saturating_sub(1), // TODO: no unwrap
216+ column: breakpoint.column,
217+ ..Default::default()
218+ });
219+ }
220+ }
221+ "changed" => {
222+ for breakpoints in self.breakpoints.values_mut() {
223+ if let Some(i) =
224+ breakpoints.iter().position(|b| b.id == breakpoint.id)
225+ {
226+ breakpoints[i].verified = breakpoint.verified;
227+ breakpoints[i].message = breakpoint.message.clone();
228+ breakpoints[i].line =
229+ breakpoint.line.unwrap().saturating_sub(1); // TODO: no unwrap
230+ breakpoints[i].column = breakpoint.column;
231+ }
232+ }
233+ }
234+ "removed" => {
235+ for breakpoints in self.breakpoints.values_mut() {
236+ if let Some(i) =
237+ breakpoints.iter().position(|b| b.id == breakpoint.id)
238+ {
239+ breakpoints.remove(i);
240+ }
241+ }
242+ }
243+ reason => {
244+ warn!("Unknown breakpoint event: {}", reason);
245+ }
246+ }
247+ }
248+ Event::Output(events::Output {
249+ category, output, ..
250+ }) => {
251+ let prefix = match category {
252+ Some(category) => {
253+ if &category == "telemetry" {
254+ return false;
255+ }
256+ format!("Debug ({}):", category)
257+ }
258+ None => "Debug:".to_owned(),
259+ };
260+
261+ log::info!("{}", output);
262+ self.set_status(format!("{} {}", prefix, output));
263+ }
264+ Event::Initialized => {
265+ // send existing breakpoints
266+ for (path, breakpoints) in &mut self.breakpoints {
267+ // TODO: call futures in parallel, await all
268+ let _ = breakpoints_changed(debugger, path.clone(), breakpoints);
269+ }
270+ // TODO: fetch breakpoints (in case we're attaching)
271+
272+ if debugger.configuration_done().await.is_ok() {
273+ self.set_status("Debugged application started");
274+ }; // TODO: do we need to handle error?
275+ }
276+ ev => {
277+ log::warn!("Unhandled event {:?}", ev);
278+ return false; // return early to skip render
279+ }
280+ },
281+ Payload::Response(_) => unreachable!(),
282+ Payload::Request(request) => match request.command.as_str() {
283+ RunInTerminal::COMMAND => {
284+ let arguments: dap::requests::RunInTerminalArguments =
285+ serde_json::from_value(request.arguments.unwrap_or_default()).unwrap();
286+ // TODO: no unwrap
287+
288+ let process = std::process::Command::new("tmux")
289+ .arg("split-window")
290+ .arg(arguments.args.join(" "))
291+ .spawn()
292+ .unwrap();
293+
294+ let _ = debugger
295+ .reply(
296+ request.seq,
297+ dap::requests::RunInTerminal::COMMAND,
298+ serde_json::to_value(dap::requests::RunInTerminalResponse {
299+ process_id: Some(process.id()),
300+ shell_process_id: None,
301+ })
302+ .map_err(|e| e.into()),
303+ )
304+ .await;
305+ }
306+ _ => log::error!("DAP reverse request not implemented: {:?}", request),
307+ },
308+ }
309+ true
310+ }
311+}
helix-view/src/handlers/lsp.rsnew file

No changes to the content.

helix-view/src/lib.rs+28
@@ -6,6 +6,10 @@ pub mod document;
6pub mod editor;
6pub mod editor;
7pub mod graphics;
7pub mod graphics;
8pub mod gutter;
8pub mod gutter;
9+pub mod handlers {
10+ pub mod dap;
11+ pub mod lsp;
12+}
9pub mod info;
13pub mod info;
10pub mod input;
14pub mod input;
11pub mod keyboard;
15pub mod keyboard;
@@ -36,6 +40,30 @@ slotmap::new_key_type! {
36 pub struct ViewId;
40 pub struct ViewId;
37}
41}
38
42
43+pub enum Align {
44+ Top,
45+ Center,
46+ Bottom,
47+}
48+
49+pub fn align_view(doc: &Document, view: &mut View, align: Align) {
50+ let pos = doc
51+ .selection(view.id)
52+ .primary()
53+ .cursor(doc.text().slice(..));
54+ let line = doc.text().char_to_line(pos);
55+
56+ let height = view.inner_area().height as usize;
57+
58+ let relative = match align {
59+ Align::Center => height / 2,
60+ Align::Top => 0,
61+ Align::Bottom => height,
62+ };
63+
64+ view.offset.row = line.saturating_sub(relative);
65+}
66+
39pub use document::Document;
67pub use document::Document;
40pub use editor::Editor;
68pub use editor::Editor;
41pub use theme::Theme;
69pub use theme::Theme;