Skip to content

Commit f4cf282

Browse files
committed
debugger_message API for Python
1 parent 158389f commit f4cf282

File tree

7 files changed

+93
-53
lines changed

7 files changed

+93
-53
lines changed

adapter/adapter-protocol/src/codelldb.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,7 @@ pub enum RequestArguments {
115115
_symbols(SymbolsRequest),
116116
_excludeCaller(ExcludeCallerRequest),
117117
_setExcludedCallers(SetExcludedCallersRequest),
118-
//_pythonMessage(Box<serde_json::value::RawValue>), https://github.com/serde-rs/json/issues/883
119-
_pythonMessage(serde_json::value::Value),
118+
_pythonMessage(serde_json::Value),
120119
#[serde(other)]
121120
unknown,
122121
}
@@ -188,7 +187,7 @@ pub enum EventBody {
188187
invalidated(InvalidatedEventBody),
189188
stopped(StoppedEventBody),
190189
// Custom
191-
_pythonMessage(Box<serde_json::value::RawValue>),
190+
_pythonMessage(serde_json::Value),
192191
}
193192

194193
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]

adapter/codelldb/src/debug_session.rs

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::fsutil::normalize_path;
1414
use crate::handles::{self, HandleTree};
1515
use crate::must_initialize::{Initialized, MustInitialize, NotInitialized};
1616
use crate::platform::{get_fs_path_case, make_case_folder, pipe};
17+
use crate::python::PythonEvent;
1718
use crate::python::{PythonInterface, PythonSession};
1819
use crate::shared::Shared;
1920
use crate::terminal::Terminal;
@@ -119,12 +120,12 @@ impl DebugSession {
119120

120121
let (con_reader, con_writer) = pipe().unwrap();
121122

122-
let python = if let Some(python_interface) = python_interface {
123+
let (python, mut python_events_stream) = if let Some(python_interface) = python_interface {
123124
let (python, python_events) = python_interface.new_session(&debugger, con_writer.try_clone().unwrap());
124-
DebugSession::pipe_python_events(&dap_session, python_events);
125-
Some(python)
125+
(Some(python), python_events)
126126
} else {
127-
None
127+
let (_, receiver) = mpsc::channel(0);
128+
(None, receiver)
128129
};
129130

130131
let target = debugger.dummy_target();
@@ -194,6 +195,14 @@ impl DebugSession {
194195
local_set.spawn_local(async move {
195196
loop {
196197
tokio::select! {
198+
// Python events
199+
Some(event) = python_events_stream.recv() => {
200+
shared_session.map(|s| s.handle_python_event(event)).await;
201+
}
202+
// LLDB events.
203+
Some(event) = debug_events_stream.recv() => {
204+
shared_session.map(|s| s.handle_debug_event(event)).await;
205+
}
197206
// Requests from VSCode
198207
request = requests_receiver.recv() => {
199208
match request {
@@ -205,10 +214,6 @@ impl DebugSession {
205214
}
206215
}
207216
}
208-
// LLDB events.
209-
Some(event) = debug_events_stream.recv() => {
210-
shared_session.map( |s| s.handle_debug_event(event)).await;
211-
}
212217
}
213218
}
214219

@@ -263,16 +268,6 @@ impl DebugSession {
263268
});
264269
}
265270

266-
// Pipe events from the Python interface to the DAP session.
267-
fn pipe_python_events(dap_session: &DAPSession, mut python_events: mpsc::Receiver<EventBody>) {
268-
let dap_session = dap_session.clone();
269-
tokio::spawn(async move {
270-
while let Some(event) = python_events.recv().await {
271-
log_errors!(dap_session.send_event(event).await);
272-
}
273-
});
274-
}
275-
276271
/// Handle request cancellations.
277272
fn cancellation_filter(
278273
dap_session: &DAPSession,
@@ -1391,6 +1386,17 @@ impl DebugSession {
13911386
Ok(())
13921387
}
13931388

1389+
fn handle_python_event(&mut self, event: PythonEvent) {
1390+
match event {
1391+
PythonEvent::SendDapEvent(event) => {
1392+
log_errors!(self.dap_session.try_send_event(event));
1393+
}
1394+
PythonEvent::DebuggerMessage { output, category } => {
1395+
self.console_message_impl(Some(&category), output);
1396+
}
1397+
}
1398+
}
1399+
13941400
fn handle_adapter_settings(&mut self, args: AdapterSettings) -> Result<(), Error> {
13951401
let old_console_mode = self.console_mode;
13961402
self.update_adapter_settings_and_caps(&args);

adapter/codelldb/src/python.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::prelude::*;
33
use crate::must_initialize::{Initialized, MustInitialize};
44
use adapter_protocol::{AdapterSettings, EventBody};
55
use lldb::*;
6+
use serde_derive::*;
67

78
use std::collections::HashMap;
89
use std::ffi::CStr;
@@ -34,12 +35,19 @@ impl Drop for PyObject {
3435

3536
unsafe impl Send for PyObject {}
3637

38+
#[derive(Deserialize, Serialize, Debug, Clone)]
39+
#[serde(tag = "type")]
40+
pub enum PythonEvent {
41+
DebuggerMessage { output: String, category: String },
42+
SendDapEvent(EventBody),
43+
}
44+
3745
// Interface through which the rest of CodeLLDB interacts with Python, via C ABI.
3846
// It is intended to be a singleton, since there is only one Python interpreter in LLDB process.
3947
pub struct PythonInterface {
4048
adapter_dir: PathBuf,
4149
py: MustInitialize<PythonCalls>,
42-
session_event_senders: Mutex<HashMap<u64, mpsc::Sender<EventBody>>>,
50+
session_event_senders: Mutex<HashMap<u64, mpsc::Sender<PythonEvent>>>,
4351
}
4452

4553
struct PythonCalls {
@@ -102,11 +110,12 @@ pub fn initialize(debugger: &SBDebugger, adapter_dir: &Path) -> Result<Arc<Pytho
102110
unsafe extern "C" fn python_event_callback(
103111
interface: *mut PythonInterface,
104112
session_id: c_int,
105-
body_json: *const c_char,
113+
event: *const c_char,
106114
) {
107-
let body_json = CStr::from_ptr(body_json).to_str().unwrap().to_string();
108-
let event = EventBody::_pythonMessage(serde_json::value::RawValue::from_string(body_json).unwrap());
109-
(*interface).dispatch_python_event(session_id as u64, event);
115+
match serde_json::from_slice::<PythonEvent>(CStr::from_ptr(event).to_bytes()) {
116+
Ok(event) => (*interface).dispatch_python_event(session_id as u64, event),
117+
Err(err) => error!("{}", err),
118+
}
110119
}
111120

112121
unsafe extern "C" fn init_callback(
@@ -170,7 +179,7 @@ impl PythonInterface {
170179
self: Arc<PythonInterface>,
171180
debugger: &SBDebugger,
172181
console_stream: std::fs::File,
173-
) -> (PythonSession, mpsc::Receiver<EventBody>) {
182+
) -> (PythonSession, mpsc::Receiver<PythonEvent>) {
174183
let (sender, receiver) = mpsc::channel(100);
175184
let session = PythonSession {
176185
interface: self.clone(),
@@ -189,8 +198,8 @@ impl PythonInterface {
189198
(session, receiver)
190199
}
191200

192-
// Send event generated by Python to DAP client
193-
fn dispatch_python_event(&self, session_id: u64, event: EventBody) {
201+
// Dispatch Python events to the appropriate DebugSession
202+
fn dispatch_python_event(&self, session_id: u64, event: PythonEvent) {
194203
let senders = self.session_event_senders.lock().unwrap();
195204
if let Some(sender) = senders.get(&session_id) {
196205
log_errors!(sender.try_send(event));
@@ -381,3 +390,11 @@ fn evaluate() {
381390
let value = result.unwrap().value_as_signed(0);
382391
assert_eq!(value, 4);
383392
}
393+
394+
#[test]
395+
fn serialization() {
396+
serde_json::from_str::<PythonEvent>(
397+
r#"{"type":"SendDapEvent", "event": "_pythonMessage", "body": {"foo": "bar"} }"#,
398+
)
399+
.unwrap();
400+
}

adapter/scripts/codelldb/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .api import evaluate, wrap, unwrap, get_config, create_webview, display_html
1+
from .api import evaluate, wrap, unwrap, get_config, create_webview, display_html, debugger_message
22
from .value import Value
33

44
def __lldb_init_module(debugger, internal_dict): # pyright: ignore

adapter/scripts/codelldb/api.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,17 @@ def unwrap(obj: Value) -> lldb.SBValue:
4343

4444
def create_webview(html: Optional[str] = None, title: Optional[str] = None, view_column: Optional[int] = None,
4545
preserve_focus: bool = False, enable_find_widget: bool = False,
46-
retain_context_when_hidden: bool = False, enable_scripts: bool = False):
46+
retain_context_when_hidden: bool = False, enable_scripts: bool = False,
47+
preserve_orphaned: bool = False):
4748
'''Create a [webview panel](https://code.visualstudio.com/api/references/vscode-api#WebviewPanel).
4849
html: HTML content to display in the webview. May be later replaced via Webview.set_html().
4950
title: Panel title.
5051
view_column: Column in which to show the webview.
5152
preserve_focus: Whether to preserve focus in the current editor when revealing the webview.
52-
enable_find_widget: Controls if the find widget is enabled in the panel.
53-
retain_context_when_hidden: Controls if the webview panel retains its context when it is hidden.
54-
enable_scripts: Controls if scripts are enabled in the webview.
53+
enable_find_widget: Controls whether the find widget is enabled in the panel.
54+
retain_context_when_hidden: Controls whether the webview panel retains its context when hidden.
55+
enable_scripts: Controls whether scripts are enabled in the webview.
56+
preserve_orphaned: Preserve webview panel after the end of the debug session.
5557
'''
5658
debugger_id = lldb.debugger.GetID()
5759
webview = Webview(debugger_id)
@@ -64,17 +66,24 @@ def create_webview(html: Optional[str] = None, title: Optional[str] = None, view
6466
preserveFocus=preserve_focus,
6567
enableFindWidget=enable_find_widget,
6668
retainContextWhenHidden=retain_context_when_hidden,
67-
enableScripts=enable_scripts
69+
enableScripts=enable_scripts,
70+
preserveOrphaned=preserve_orphaned,
6871
)
6972
)
7073
return webview
7174

7275

73-
def display_html(html: str, title: Optional[str] = None, position: Optional[int] = None, reveal: bool = False):
76+
def debugger_message(output: str, category: str = 'console'):
77+
interface.fire_event(lldb.debugger.GetID(), dict(type='DebuggerMessage', output=output, category=category))
78+
79+
80+
def display_html(html: str, title: Optional[str] = None, position: Optional[int] = None, reveal: bool = False,
81+
preserve_orphaned: bool = True):
7482
'''Display HTML content in a webview panel.
7583
display_html is **deprecated**, use create_webview instead.
7684
'''
77-
global html_webview
85+
inst_dict = interface.get_instance_dict(lldb.debugger)
86+
html_webview = inst_dict.get('html_webview')
7887
if html_webview is None:
7988
warnings.warn("display_html is deprecated, use create_webview instead", DeprecationWarning)
8089

@@ -83,27 +92,25 @@ def display_html(html: str, title: Optional[str] = None, position: Optional[int]
8392
title=title,
8493
view_column=position,
8594
preserve_focus=not reveal,
86-
enable_scripts=True
95+
enable_scripts=True,
96+
preserve_orphaned=preserve_orphaned,
8797
)
8898

8999
def on_message(message):
90100
if message['command'] == 'execute':
91101
lldb.debugger.HandleCommand(message['text'])
92102

93103
def on_disposed(message):
94-
global html_webview
95-
html_webview = None
104+
del globals()['html_webview']
96105

97106
html_webview.on_did_receive_message.add(on_message)
98107
html_webview.on_did_dispose.add(on_disposed)
108+
inst_dict['html_webview'] = html_webview
99109
else:
100110
html_webview.set_html(html)
101111
if reveal:
102112
html_webview.reveal(view_column=position)
103113

104114

105-
html_webview = None
106-
107-
108115
def __lldb_init_module(debugger, internal_dict): # pyright: ignore
109116
debugger.HandleCommand('command script add -c debugger.DebugInfoCommand debug_info')

adapter/scripts/codelldb/interface.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,17 +111,22 @@ def PyResult(name, T): # type: (str, type) -> type
111111

112112
# ============================================================================================
113113

114-
114+
# Incoming messages from the DAP client
115115
on_did_receive_message = Event()
116116

117117

118-
def send_message(debugger_id, message):
118+
def send_message(debugger_id, message_body):
119+
'''Send '_pythonMessage' event to the DAP client'''
120+
fire_event(debugger_id, dict(type='SendDapEvent', event='_pythonMessage', body=message_body))
121+
122+
123+
def fire_event(debugger_id, event_body: object):
119124
log.error('interface has not been initialized yet')
120125

121126

122127
def initialize(init_callback_addr, callback_context, send_message_addr, log_level):
123128
'''One-time initialization of Rust-Python interface'''
124-
global send_message
129+
global fire_event
125130
logging.getLogger().setLevel(log_level)
126131

127132
interrupt = ctypes.pythonapi.PyErr_SetInterrupt
@@ -139,12 +144,12 @@ def initialize(init_callback_addr, callback_context, send_message_addr, log_leve
139144
init_callback = CFUNCTYPE(None, c_void_p, POINTER(c_void_p), c_size_t)(init_callback_addr)
140145
init_callback(callback_context, ptr_arr, len(pointers))
141146

142-
send_message_raw = CFUNCTYPE(None, c_void_p, c_int, c_char_p)(send_message_addr)
147+
fire_event_raw = CFUNCTYPE(None, c_void_p, c_int, c_char_p)(send_message_addr)
143148

144-
def _send_message(debugger_id, message):
145-
return send_message_raw(callback_context, debugger_id, str_to_bytes(json.dumps(message)))
149+
def _fire_event(debugger_id, event_body):
150+
return fire_event_raw(callback_context, debugger_id, str_to_bytes(json.dumps(event_body)))
146151
# Override the global dummy function
147-
send_message = _send_message
152+
fire_event = _fire_event
148153

149154

150155
session_stdouts = {}

extension/webview.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ import {
33
} from "vscode";
44
import { Dict } from './novsc/commonTypes';
55

6+
interface DebuggerPanel extends WebviewPanel {
7+
preserveOrphaned: boolean
8+
}
9+
610
export class WebviewManager {
7-
sessionPanels: Dict<Dict<WebviewPanel>> = {};
11+
sessionPanels: Dict<Dict<DebuggerPanel>> = {};
812

913
constructor(context: ExtensionContext) {
1014
let subscriptions = context.subscriptions;
@@ -17,7 +21,8 @@ export class WebviewManager {
1721
let panels = this.sessionPanels[session.id];
1822
if (panels) {
1923
for (let panel of Object.values(panels)) {
20-
panel.dispose();
24+
if (!panel.preserveOrphaned)
25+
panel.dispose();
2126
}
2227
}
2328
}
@@ -43,7 +48,7 @@ export class WebviewManager {
4348

4449
createWebview(session: DebugSession, body: any) {
4550
let view_id = body.id;
46-
let panel = window.createWebviewPanel(
51+
let panel = <DebuggerPanel>window.createWebviewPanel(
4752
'codelldb.webview',
4853
body.title || session.name,
4954
{
@@ -65,6 +70,7 @@ export class WebviewManager {
6570
});
6671
if (body.html)
6772
panel.webview.html = body.html;
73+
panel.preserveOrphaned = body.preserveOrphaned
6874

6975
let panels = this.sessionPanels[session.id];
7076
if (panels == undefined) {

0 commit comments

Comments
 (0)