Skip to content

Commit 0334186

Browse files
nstrayerjuliasilge
andauthored
API docs (#90)
Add docs for Positron API along with the accompanying npm types and utilities package. --------- Co-authored-by: Julia Silge <julia.silge@gmail.com>
1 parent 1ff9606 commit 0334186

File tree

1 file changed

+367
-7
lines changed

1 file changed

+367
-7
lines changed

extension-development.qmd

Lines changed: 367 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,119 @@
22
title: "Extension Development"
33
---
44

5-
Positron is compatible with VS Code extensions so you can create extensions [as you would for VS Code](https://code.visualstudio.com/api/get-started/your-first-extension). You can use Positron to develop your extension and run it in a new **Extension Development Host** window.
5+
Positron is compatible with VS Code extensions so you can create extensions [as you would for VS Code](https://code.visualstudio.com/api/get-started/your-first-extension). You can use Positron to develop your extension and run it in a new **Extension Development Host** window.
66

7-
## Context keys
7+
## Prerequisites
88

9-
When defining your extension's manifest, you can use the `isPositron` context key for enablement or in a `when` clause.
9+
To follow these docs and run the examples, you'll need:
10+
11+
- Basic familiarity with TypeScript/JavaScript and npm
12+
- An up-to-date version of Positron installed
13+
- Node.js (version 18 or greater)
14+
15+
These docs use the [@posit-dev/positron](https://www.npmjs.com/package/@posit-dev/positron) npm package for type safety and IntelliSense.
16+
17+
The easiest way to follow along is to download the [Positron extension template](https://github.com/posit-dev/positron-extension-template) and add the examples to it as needed.
18+
19+
For more comprehensive setup instructions see the [VS Code documentation on developing an extension.](https://code.visualstudio.com/api/get-started/your-first-extension)
20+
21+
## What can you build?
22+
23+
The Positron API provides access to Positron-specific features like running code in active R or Python sessions, utilizing Positron's preview pane, or contributing new database connections.
24+
25+
The following are the different sections of the API and how they're used.
26+
27+
| Namespace | Typical use case |
28+
| ---------------------- | ------------------------------------------------------------------------ |
29+
| `positron.window` | Preview URLs and HTML, monitor console layout |
30+
| `positron.runtime` | Execute Python or R code in the active console, enumerate sessions, inspect variables |
31+
| `positron.connections` | Add database / service drivers with custom credential UI |
32+
| `positron.languages` | Provide statement range & help topic providers for new languages |
33+
| `positron.methods` | Call low-level RPCs or show purpose-built dialogs |
34+
| `positron.environment` | Inspect environment variable contributions from all installed extensions |
35+
36+
37+
38+
## Setting up your extension
39+
40+
The easiest way to start an extension is to use the [Positron extension template](https://github.com/posit-dev/positron-extension-template).
41+
42+
Alternatively, you can use VS Code's [extension template system](https://code.visualstudio.com/api/get-started/your-first-extension) to scaffold out a plain VS Code extension and then add Positron functionality to that by installing the Positron npm package.
43+
44+
```bash
45+
npm install @posit-dev/positron
46+
```
47+
48+
### Key files
49+
There are two files that are critical when setting up your extension.
50+
51+
#### Extension script (`extension.ts`)
52+
53+
54+
```typescript
55+
import * as vscode from 'vscode';
56+
import { tryAcquirePositronApi } from '@posit-dev/positron';
57+
58+
export function activate(context: vscode.ExtensionContext) {
59+
// Register a command
60+
const disposable = vscode.commands.registerCommand('myExtension.helloWorld', () => {
61+
const positron = tryAcquirePositronApi();
62+
63+
if (positron) {
64+
vscode.window.showInformationMessage('Hello from Positron!');
65+
// Use Positron features here
66+
} else {
67+
vscode.window.showInformationMessage('Hello from VS Code!');
68+
}
69+
});
70+
71+
context.subscriptions.push(disposable);
72+
}
73+
74+
export function deactivate() {}
75+
```
76+
77+
The `activate()` function is run when your extension is [activated.](https://code.visualstudio.com/api/references/activation-events)
78+
It's where you setup the code behind commands and other extension contributions.
79+
80+
#### Extension manifest (`package.json`)
81+
82+
```json
83+
{
84+
"name": "my-extension",
85+
"displayName": "My Extension",
86+
"version": "0.0.1",
87+
"engines": {
88+
"vscode": "^1.74.0"
89+
},
90+
"activationEvents": [],
91+
"main": "./out/extension.js",
92+
"contributes": {
93+
"commands": [
94+
{
95+
"command": "myExtension.helloWorld",
96+
"title": "Hello World"
97+
}
98+
]
99+
},
100+
"dependencies": {
101+
"@posit-dev/positron": "^0.1.0"
102+
}
103+
}
104+
```
105+
106+
Your extension's `package.json` file uses custom extension fields to declare what your extension contributes. It's important to note that you need to add the contribution (in this case the command `myExtension.helloWorld`) to _both_ the Typescript/Javascript code and the `package.json`.
107+
108+
Now users can run your command from the **Command Palette** (<kbd>Cmd/Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd>).
109+
110+
111+
### Detecting Positron
112+
113+
You have two ways to detect if your extension is running in Positron:
114+
115+
#### Option 1: Context keys
116+
117+
Use the `isPositron` context key in your extension manifest for simple detection:
10118

11119
```json
12120
"commands": [
@@ -19,10 +127,262 @@ When defining your extension's manifest, you can use the `isPositron` context ke
19127
]
20128
```
21129

22-
This allows your extension to enable commands, keybindings, menu items, and any other contribution points only for Positron.
130+
This enables commands, keybindings, and menu items to be visible _only_ in Positron.
131+
132+
#### Option 2: Positron API
133+
134+
Detect Positron in your code:
135+
136+
```typescript
137+
import { tryAcquirePositronApi } from '@posit-dev/positron';
138+
139+
const positron = tryAcquirePositronApi();
140+
if (positron) {
141+
// Positron features are available with full type safety
142+
}
143+
```
144+
145+
Alternatively, if you just need to check but don't need the API you can use `inPositron()`:
146+
147+
```typescript
148+
import { inPositron } from '@posit-dev/positron';
149+
150+
if (inPositron()) {
151+
// Behavior specific for Positron
152+
}
153+
```
154+
155+
156+
157+
## Examples
158+
159+
Each example shows how to use a Positron feature inside a command.
160+
161+
:::{.callout-tip}
162+
Prefer to look at examples in a full extension context? Check out the [Positron API showcase extension.](https://github.com/posit-dev/positron-api-showcase)
163+
:::
164+
165+
### Display a web page
166+
167+
```typescript
168+
const disposable = vscode.commands.registerCommand('myExtension.showWebPage', () => {
169+
const positron = tryAcquirePositronApi();
170+
if (positron) {
171+
const url = vscode.Uri.parse('https://example.com');
172+
positron.window.previewUrl(url);
173+
}
174+
});
175+
```
176+
177+
Add this command to your package.json:
178+
179+
```json
180+
{
181+
"command": "myExtension.showWebPage",
182+
"title": "Show Web Page"
183+
}
184+
```
185+
186+
### Run code in the console
187+
188+
Execute code in the active Python or R session:
189+
190+
```typescript
191+
const disposable = vscode.commands.registerCommand('myExtension.runCode', () => {
192+
const positron = tryAcquirePositronApi();
193+
if (positron) {
194+
// Run Python code
195+
positron.runtime.executeCode('python', 'print("Hello from extension!")', true);
196+
197+
// Or run R code
198+
// positron.runtime.executeCode('r', 'print("Hello from R")', true);
199+
}
200+
});
201+
```
202+
203+
The third parameter (`true`) means the code appears in the console history.
204+
205+
### Add a database connection
206+
207+
Register a custom database type with a working mock example:
208+
209+
```typescript
210+
// In your activate function
211+
export function activate(context: vscode.ExtensionContext) {
212+
const positron = tryAcquirePositronApi();
213+
if (positron) {
214+
const disposable = positron.connections.registerConnectionDriver({
215+
driverId: 'mockdb',
216+
metadata: {
217+
languageId: 'python',
218+
name: 'Mock Database',
219+
inputs: [
220+
{
221+
id: 'database',
222+
label: 'Database Name',
223+
type: 'string',
224+
value: 'test_db'
225+
}
226+
]
227+
},
228+
generateCode: (inputs) => {
229+
// If any of the inputs are an ID, use that as the name
230+
const dbName = inputs.find(i => i.id === 'database')?.value || 'test_db';
231+
return mockDatabaseCode(dbName);
232+
},
233+
connect: async (code) => {
234+
await positron.runtime.executeCode('python', code, true);
235+
}
236+
});
237+
context.subscriptions.push(disposable);
238+
}
239+
}
240+
```
241+
242+
<details>
243+
<summary>Mock database code</summary>
244+
245+
In this example we simulate a database connection with a basic Python class. Swap this logic out for your use case!
246+
247+
```typescript
248+
// Mock database code generator.
249+
const mockDatabaseCode = (dbName: string) => `
250+
# Mock database connection
251+
class MockDatabase:
252+
def __init__(self, name):
253+
self.name = name
254+
self.tables = ['users', 'products', 'orders']
255+
print(f"Connected to mock database: {name}")
256+
print(f"Available tables: {', '.join(self.tables)}")
257+
258+
def query(self, sql):
259+
return f"Mock result for: {sql}"
260+
261+
# Create connection
262+
mock_db = MockDatabase("${dbName}")
263+
print("\\nConnection successful! Try: mock_db.query('SELECT * FROM users')")
264+
`;
265+
266+
```
267+
The `mockDatabaseCode` function generates Python code that creates a simple mock database class. This lets you test the connection without installing a real database.
268+
269+
</details>
270+
271+
272+
### Advanced examples
273+
274+
These examples demonstrate more advanced integrations with Positron's data science features:
275+
276+
#### Stream output with observers
277+
278+
Capture and process output from long-running computations as they execute:
279+
280+
```typescript
281+
import * as vscode from "vscode";
282+
283+
export async function runLongTask(code: string) {
284+
const positron = tryAcquirePositronApi();
285+
if (!positron) return;
286+
287+
// Create an observer to handle output events
288+
const observer = {
289+
onStarted: () => vscode.window.showInformationMessage("Analysis started"),
290+
onOutput: (message) => {
291+
// Process output chunks as they arrive
292+
console.log("Output:", message.text || message);
293+
},
294+
onError: (error) => {
295+
console.error("Runtime error:", error);
296+
vscode.window.showErrorMessage(`Error: ${error.message}`);
297+
},
298+
onFinished: () => vscode.window.showInformationMessage("Analysis complete"),
299+
};
300+
301+
// Execute code with streaming output
302+
await positron.runtime.executeCode(
303+
"python",
304+
code,
305+
true, // show in console
306+
false, // not an evaluation
307+
undefined,
308+
undefined,
309+
observer
310+
);
311+
}
312+
```
313+
314+
Use observers to process output progressively without blocking the UI. This approach works especially well for monitoring long-running analyses or streaming results.
315+
316+
## Best practices
317+
318+
### Performance
319+
* Batch API calls when possible; use `getSessionVariables()` instead of multiple individual lookups.
320+
* All API methods are asynchronous; always use `await` or handle promises appropriately.
321+
322+
### Testing
323+
* The [Positron extension template](https://github.com/posit-dev/positron-extension-template) has simple testing infrastructure setup for you that can be extended to fit your extension's needs.
324+
* The [Positron showcase extension](https://github.com/posit-dev/positron-api-showcase) has a more fleshed out testing implementation.
325+
* Refer to the [VS Code testing docs](https://code.visualstudio.com/api/working-with-extensions/testing-extension) for more information.
326+
327+
### Resource management
328+
* Clean up event listeners in your extension's `deactivate()` function.
329+
* Add disposables to `context.subscriptions` for automatic cleanup.
330+
331+
### Security
332+
* Set `localResourceRoots` when creating webviews.
333+
* Use content security policies for webviews that load external content.
334+
* Never expose sensitive data in webview messages.
335+
336+
## Version compatibility
337+
338+
You can ensure your extension only activates in versions of Positron that you explicitly support by declaring a suitable version range in your `package.json`.
339+
340+
### Positron versioning
341+
342+
Positron follows a calendar versioning scheme, while VS Code uses semantic versioning. Because of this, common range operators such as the caret (`^`) behave differently on each platform:
343+
344+
* **VS Code**: `major.minor.patch`
345+
* A caret range stays within the current major version; `^1.74.0` expands to `>=1.74.0` and `<2.0.0`.
346+
* Stepping up to `2.0.0` can therefore introduce breaking changes.
347+
348+
* **Positron**: `YYYY.M.P` (year · month · patch)
349+
* Under calendar versioning, the caret simply means "this version or anything newer"; `^2025.6.0` translates to `>=2025.6.0` with no upper bound.
350+
* Entering a new year (`2026.x.x`) is *not* automatically breaking—always consult the release notes.
351+
352+
353+
### Specifying Positron version requirements
354+
355+
Add a `positron` entry under the `engines` section of your `package.json` to declare the minimum build that your extension supports.
356+
357+
```json
358+
{
359+
"engines": {
360+
"vscode": "^1.74.0", // VS Code 1.74.0 or later
361+
"positron": "^2025.6.0" // Positron 2025.6.0 or later
362+
}
363+
}
364+
```
365+
366+
367+
### `@posit-dev/positron` versioning
368+
369+
You can check the [version compatibility table](https://github.com/posit-dev/positron-api-pkg?tab=readme-ov-file#version-compatibility) in the `@posit-dev/positron` package to check for any breaking changes you may need to be aware of.
370+
371+
372+
## Next steps
373+
374+
### Resources
375+
376+
- Start quickly with the [Positron extension template](https://github.com/posit-dev/positron-extension-template).
377+
- See more examples of the available API in the [showcase extension](https://github.com/posit-dev/positron-api-showcase).
378+
- Complete type documentation is available on [npm](https://www.npmjs.com/package/@posit-dev/positron).
379+
- Browse the source code of the [API package repository](https://github.com/posit-dev/positron-api-pkg).
23380

24-
## Extension APIs
381+
### Get help
25382

26-
Positron provides [all the normal contribution points and the VS Code API](https://code.visualstudio.com/api/extension-capabilities/overview) to extensions. Unlike VS Code, however, so-called [proposed APIs](https://code.visualstudio.com/api/advanced-topics/using-proposed-api) are available to all extension authors by default, and don't require special configuration on the part of users to enable.
383+
- Ask questions in [GitHub discussions](https://github.com/posit-dev/positron/discussions).
384+
- File bugs in the [API repository](https://github.com/posit-dev/positron-api-pkg/issues).
27385

28-
Positron-native extensions can also make use of its own API. We plan to make the extension development experience better (for example, [safely wrapping](https://github.com/posit-dev/positron/issues/458) and [providing typing for](https://github.com/posit-dev/positron/issues/809) the Positron API), but in the meantime, we recommend you [take a look at the Positron API details directly](https://github.com/posit-dev/positron/tree/main/src/positron-dts).
386+
::: {.callout-tip}
387+
Start with the extension template to get a working setup quickly. Then add features one at a time as you learn the API.
388+
:::

0 commit comments

Comments
 (0)