Skip to content

Commit

Permalink
Converted extension.js to typescript (#1772)
Browse files Browse the repository at this point in the history
* Started working on ts conversion

* Worked on ts file

* Deleted accidentally committed files

* Converted to ts and made ports and path configurable

* Edited README

* deleted extension.js
  • Loading branch information
kadenlei authored Nov 15, 2023
1 parent a93c510 commit 187abf2
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 94 deletions.
97 changes: 62 additions & 35 deletions cider-dap/README.md
Original file line number Diff line number Diff line change
@@ -1,125 +1,152 @@
# Cider Debug Adapter

## Installing the extension

Navigate to your vscode extension directory `~/.vscode/extensions` for local
installations. For WSL users, you need to use the *server's* extension folder not
installations. For WSL users, you need to use the _server's_ extension folder not
the normal installation in windows. Once in the appropriate folder create a
symlink for the extension

```
ln -s <PATH TO CALYX ROOT>/cider-dap/calyxDebug cider.cider-dap-0.0.1
```

Once vscode is reloaded, the extension should be active and viewable in the
`Cider dap` tab of the output panel. You will also need to create a symlink for
the cider-dap binary somewhere on your path. From some directory on your PATH:

```
ln -s <PATH TO CALYX ROOT>/target/debug/cider-dap
```

You can then launch the adapter with the Launch Program (Multi Session) action
You will have to configure user settings of cider-dap in VSCode and input your cider binary path and port number. You can then launch the adapter with the Launch Program (Multi Session) action

## Known issues
* The launch action can sometimes attempt a connection before the server is
ready and will cause a failure, subsequent attempts will work until the
server closes. Ideally the extension would wait until the server has fully launched.

- The launch action can sometimes attempt a connection before the server is
ready and will cause a failure, subsequent attempts will work until the
server closes. Ideally the extension would wait until the server has fully launched.

## Project Overview:

Cider-dap is a debug adapter protocol implementation using Rust, which aids in debugging processes. It interfaces with IDEs or other tools using the Debug Adapter Protocol.
This project primarily leverages the Debug Adapter Protocol (DAP) for its functionality. The structure is organized into different directories and files which encapsulate the functionalities:
<br>
<br> 1.`cider-dap` directory: The main directory which contains the following sub-directories and files:
<br>
1.``` cider-dap ``` directory: The main directory which contains the following sub-directories and files:
<br>
&nbsp; &nbsp; &nbsp; &nbsp; ```calyxDebug```: Contains the file responsible for debugging extensions and related utilities. So it is a dedicated directory for VSCode debugging extensions. It establishes the bridge between your Rust codebase and the VSCode debugging environment. <br>
&nbsp; &nbsp; &nbsp; &nbsp; ```src```: Houses the Rust source files for the project. It contains the project's core functionalities, logic, and structures. <br>
&nbsp; &nbsp; &nbsp; &nbsp; ```cargo.lock``` & ```cargo.toml```: Standard Rust project files detailing dependencies and project metadata. <br>
3. ```src``` directory: <br>
&nbsp; &nbsp; &nbsp; &nbsp; ```adapter.rs```: Defines the primary adapter structure for the project and its associated functionalities. Not just any adapter, this file structures the fundamental protocols, handling the translation of high-level debugging commands into actionable, low-level instructions. <br>
&nbsp; &nbsp; &nbsp; &nbsp; ```error.rs```: Contains custom error definitions and types for better error handling. <br>
&nbsp; &nbsp; &nbsp; &nbsp; ```main.rs```: The entry point for the project, it integrates functionalities from the other source files and provides the main execution logic. <br>
4. ```calyxDebug``` directory: <br>
&nbsp; &nbsp; &nbsp; &nbsp; ```extension.js```: JavaScript file for VSCode extension integration. It provides functions to interface between the VSCode environment and the Rust backend. <br>
&nbsp; &nbsp; &nbsp; &nbsp; `calyxDebug`: Contains the file responsible for debugging extensions and related utilities. So it is a dedicated directory for VSCode debugging extensions. It establishes the bridge between your Rust codebase and the VSCode debugging environment. <br>
&nbsp; &nbsp; &nbsp; &nbsp; `src`: Houses the Rust source files for the project. It contains the project's core functionalities, logic, and structures. <br>
&nbsp; &nbsp; &nbsp; &nbsp; `cargo.lock` & `cargo.toml`: Standard Rust project files detailing dependencies and project metadata. <br> 3. `src` directory: <br>
&nbsp; &nbsp; &nbsp; &nbsp; `adapter.rs`: Defines the primary adapter structure for the project and its associated functionalities. Not just any adapter, this file structures the fundamental protocols, handling the translation of high-level debugging commands into actionable, low-level instructions. <br>
&nbsp; &nbsp; &nbsp; &nbsp; `error.rs`: Contains custom error definitions and types for better error handling. <br>
&nbsp; &nbsp; &nbsp; &nbsp; `main.rs`: The entry point for the project, it integrates functionalities from the other source files and provides the main execution logic. <br> 4. `calyxDebug` directory: <br>
&nbsp; &nbsp; &nbsp; &nbsp; `extension.js`: JavaScript file for VSCode extension integration. It provides functions to interface between the VSCode environment and the Rust backend. <br>

### About main.rs: the Main File
In ``` main.rs ```, our program is set up to accommodate both single and multi-session debugging. It defines the entry point of our application and orchestrates how the server is run based on user-provided arguments.

In `main.rs`, our program is set up to accommodate both single and multi-session debugging. It defines the entry point of our application and orchestrates how the server is run based on user-provided arguments.

#### Initialization:
At the start of the ```main()``` function:

At the start of the `main()` function:

- The Opts struct captures command-line arguments. This struct contains an optional file path, a switch to determine if the application runs in multi-session mode, and a port number (with a default of 8080).
- ```argh::from_env()``` processes the command-line arguments based on the defined struct. The use of argh simplifies command-line parsing, allowing you to focus on the main logic without getting bogged down in argument processing.
- `argh::from_env()` processes the command-line arguments based on the defined struct. The use of argh simplifies command-line parsing, allowing you to focus on the main logic without getting bogged down in argument processing.

#### Single vs. Multi-Session Decision:
Depending on whether the ```is_multi_session flag``` is set:

Depending on whether the `is_multi_session flag` is set:

##### Multi-Session:

- &nbsp; &nbsp; &nbsp; &nbsp; A TCP listener is set up, which will listen for incoming debugger connections.
- &nbsp; &nbsp; &nbsp; &nbsp; On accepting a connection, the streams are buffered for efficient I/O operations.
- &nbsp; &nbsp; &nbsp; &nbsp; The multi_session_init function gets the adapter configured for the session, handling initial handshakes like the Initialize and Launch commands.
- &nbsp; &nbsp; &nbsp; &nbsp; The run_server function then takes over, orchestrating the actual debugging session with the given adapter.

##### Single-Session:

- &nbsp; &nbsp; &nbsp; &nbsp; Directly reads from standard input and writes to standard output.
- &nbsp; &nbsp; &nbsp; &nbsp; Instead of expecting and processing initial handshakes, the function simply sets up the adapter with the provided file and begins the server loop.

This dual mode is valuable: the single-session approach allows for streamlined debugging in local environments or for simpler setups, while the multi-session setup allows for more advanced scenarios, perhaps remote debugging or handling multiple debugger sessions.

#### ```multi_session_init```
#### `multi_session_init`

This function sets up an adapter for a multi-session environment. Here's a step-by-step breakdown:

##### 1. Initial Handshake:

- It first waits for and processes an Initialize request. This is fundamental in the DAP as it establishes the initial connection between the debugger client and server.
- After successfully processing this request, it sends an Initialized event to notify the client that the server is ready for subsequent commands.

##### 2. Setup:

- The next expected command is a Launch command. This command contains additional information (like the path to the program being debugged). This path is extracted and checked for validity.
- The program is then opened and used to construct the MyAdapter instance.
The purpose of this function is to perform the initial setup necessary to start a debugging session. By separating it from the run_server function, the code remains modular, allowing for easier debugging, testing, and modification.
The purpose of this function is to perform the initial setup necessary to start a debugging session. By separating it from the run_server function, the code remains modular, allowing for easier debugging, testing, and modification.

#### <font size="3"> run_server </font> :

#### <font size="3"> run_server </font> :
The heart of the debugger's runtime:

##### <font size="3"> Core Loop </font>:

The function continuously polls for requests from the client.

- Upon receiving a Launch command, it sends a successful response back to the client. This indicates that the server is ready to begin debugging.
- The loop can be expanded to handle other DAP commands as needed. For example, handling a Disconnect command could cleanly terminate the loop and close the server.

##### Command Processing:

- The only command being actively handled right now is the Launch command. Upon receiving this command, the server simply responds with a success message, indicating that it's ready to commence debugging.
- The loop is designed with extensibility in mind. Comments suggest places where commands like Disconnect can be incorporated to handle disconnection events, allowing the server to terminate gracefully.

##### <font size="3"> Error Handling </font>:

- If an unknown command is received, it's printed to the error output and the server terminates with an UnhandledCommandError.
- This is a robust approach, ensuring that only expected commands are processed and any anomalies are immediately flagged.

### Dependencies

The following dependencies have been added to the project as specified in the cargo.toml:
<br>
- ```dap```: Rust DAP implementation. At its core, this Rust DAP implementation is what powers cider-dap. It's the backbone that ensures all debugging actions are in line with the protocol's standards. <br>
- ```thiserror```: Used for ergonomic error handling. Enhancing error handling by providing more contextual feedback and streamlined debugging. <br>
- ```serde_json``` & ```serde```: Serialization and deserialization of data. Essential for data communication. They ensure that data structures are efficiently serialized and deserialized between different parts of the system. <br>
- ```owo-colors```: For colored console output. So it elevates user experience by introducing color-coded outputs, making console interactions more intuitive. <br>
- ```argh```: For command line argument parsing. It simplifies command line interactions, ensuring that user inputs are effectively parsed and processed. <br>

- `dap`: Rust DAP implementation. At its core, this Rust DAP implementation is what powers cider-dap. It's the backbone that ensures all debugging actions are in line with the protocol's standards. <br>
- `thiserror`: Used for ergonomic error handling. Enhancing error handling by providing more contextual feedback and streamlined debugging. <br>
- `serde_json` & `serde`: Serialization and deserialization of data. Essential for data communication. They ensure that data structures are efficiently serialized and deserialized between different parts of the system. <br>
- `owo-colors`: For colored console output. So it elevates user experience by introducing color-coded outputs, making console interactions more intuitive. <br>
- `argh`: For command line argument parsing. It simplifies command line interactions, ensuring that user inputs are effectively parsed and processed. <br>

### Running the Project

1. Ensure you have the necessary dependencies installed. If not, you can install them using cargo:
```cargo install ```
3. To run the main project:
```cargo run ```
`cargo install `
2. To run the main project:
`cargo run `

### Next Steps

1. Advanced Error Handling: Utilize the structures in error.rs to provide detailed insights, potentially integrating with external error databases or logs.
2. Command Enhancements: Augment the DAP commands and responses in main.rs, exploring beyond traditional debugging actions.
3. There are changes needed to be done inside run_server:

### Additional Command Handling:

- Incorporate command handlers for other DAP commands:
- ```Disconnect ```: Handle disconnect commands gracefully, ensuring any necessary cleanup is done before closing the server.
- ```Breakpoint ```: Implement functionality to pause execution at specific points.
- ```StepOver ```, ```StepInto ```, ```StepOut ```: Allow fine-grained control over the debugging process, allowing users to inspect code step-by-step.
- ```Evaluate ```: Handle evaluation requests from the debugger, returning values as needed.
- `Disconnect `: Handle disconnect commands gracefully, ensuring any necessary cleanup is done before closing the server.
- `Breakpoint `: Implement functionality to pause execution at specific points.
- `StepOver `, `StepInto `, `StepOut `: Allow fine-grained control over the debugging process, allowing users to inspect code step-by-step.
- `Evaluate `: Handle evaluation requests from the debugger, returning values as needed.

### Refined Error Handling:

- Instead of immediate termination on an unknown command, consider logging these events or sending specific error messages back to the client. This provides a more user-friendly debugging experience.

### Enhanced Logging:

- Implement more detailed logging to provide insights into server operations. This would be especially useful in identifying issues or understanding the flow of commands and responses.

### Asynchronous Processing:

- Consider incorporating asynchronous command processing. This would allow the server to handle multiple requests concurrently, offering faster response times and smoother user experiences, especially in complex debugging scenarios.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const vscode = require('vscode');
const cp = require('child_process');
const net = require('net');
import * as vscode from "vscode";
import cp = require("child_process");
import net = require("net");

// Hold the debug adapter instance
let debugAdapter = null;
Expand All @@ -16,14 +16,18 @@ function logToPanel(message) {
// Function to get the program name from the user
async function getProgramName() {
const fileName = await vscode.window.showInputBox({
placeHolder: 'Please enter the name of a futil file in the workspace folder',
value: 'default.futil'
placeHolder:
"Please enter the name of a futil file in the workspace folder",
value: "default.futil",
});

if (fileName) {
if (!fileName.startsWith('/')) {
const path = require('path');
return path.join(vscode.workspace.workspaceFolders[0].uri.fsPath, fileName);
if (!fileName.startsWith("/")) {
const path = require("path");
return path.join(
vscode.workspace.workspaceFolders[0].uri.fsPath,
fileName
);
}
return fileName;
} else {
Expand All @@ -32,6 +36,11 @@ async function getProgramName() {
}

class CiderDebugAdapterDescriptorFactory {
adapter: CiderDebugAdapter;
adapterPath: string;
workspace: string;
outputChannel: object;

constructor(adapterPath, workspace, outputChannel) {
logToPanel("inside constructor");
this.adapter = new CiderDebugAdapter(adapterPath, workspace, outputChannel);
Expand All @@ -49,8 +58,8 @@ class CiderDebugAdapterDescriptorFactory {

_startDebugServer(session) {
logToPanel("start of startDebugServer");
const port = 8888; // This is the default value

// default port: 8888
const port = vscode.workspace.getConfiguration("cider-dap").port;
if (!this.adapter.isServerRunning()) {
logToPanel("server is not running");
this.adapter.start(port);
Expand All @@ -62,6 +71,12 @@ class CiderDebugAdapterDescriptorFactory {
}
}
class CiderDebugAdapter {
adapterPath: string;
outputChannel: object;
cwd: string;
adapterProcess: cp.ChildProcessWithoutNullStreams | null;
isRunning: boolean;

constructor(adapterPath, cwd, outputChannel) {
logToPanel("inside CiderDebugAdapter");
this.adapterPath = adapterPath;
Expand All @@ -76,61 +91,70 @@ class CiderDebugAdapter {
}
// Start the debug adapter process
start(port) {
logToPanel('beginning of start');
logToPanel("beginning of start");

// Spawn a new child process for the debug adapter
// Include the port as a command line argument
this.adapterProcess = cp.spawn(this.adapterPath, [programName, '--port', port, "--tcp"], { cwd: this.cwd });
this.adapterProcess = cp.spawn(
this.adapterPath,
[programName, "--port", port, "--tcp"],
{ cwd: this.cwd }
);

// Attach event listener to capture standard output of the adapter process and log it to the output channel
this.adapterProcess.stdout.on('data', (data) => {
this.adapterProcess.stdout.on("data", (data) => {
logToPanel(data.toString());
});

// Attach event listener to capture standard error of the adapter process and log it to the output channel
this.adapterProcess.stderr.on('data', (data) => {
this.adapterProcess.stderr.on("data", (data) => {
logToPanel(data.toString());
});

this.adapterProcess.on('spawn', () => {
logToPanel('Debugger started on port ' + port + '!');
this.adapterProcess.on("spawn", () => {
logToPanel("Debugger started on port " + port + "!");
});

}

stop() {
if (this.adapterProcess) {
this.adapterProcess.kill();
this.adapterProcess = null;
this.isRunning = false;
logToPanel('Debugger stopped.');
logToPanel("Debugger stopped.");
} else {
logToPanel('No running debug adapter to stop.');
logToPanel("No running debug adapter to stop.");
}
}
}

function activate(context) {
logToPanel('Extension activated!');
logToPanel("Extension activated!");

// Start the debug server explicitly
const factory = new CiderDebugAdapterDescriptorFactory('cider-dap', vscode.workspace.rootPath, outputChannel);
context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('cider-dap', factory));
const factory = new CiderDebugAdapterDescriptorFactory(
vscode.workspace.getConfiguration("cider-dap").path,
vscode.workspace.rootPath,
outputChannel
);
context.subscriptions.push(
vscode.debug.registerDebugAdapterDescriptorFactory("cider-dap", factory)
);
logToPanel("after start server");

// Update the adapter path with the serverPort and use it for starting the debug adapter
logToPanel("before startDebugging");
/* context.subscriptions.push(vscode.commands.registerCommand('cider.startDebugging', startDebugging));
context.subscriptions.push(vscode.commands.registerCommand('cider.stopDebugging', stopDebugging));
*/
logToPanel('Hello, your extension is now activated!');
logToPanel("Hello, your extension is now activated!");
}

function stopDebugging() {
if (debugAdapter) {
debugAdapter.stop();
} else {
logToPanel('No running debug adapter to stop.');
logToPanel("No running debug adapter to stop.");
}
}

Expand All @@ -140,5 +164,5 @@ function deactivate() {

module.exports = {
activate,
deactivate
deactivate,
};
Loading

0 comments on commit 187abf2

Please sign in to comment.