diff --git a/CHANGELOG.md b/CHANGELOG.md index b8a1b3a6..87157140 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Release Notes +# 1.11.0 + +### New +- Updated bundled LLDB to v19.1.0. +- The Python module implementing the CodeLLDB Python API is now called `codelldb` (aliased to `debugger` for backward + compatibility). +- Python scripts running in the context of CodeLLDB can now read workspace configuration settings stored under + the `lldb.script` namespace via `codelldb.get_config()`. + +### Changed +- To reduce the maintenance burden, support for the Rust language service and custom data formatters in CodeLLDB has + been removed. The constant breaking changes in LLDB's language service API, along with Rust's evolving internal + representation of `std::` types, have made it increasingly difficult to maintain these updates. Future versions of + CodeLLDB will be based on stock LLDB, without the Rust language service. Rust data types will still have partial + support via the data formatters provided by `rustc`, but custom formatters will no longer be maintained. + # 1.10.0 ## New @@ -14,12 +30,12 @@ # 1.9.2 ## New -- Implemented [Excluded Callers](MANUAL.md#excluded-callers) feature, similar to the [one in Javascript debugger](https://code.visualstudio.com/updates/v1_64#_javascript-debugging). +- Implemented [Excluded Callers](MANUAL.md#excluded-callers) feature, similar to the + [one in Javascript debugger](https://code.visualstudio.com/updates/v1_64#_javascript-debugging). - Added [create_webview()](MANUAL.md#webview) Python API, which allows scripts to create and manipulate VSCode Webviews. -This function supersedes functionality of the older `display_html` API. + This function supersedes functionality of the older `display_html` API. - Enabled conditions on exception breakpoints. - # 1.9.1 ## New diff --git a/CMakeLists.txt b/CMakeLists.txt index bfa58cf2..79729e87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10) project(CodeLLDB) enable_testing() -set(VERSION "1.10.1") # Base version +set(VERSION "1.11.0") # Base version include(cmake/CopyFiles.cmake) diff --git a/Cargo.toml b/Cargo.toml index c9ba5104..bdab307a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "adapter/lldb-stub", "debuggee/rust", ] +resolver = "1" [profile.release] debug = true diff --git a/MANUAL.md b/MANUAL.md index bc41fc95..e77374a2 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -38,7 +38,7 @@ # Starting a New Debug Session -To start a debugging session you will need to create a [launch configuration](https://code.visualstudio.com/Docs/editor/debugging#_launch-configurations) for your program. Here's a minimal one: +To start a debugging session, you will need to create a [launch configuration](https://code.visualstudio.com/Docs/editor/debugging#_launch-configurations) for your program. Here's a minimal one: ```javascript { @@ -60,7 +60,7 @@ To start a debugging session you will need to create a [launch configuration](ht |**initCommands** |[string]| LLDB commands executed upon debugger startup. |**targetCreateCommands**|[string]| LLDB commands executed to create debug target. |**preRunCommands** |[string]| LLDB commands executed just before launching/attaching the debuggee. -|**processCreateCommands**|[string]| LLDB commands executed to created/attach the debuggee process. +|**processCreateCommands**|[string]| LLDB commands executed to create/attach the debuggee process. |**postRunCommands** |[string]| LLDB commands executed just after launching/attaching the debuggee. |**exitCommands** |[string]| LLDB commands executed at the end of the debugging session. |**expressions** |string| The default expression evaluator type: `simple`, `python` or `native`. See [Expressions](#expressions). @@ -85,7 +85,7 @@ These attributes are applicable when the "launch" initiation method is selected: |**env** |dictionary| Environment variables to set in addition to the ones inherited from the parent process environment (unless LLDB's `target.inherit-env` setting has been set to `false`, in which case the initial process environment is empty). You may refer to existing environment variables using `${env:NAME}` syntax. For example, in order to alter the inherited `PATH` variable, you can do this: `"PATH":"${env:HOME}/bin:${env:PATH}"`. |**envFile** |string| Path of the file to read the environment variables from. Note that `env` entries will override `envPath` entries. |**stdio** |string ❘ [string] ❘ dictionary| See [Stdio Redirection](#stdio-redirection). -|**terminal** |string| Destination for debuggee stdio streams: +|**terminal** |string| Destination for debuggee's stdio streams: |**stopOnEntry** |boolean| Whether to stop debuggee immediately after launching. Operations performed for launch: @@ -96,7 +96,7 @@ Operations performed for launch: - Otherwise, target is created from the binary pointed to by the `program` attribute. - Target properties are configured using `args`, `env`, `cwd`, `stdio`, etc, configuration attributes. - Breakpoints are created. -- The `preRunCommands` sequence is executed. These commands may alter debug target configuration (e.g. alter args or env). +- The `preRunCommands` sequence is executed. These commands may alter debug target configuration (e.g. args or env). - The debuggee process is created: - If `processCreateCommands` attribute is present, this command sequence is executed. These are expected to have created a process corresponding to the debug target. @@ -127,7 +127,7 @@ These attributes are applicable when the "attach" initiation method is selected: |attribute |type | | |-------------------|--------|---------| |**program** |string |Path of the executable file. -|**pid** |number |Process id to attach to. **pid** may be omitted, in which case debugger will attempt to locate an already running instance of the program. You may also put `${command:pickProcess}` or `${command:pickMyProcess}` here to choose a process interactively. +|**pid** |number |Process id to attach to. **pid** may be omitted, in which case debugger will attempt to locate an already running instance of the program. You may also use [`${command:pickProcess}` or `${command:pickMyProcess}`](#pick-process-command) here to choose process interactively. |**stopOnEntry** |boolean |Whether to stop the debuggee immediately after attaching. |**waitFor** |boolean |Wait for the process to launch. @@ -365,7 +365,7 @@ target modules load --file ${workspaceFolder}/build/debuggee -s ` command: ``` ## Source Path Remapping -Source path remapping is helpful in cases when program's source code is located in a different -directory then it was in at build time (for example, if a build server was used). +Source path remapping is helpful when the program's source code is located in a different directory than it was at build time (for example, if a build server was used). A source map consists of pairs of "from" and "to" path prefixes. When the debugger encounters a source file path beginning with one of the "from" prefixes, it will substitute the corresponding "to" prefix @@ -585,7 +584,7 @@ views](https://lldb.llvm.org/use/varformats.html) of the debuggee variables. Fo `std::vector` or comparing `std::string` to a string literal should "just work". The followng features are supported: -- References to variables: all identifiers are assumed to refer to variables in the debuggee's current stack frame. +- References to variables: all identifiers are assumed to refer to variables in the debuggee current stack frame. The identifiers may be qualified with namespaces and template parameters (e.g. `std::numeric_limits::digits`). - Embedded [native expressions](#native-expressions): these must be delimited with `${` and `}`. - Literals: integers, floats and strings, `True`, `False`. @@ -627,42 +626,70 @@ thus they are often not as convenient as "simple" or "python" expressions. ## Debugger API -CodeLLDB provides extended Python API via the `debugger` module (which is auto-imported into debugger's main script context). -This module exports the following functions: - -def **evaluate(expression: `str`, unwrap=False) -> `Value` | `lldb.SBValue`** : Performs dynamic evaluation of [native expressions](#native-expressions) returning instances of [`Value`](#value). -- **expression**: The expression to evaluate. -- **unwrap**: Whether to unwrap the result and return it as `lldb.SBValue`. - -**unwrap(obj: `Value`) -> `lldb.SBValue`** : Extracts an [`lldb.SBValue`](https://lldb.llvm.org/python_api/lldb.SBValue.html) from [`Value`](#value). - -**wrap(obj: `lldb.SBValue`) -> `Value`** : Wraps [`lldb.SBValue`](https://lldb.llvm.org/python_api/lldb.SBValue.html) in a [`Value`](#value) object. - -**create_webview(html: `str`=None, title: `str`=None, view_column: `int`=None, preserve_focus: `bool`=False, enable_find_widget: `bool`=False, retain_context_when_hidden: `bool`=False, enable_scripts: `bool`=False) -> Webview** : -Create a [Webview](#webview), which can be used to display HTML content in VSCode UI. - -**display_html(html: `str`, title: `str` = None, position: `int` = None, reveal: `bool` = False)** : (deprecated - use webviews instead) Displays content in a VSCode Webview panel: -- **html**: HTML markup to display. -- **title**: Title of the panel. Defaults to the name of the current launch configuration. -- **position**: Position (column) of the panel. The allowed range is 1 through 3. -- **reveal**: Whether to reveal the panel if one already exists. +CodeLLDB provides extended Python API via the `codelldb` module (also aliased as `debugger`), +which is auto-imported into debugger's main script context: + +```python +# codelldb + +def get_config(name: str, default: Any = None) -> Any: + '''Retrieve a configuration value from the adapter settings. + name: Dot-separated path of the setting to retrieve. For example, 'foo.bar', will retrieve the value of `lldb.script.foo.bar`. + default: The default value to return if the configuration value is not found. + ''' +def evaluate(expr: str, unwrap: bool = False) -> Value | lldb.SBValue: + '''Performs dynamic evaluation of native expressions returning instances of Value or SBValue. + expression: The expression to evaluate. + unwrap: Whether to unwrap the result and return it as lldb.SBValue + ''' +def wrap(obj: lldb.SBValue) -> Value: + '''Extracts an lldb.SBValue from Value''' +def unwrap(obj: Value) -> lldb.SBValue: + '''Wraps lldb.SBValue in a Value object''' +def create_webview(html: Optional[str] = None, title: Optional[str] = None, view_column: Optional[int] = None, + preserve_focus: bool = False, enable_find_widget: bool = False, + retain_context_when_hidden: bool = False, enable_scripts: bool = False): + '''Create a webview panel. + html: HTML content to display in the webview. May be later replaced via Webview.set_html(). + title: Panel title. + view_column: Column in which to show the webview. + preserve_focus: Whether to preserve focus in the current editor when revealing the webview. + enable_find_widget: Controls if the find widget is enabled in the panel. + retain_context_when_hidden: Controls if the webview panel retains its context when it is hidden. + enable_scripts: Controls if scripts are enabled in the webview. + ''' +``` ## Webview -The Webview class provides a simplified interface to VSCode [Webview](https://code.visualstudio.com/api/references/vscode-api#WebviewPanel). - -class **Webview:** - - **set_html(html: `str`)**: Set HTML contents of the webview. - - **reveal(view_column: `int`=None, preserve_focus: `bool`=False)**: Show the webview panel in a given column. - - **post_message(message: `object`)**: Post a message to the webview content. - - **dispose()**: Destroy the webview panel - - **on_did_receive_message: `Event[object]`**: Fired when the webview content posts a message. - - **on_did_dispose: `Event`**: Fired when the webview panel is disposed (either by the user or by calling dispose()). +A simplified interface for [webview panels](https://code.visualstudio.com/api/references/vscode-api#WebviewPanel). + +```python +class Webview: + def dispose(self): + '''Destroy webview panel.''' + def set_html(self, html: str): + '''Set HTML contents of the webview.''' + def reveal(self, view_column: Optional[int] = None, preserve_focus: bool = False): + '''Show the webview panel in a given column.''' + def post_message(self, message: Any): + '''Post a message to the webview content.''' + interface.send_message(dict(message='webviewPostMessage', id=self.id, inner=message)) + @property + def on_did_receive_message(self) -> Event: + '''Fired when webview content posts a new message.''' + @property + def on_did_dispose(self) -> Event: + '''Fired when the webview panel is disposed (either by the user or by calling dispose())''' +``` ## Event -class **Event[T]:** - - **add(handler: `Callable[T]`)**: Add event listener. - - **remove(handler: `Callable[T]`)**: Remove event listener. - +```python +class Event: + def add(self, listener: Callable[[Any]]): + '''Add an event listener.''' + def remove(self, listener: Callable[[Any]]): + '''Remove an event listener.''' +``` ## Value `Value` objects ([source](adapter/scripts/codelldb/value.py)) are proxy wrappers around [`lldb.SBValue`](https://lldb.llvm.org/python_api/lldb.SBValue.html), @@ -700,19 +727,18 @@ this configuration entry: `"lldb.adapterEnv": {"LLDB_DEBUGSERVER_PATH": "`commands` - treat debug console input as debugger commands. In order to evaluate an expression, prefix it with '?' (question mark).",
  • `evaluate` - treat DEBUG CONSOLE input as expressions. In order to execute a debugger command, prefix it with '/cmd ' or '\`' (backtick),
  • `split` - (experimental) use the DEBUG CONSOLE for evaluation of expressions, open a separate terminal for LLDB console. +|**lldb.script** |Configuration settings provided to Python scripts running in the context of CodeLLDB. These may be read via [`get_config()`](#debugger-api). + ## Advanced | | | diff --git a/adapter/scripts/codelldb/api.py b/adapter/scripts/codelldb/api.py index f7e7f2b3..68938dbb 100644 --- a/adapter/scripts/codelldb/api.py +++ b/adapter/scripts/codelldb/api.py @@ -1,38 +1,58 @@ import lldb import warnings import __main__ +from typing import Any, Optional, Union from . import interface from .value import Value from .webview import Webview -def evaluate(expr, unwrap=False): +def get_config(name: str, default: Any = None) -> Any: + '''Retrieve a configuration value from the adapter settings. + name: Dot-separated path of the setting to retrieve. For example, 'foo.bar', will retrieve the value of `lldb.script.foo.bar`. + default: The default value to return if the configuration value is not found. + ''' + internal_dict = interface.get_instance_dict(lldb.debugger) + settings = internal_dict['adapter_settings'].get('scriptConfig') + for segment in name.split('.'): + if settings is None: + return default + settings = settings.get(segment) + return settings + + +def evaluate(expr: str, unwrap: bool = False) -> Union[Value, lldb.SBValue]: + '''Performs dynamic evaluation of native expressions returning instances of Value or SBValue. + expression: The expression to evaluate. + unwrap: Whether to unwrap the result and return it as lldb.SBValue + ''' value = interface.nat_eval(lldb.frame, expr) return Value.unwrap(value) if unwrap else value -def wrap(obj): +def wrap(obj: lldb.SBValue) -> Value: + '''Extracts an lldb.SBValue from Value''' return obj if type(obj) is Value else Value(obj) -def unwrap(obj): +def unwrap(obj: Value) -> lldb.SBValue: + '''Wraps lldb.SBValue in a Value object''' return Value.unwrap(obj) -def get_config(name, default=None): - internal_dict = interface.get_instance_dict(lldb.debugger) - settings = internal_dict['adapter_settings'].get('scriptConfig') - for segment in name.split('.'): - if settings is None: - return default - settings = settings.get(segment) - return settings - - -def create_webview(html=None, title=None, view_column=None, preserve_focus=False, - enable_find_widget=False, retain_context_when_hidden=False, - enable_scripts=False): +def create_webview(html: Optional[str] = None, title: Optional[str] = None, view_column: Optional[int] = None, + preserve_focus: bool = False, enable_find_widget: bool = False, + retain_context_when_hidden: bool = False, enable_scripts: bool = False): + '''Create a [webview panel](https://code.visualstudio.com/api/references/vscode-api#WebviewPanel). + html: HTML content to display in the webview. May be later replaced via Webview.set_html(). + title: Panel title. + view_column: Column in which to show the webview. + preserve_focus: Whether to preserve focus in the current editor when revealing the webview. + enable_find_widget: Controls if the find widget is enabled in the panel. + retain_context_when_hidden: Controls if the webview panel retains its context when it is hidden. + enable_scripts: Controls if scripts are enabled in the webview. + ''' webview = Webview() interface.send_message(dict(message='webviewCreate', id=webview.id, @@ -47,7 +67,10 @@ def create_webview(html=None, title=None, view_column=None, preserve_focus=False return webview -def display_html(html, title=None, position=None, reveal=False): +def display_html(html: str, title: Optional[str] = None, position: Optional[int] = None, reveal: bool = False): + '''Display HTML content in a webview panel. + display_html is **deprecated**, use create_webview instead. + ''' global html_webview if html_webview is None: warnings.warn("display_html is deprecated, use create_webview instead", DeprecationWarning) diff --git a/adapter/scripts/codelldb/event.py b/adapter/scripts/codelldb/event.py index 1598f611..a3006cfd 100644 --- a/adapter/scripts/codelldb/event.py +++ b/adapter/scripts/codelldb/event.py @@ -1,13 +1,19 @@ +from typing import Callable, Any + + class Event: def __init__(self): self._listeners = [] - def add(self, listener): + def add(self, listener: Callable[[Any], None]): + '''Add an event listener.''' self._listeners.append(listener) - def remove(self, listener): + def remove(self, listener: Callable[[Any], None]): + '''Remove an event listener.''' self._listeners.remove(listener) - def emit(self, message): + def emit(self, message: Any): + '''Notify all listeners.''' for listener in self._listeners: listener(message) diff --git a/adapter/scripts/codelldb/webview.py b/adapter/scripts/codelldb/webview.py index 7d472471..c9dc6e91 100644 --- a/adapter/scripts/codelldb/webview.py +++ b/adapter/scripts/codelldb/webview.py @@ -1,3 +1,4 @@ +from typing import Any, Optional from . import interface from .event import Event @@ -5,34 +6,50 @@ class Webview: + '''A simplified interface for [webview panels](https://code.visualstudio.com/api/references/vscode-api#WebviewPanel).''' + def __init__(self): global view_id view_id += 1 self.id = view_id - self.on_did_receive_message = Event() - self.on_did_dispose = Event() + self._on_did_receive_message = Event() + self._on_did_dispose = Event() interface.on_did_receive_message.add(self._message_handler) def _message_handler(self, message): if message.get('id', None) == self.id: message_type = message.get('message', None) if message_type == 'webviewDidReceiveMessage': - self.on_did_receive_message.emit(message.get('inner', None)) + self._on_did_receive_message.emit(message.get('inner', None)) elif message_type == 'webviewDidDispose': - self.on_did_dispose.emit(message.get('inner', None)) + self._on_did_dispose.emit(message.get('inner', None)) def __del__(self): interface.on_did_receive_message.remove(self._message_handler) def dispose(self): + '''Destroy webview panel.''' interface.send_message(dict(message='webviewDispose', id=self.id)) - def set_html(self, html): + def set_html(self, html: str): + '''Set HTML contents of the webview.''' interface.send_message(dict(message='webviewSetHtml', id=self.id, html=html)) - def reveal(self, view_column=None, preserve_focus=False): + def reveal(self, view_column: Optional[int] = None, preserve_focus: bool = False): + '''Show the webview panel in a given column.''' interface.send_message(dict(message='webviewReveal', id=self.id, - viewColumn=view_column, preserveFocus=preserve_focus)) + viewColumn=view_column, preserveFocus=preserve_focus)) - def post_message(self, message): + def post_message(self, message: Any): + '''Post a message to the webview content.''' interface.send_message(dict(message='webviewPostMessage', id=self.id, inner=message)) + + @property + def on_did_receive_message(self) -> Event: + '''Fired when webview content posts a new message.''' + return self._on_did_receive_message + + @property + def on_did_dispose(self) -> Event: + '''Fired when the webview panel is disposed (either by the user or by calling dispose())''' + return self._on_did_dispose