Skip to content

Commit 8b714f4

Browse files
committed
Update sess/README.md and implement document_close on client
1 parent 2dc1aaf commit 8b714f4

3 files changed

Lines changed: 42 additions & 47 deletions

File tree

sess/README.md

Lines changed: 23 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,22 @@ The following methods are sent as notifications from R to the client:
4040
- **`plot_updated`**: Notifies that a new static plot is available. The client should request the `plot_latest` method via HTTP.
4141
- **`httpgd`**: Provides a URL for an `httpgd` live plot server (params: `url`).
4242
- **`help`**: Requests the client to display an R help page (params: `requestPath`).
43-
- **`browser`**: Requests the client to open a URL (params: `url`, `title`).
44-
- **`webview`**: Requests the client to open a local HTML file or URL in a webview (params: `file`, `title`).
43+
- **`browser`**: Requests the client to open a URL (params: `url`, `title`, `viewer`).
44+
- **`webview`**: Requests the client to open a local HTML file or URL in a webview (params: `file`, `title`, `viewer`).
4545
- **`restart_r`**: Requests the client to restart the R session (params: `command`, `clean`).
4646
- **`send_to_console`**: Sends code to the console for execution without blocking the R session (params: `code`, `execute`, `focus`, `animate`).
4747

4848
#### 2. Synchronous Client Requests (`request_client`)
4949

50-
The `request_client()` function allows R to call client-side functions synchronously by sending a **JSON-RPC Request** (with an `id`) over the WebSocket. This is primarily used to emulate the RStudio API:
50+
The `request_client()` function allows R to call client-side functions synchronously by sending a **JSON-RPC Request** (with an `id`) over the WebSocket. This is primarily used to emulate the RStudio API.
5151

52-
1. R sends a request with `method` set to the action name (e.g., `"active_editor_context"`).
53-
2. R enters a `while` loop that calls `httpuv::service()`.
54-
3. The client processes the action and sends back a **JSON-RPC Response** (with the same id) via the WebSocket.
55-
4. R retrieves the `result` (or `error`) and returns it.
52+
**Coordinate Handling**: The `sess` protocol uses **1-indexed** coordinates for all rows (lines) and columns (characters) on the wire. This aligns with R's internal representation. The client (e.g., VS Code extension) is responsible for converting these to its internal 0-indexed representation if necessary.
5653

57-
**Coordinate Handling**: The emulation layer automatically converts between R (1-indexed) and IPC (0-indexed) coordinates. Locations and ranges sent to the client are 0-indexed, while data received from the client (e.g., in `document_context`) is converted back to 1-indexed R objects.
54+
**Serialization Format**:
55+
- **Position**: A numeric array `[row, column]`.
56+
- **Range**: An object `{ "start": [row, column], "end": [row, column] }`.
5857

59-
Below are all the JSON-RPC methods sent from R to the client to emulate RStudio API functionality:
58+
Below are the JSON-RPC methods sent from R to the client to emulate RStudio API functionality:
6059

6160
- **`active_editor_context`**: Requests the current context of the active editor.
6261
- **`replace_text_in_current_selection`**: Replaces text in the current selection (params: `text`, `id`).
@@ -129,15 +128,21 @@ Returns the most recent static plot captured by the R session.
129128

130129
---
131130

132-
## 3. Hook Registration
131+
## 3. Hook Registration & Options
133132

134133
By default, the package does not inject hooks into the R session on load. Calling `sess::sess_app()` will start the server and automatically call `sess::register_hooks()` to enable features like automatic `View()` interception or plot redirection.
135134

136-
If you need to manually register hooks without starting the full app environment (or re-register them if they were overridden), you can call:
137-
```r
138-
sess::register_hooks()
139-
```
140-
This rebinds internal functions (like `utils::View`) and sets `options()` for browsers, viewers, and devices to route through the IPC server.
135+
### Intercepted Functions
136+
- **`utils::View()`**: Redirects data to the client's data viewer. Supports `data.frame`, `matrix`, `list`, and `ArrowTabular` objects.
137+
- **`browser()`**, **`viewer()`**, **`page_viewer()`**: Redirects URLs and HTML files to the client's browser or webview.
138+
- **Help System**: Intercepts help topic printing to route HTML help to the client.
139+
140+
### Global Options
141+
- **`sess.row_limit`**: Limits the number of rows sent to the data viewer (default: 100). Set to 0 for no limit.
142+
- **`sess.dataview`**: Target viewer column for data (default: `"Two"`).
143+
- **`sess.browser`**: Target viewer for browser (default: `"Active"`).
144+
- **`sess.webview`**: Target viewer for webview (default: `"Two"`).
145+
- **`sess.helpPanel`**: Target viewer for help (default: `"Two"`).
141146

142147
## 4. Comparison with Legacy IPC
143148

@@ -153,37 +158,8 @@ The `sess` package replaces the legacy file-based IPC mechanism with a modern, i
153158
| **Transport Reliability**| OS-level File System Watchers | **WebSocket & HTTP streams** |
154159
| **Protocol Standard** | Ad-hoc JSON formats | **JSON-RPC 2.0** |
155160

156-
### Detailed Mapping: Legacy to Modern
157-
158-
**1. Command Dispatch (e.g., `View()`, `browser()`, `help()`)**
159-
- **Old Approach:** R intercepted commands and appended custom JSON structures to a `request.log` file, then updated the timestamp on a `request.lock` file. The client OS file watcher detected the `.lock` file change, read the `.log` file, and processed the pending commands.
160-
- **New Approach (`sess`):** R directly pushes an instantaneous **JSON-RPC Notification** (e.g., `method: "dataview"`, `method: "help"`) over the persistent WebSocket connection. The client receives and processes the payload instantly, bypassing disk I/O and file system watchers entirely.
161-
162-
**2. Synchronous RStudio API Emulation**
163-
- **Old Approach:** R wrote a command to `request.log`, and then entered a blocking `while` loop aggressively polling for the creation of a `response.lock` file by the client.
164-
- **New Approach (`sess`):** R sends a **JSON-RPC Request** over the WebSocket with an `id`. It enters a `while` loop calling `httpuv::service()`, maintaining a responsive background state, and waits for a corresponding **JSON-RPC Response** with the matching `id` over the same WebSocket.
165-
166-
**3. Workspace State (Global Environment)**
167-
- **Old Approach:** An R task callback ran after every top-level console execution, eagerly evaluating and serializing the entire Global Environment to a `workspace.json` file, followed by touching a `workspace.lock` file. The client watched for the lock file change to read the JSON file. This caused constant overhead and disk writes, even when the client's workspace pane was hidden.
168-
- **New Approach (`sess`):** Adopts a "Pull" architecture. The workspace is *only* evaluated and serialized when the client explicitly sends an **HTTP POST Request** to `/rpc` with `method: "workspace"`. This happens on-demand (e.g., when the UI pane is visible), saving significant R processing time and disk I/O.
169-
170-
**4. Static Plots**
171-
- **Old Approach:** Plotting commands (via custom devices or hooks) generated a `plot.png` file on disk and updated a `plot.lock` file. The client watcher noticed the lock change, read the new PNG file from disk, and displayed it.
172-
- **New Approach (`sess`):** When a new plot is generated to a temporary file, R sends a lightweight **JSON-RPC Notification** (`method: "plot_updated"`) via the WebSocket. The client then pulls the actual image data by sending an **HTTP POST Request** to `/rpc` (`method: "plot_latest"`), which returns the base64-encoded image over the network stream.
173-
174-
**5. Client Queries (Hover, Completion)**
175-
- **Old Approach:** R ran an internal `httpuv` server using custom JSON structures and ad-hoc request types (like `{ "type": "hover" }`).
176-
- **New Approach (`sess`):** Unified under the **JSON-RPC 2.0** standard. The client sends structured requests with strict `id` and `params` formatting to the single `/rpc` HTTP endpoint, receiving standardized JSON-RPC responses.
177-
178161
### Architectural Shifts
179162

180-
1. **Elimination of File Watchers**: The legacy system relied heavily on OS-level file system watchers (`fs.watch`) monitoring lock files to trigger client updates. This approach could be unreliable or slow across different platforms, network drives, and remote container environments. `sess` replaces this entirely with persistent WebSocket connections for instantaneous, reliable event pushing.
181-
2. **On-Demand vs. Eager Evaluation**: Previously, R would eagerly evaluate and serialize the entire Global Environment to `workspace.json` frequently (e.g., via task callbacks), incurring significant continuous disk I/O and processing overhead. `sess` shifts this to a "Pull" model. The client requests the workspace state only when needed, significantly reducing R's background workload.
182-
3. **Unified Standard**: Instead of maintaining separate, disparate mechanisms for command logs, lock-file polling loops, JSON dumps, and custom HTTP query payloads, `sess` unifies all structured communication under the ubiquitous **JSON-RPC 2.0** standard across WebSocket and HTTP transports.
183-
184-
### Responsiveness and the Event Loop
185-
186-
Because R is fundamentally single-threaded, both IPC mechanisms are constrained by the R event loop, but they surface this limitation differently to the client:
187-
188-
- **The "Busy" State**: If R is executing a long-running computation, the `httpuv` server running inside the session cannot process incoming HTTP requests (such as a request for `/rpc` `workspace` or `completion`).
189-
- **Active vs. Passive Waiting**: In the legacy system, the client passively waited for `workspace.lock` to change, effectively ignoring the busy state until the operation completed. With the `sess` HTTP RPC model, the client actively requests data. Therefore, the client *must* implement short, aggressive timeouts (e.g., 500ms) for these HTTP requests. If a timeout occurs, the client gracefully interprets this as "R is busy" and can display a loading state without locking up the IDE's UI thread.
163+
1. **Elimination of File Watchers**: Replaces unreliable OS-level file system watchers with persistent WebSocket connections for instantaneous event pushing.
164+
2. **On-Demand Evaluation**: Evaluations of the Global Environment are now performed only when requested by the client ("Pull" model), reducing R's background workload.
165+
3. **Unified Standard**: Unifies all structured communication under the **JSON-RPC 2.0** standard across WebSocket and HTTP transports.

src/rstudioapi.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,21 @@ export async function documentSaveAll(): Promise<void> {
153153
await workspace.saveAll();
154154
}
155155

156+
export async function documentClose(id: string, save: boolean): Promise<void> {
157+
const target = findTargetUri(id);
158+
const targetDocument = await workspace.openTextDocument(target);
159+
if (save) {
160+
await targetDocument.save();
161+
}
162+
// VS Code doesn't have a direct "close document" API that takes a Document.
163+
// We have to show it and then execute the close editor command,
164+
// or use more complex workbench commands.
165+
const editor = await window.showTextDocument(targetDocument, { preserveFocus: true, preview: true });
166+
if (editor) {
167+
await commands.executeCommand('workbench.action.closeActiveEditor');
168+
}
169+
}
170+
156171
// TODO: very similar to ./utils.getCurrentWorkspaceFolder()
157172
export function projectPath(): { path: string | undefined; } {
158173

src/session.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,10 @@ async function handleRequest(message: Record<string, unknown>, ws: WebSocket) {
936936
await rstudioapi.documentNew(String(params.text), String(params.type), params.position as number[]);
937937
result = true;
938938
break;
939+
case 'document_close':
940+
await rstudioapi.documentClose(String(params.id), Boolean(params.save));
941+
result = true;
942+
break;
939943
default:
940944
throw new Error(`Unsupported method: ${method}`);
941945
}

0 commit comments

Comments
 (0)