Skip to content

Add "open with system default application" event to UI comm #8588

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions build/secrets/.secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,6 @@
"path": "detect_secrets.filters.common.is_baseline_file",
"filename": "build/secrets/.secrets.baseline"
},
{
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
"min_level": 2
},
{
"path": "detect_secrets.filters.heuristic.is_indirect_reference"
},
Expand Down Expand Up @@ -1726,7 +1722,7 @@
"filename": "src/vs/workbench/services/languageRuntime/common/positronUiComm.ts",
"hashed_secret": "659b2a0d48dff4354af7e730d2138e52d57f5a6b",
"is_verified": false,
"line_number": 797,
"line_number": 819,
"is_secret": false
}
],
Expand Down
15 changes: 15 additions & 0 deletions extensions/positron-python/python_files/posit/positron/ui_comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ class UiFrontendEvent(str, enum.Enum):
# Show an HTML file in Positron
ShowHtmlFile = "show_html_file"

# Open a file or folder with the system default application
OpenWithSystem = "open_with_system"

# Webview preloads should be flushed
ClearWebviewPreloads = "clear_webview_preloads"

Expand Down Expand Up @@ -509,6 +512,16 @@ class ShowHtmlFileParams(BaseModel):
)


class OpenWithSystemParams(BaseModel):
"""
Open a file or folder with the system default application
"""

path: StrictStr = Field(
description="The file path to open with the system default application",
)


EditorContext.update_forward_refs()

TextDocument.update_forward_refs()
Expand Down Expand Up @@ -562,3 +575,5 @@ class ShowHtmlFileParams(BaseModel):
ShowUrlParams.update_forward_refs()

ShowHtmlFileParams.update_forward_refs()

OpenWithSystemParams.update_forward_refs()
14 changes: 14 additions & 0 deletions positron/comms/ui-frontend-openrpc.json
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,20 @@
}
]
},
{
"name": "open_with_system",
"summary": "Open a file or folder with the system default application",
"description": "Open a file or folder with the system default application",
"params": [
{
"name": "path",
"description": "The file path to open with the system default application",
"schema": {
"type": "string"
}
}
]
},
{
"name": "clear_webview_preloads",
"summary": "Webview preloads should be flushed",
Expand Down
22 changes: 17 additions & 5 deletions src/vs/workbench/api/browser/positron/mainThreadLanguageRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { IPositronHelpService } from '../../../contrib/positronHelp/browser/posi
import { INotebookService } from '../../../contrib/notebook/common/notebookService.js';
import { IRuntimeClientEvent } from '../../../services/languageRuntime/common/languageRuntimeUiClient.js';
import { URI } from '../../../../base/common/uri.js';
import { BusyEvent, UiFrontendEvent, OpenEditorEvent, OpenWorkspaceEvent, PromptStateEvent, WorkingDirectoryEvent, ShowMessageEvent, SetEditorSelectionsEvent } from '../../../services/languageRuntime/common/positronUiComm.js';
import { BusyEvent, UiFrontendEvent, OpenEditorEvent, OpenWorkspaceEvent, PromptStateEvent, WorkingDirectoryEvent, ShowMessageEvent, SetEditorSelectionsEvent, OpenWithSystemEvent } from '../../../services/languageRuntime/common/positronUiComm.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { IEditor } from '../../../../editor/common/editorCommon.js';
import { Selection } from '../../../../editor/common/core/selection.js';
Expand All @@ -49,6 +49,7 @@ import { CodeAttributionSource, IConsoleCodeAttribution } from '../../../service
import { QueryTableSummaryResult, Variable } from '../../../services/languageRuntime/common/positronVariablesComm.js';
import { IPositronVariablesInstance } from '../../../services/positronVariables/common/interfaces/positronVariablesInstance.js';
import { isWebviewPreloadMessage, isWebviewReplayMessage } from '../../../services/positronIPyWidgets/common/webviewPreloadUtils.js';
import { IOpenerService } from '../../../../platform/opener/common/opener.js';

/**
* Represents a language runtime event (for example a message or state change)
Expand Down Expand Up @@ -142,7 +143,9 @@ class ExtHostLanguageRuntimeSessionAdapter extends Disposable implements ILangua
private readonly _commandService: ICommandService,
private readonly _notebookService: INotebookService,
private readonly _editorService: IEditorService,
private readonly _proxy: ExtHostLanguageRuntimeShape) {
private readonly _proxy: ExtHostLanguageRuntimeShape,
private readonly _openerService: IOpenerService
) {

super();

Expand Down Expand Up @@ -190,7 +193,7 @@ class ExtHostLanguageRuntimeSessionAdapter extends Disposable implements ILangua
this._proxy.$notifyForegroundSessionChanged(session?.sessionId);
}));

this._register(this._runtimeSessionService.onDidReceiveRuntimeEvent(globalEvent => {
this._register(this._runtimeSessionService.onDidReceiveRuntimeEvent(async globalEvent => {
// Ignore events for other sessions.
if (globalEvent.session_id !== this.sessionId) {
return;
Expand Down Expand Up @@ -247,6 +250,13 @@ class ExtHostLanguageRuntimeSessionAdapter extends Disposable implements ILangua
const ws = ev.data as OpenWorkspaceEvent;
const uri = URI.file(ws.path);
this._commandService.executeCommand('vscode.openFolder', uri, ws.new_window);
} else if (ev.name === UiFrontendEvent.OpenWithSystem) {
// Open a file or folder with system default application
const openWith = ev.data as OpenWithSystemEvent;
const uri = URI.file(openWith.path);

// Use VS Code's opener service with external option
await this._openerService.open(uri, { openExternal: true });
} else if (ev.name === UiFrontendEvent.WorkingDirectory) {
// Update current working directory
const dir = ev.data as WorkingDirectoryEvent;
Expand Down Expand Up @@ -1393,7 +1403,8 @@ export class MainThreadLanguageRuntime
@ILogService private readonly _logService: ILogService,
@ICommandService private readonly _commandService: ICommandService,
@INotebookService private readonly _notebookService: INotebookService,
@IEditorService private readonly _editorService: IEditorService
@IEditorService private readonly _editorService: IEditorService,
@IOpenerService private readonly _openerService: IOpenerService
) {
// TODO@softwarenerd - We needed to find a central place where we could ensure that certain
// Positron services were up and running early in the application lifecycle. For now, this
Expand Down Expand Up @@ -1772,7 +1783,8 @@ export class MainThreadLanguageRuntime
this._commandService,
this._notebookService,
this._editorService,
this._proxy);
this._proxy,
this._openerService);
}

private findSession(handle: number): ExtHostLanguageRuntimeSessionAdapter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { IRuntimeClientInstance, RuntimeClientState } from './languageRuntimeClientInstance.js';
import { BusyEvent, ClearConsoleEvent, UiFrontendEvent, OpenEditorEvent, OpenWorkspaceEvent, PromptStateEvent, ShowMessageEvent, WorkingDirectoryEvent, ShowUrlEvent, SetEditorSelectionsEvent, ShowHtmlFileEvent, ClearWebviewPreloadsEvent } from './positronUiComm.js';
import { BusyEvent, ClearConsoleEvent, UiFrontendEvent, OpenEditorEvent, OpenWorkspaceEvent, PromptStateEvent, ShowMessageEvent, WorkingDirectoryEvent, ShowUrlEvent, SetEditorSelectionsEvent, ShowHtmlFileEvent, OpenWithSystemEvent, ClearWebviewPreloadsEvent } from './positronUiComm.js';
import { PositronUiCommInstance } from './positronUiCommInstance.js';
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
import { URI } from '../../../../base/common/uri.js';
Expand Down Expand Up @@ -88,6 +88,7 @@ export class UiClientInstance extends Disposable {
onDidWorkingDirectory: Event<WorkingDirectoryEvent>;
onDidShowUrl: Event<ShowUrlEvent>;
onDidShowHtmlFile: Event<IShowHtmlUriEvent>;
onDidOpenWithSystem: Event<OpenWithSystemEvent>;
onDidClearWebviewPreloads: Event<ClearWebviewPreloadsEvent>;

/** Emitter wrapper for Show URL events */
Expand Down Expand Up @@ -124,6 +125,7 @@ export class UiClientInstance extends Disposable {
this.onDidWorkingDirectory = this._comm.onDidWorkingDirectory;
this.onDidShowUrl = this._onDidShowUrlEmitter.event;
this.onDidShowHtmlFile = this._onDidShowHtmlFileEmitter.event;
this.onDidOpenWithSystem = this._comm.onDidOpenWithSystem;
this.onDidClearWebviewPreloads = this._comm.onDidClearWebviewPreloads;

// Wrap the ShowUrl event to resolve incoming external URIs from the
Expand Down
29 changes: 29 additions & 0 deletions src/vs/workbench/services/languageRuntime/common/positronUiComm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,16 @@ export interface ShowHtmlFileParams {
height: number;
}

/**
* Parameters for the OpenWithSystem method.
*/
export interface OpenWithSystemParams {
/**
* The file path to open with the system default application
*/
path: string;
}

/**
* Event: Change in backend's busy/idle status
*/
Expand Down Expand Up @@ -588,6 +598,17 @@ export interface ShowHtmlFileEvent {

}

/**
* Event: Open a file or folder with the system default application
*/
export interface OpenWithSystemEvent {
/**
* The file path to open with the system default application
*/
path: string;

}

/**
* Event: Webview preloads should be flushed
*/
Expand Down Expand Up @@ -787,6 +808,7 @@ export enum UiFrontendEvent {
SetEditorSelections = 'set_editor_selections',
ShowUrl = 'show_url',
ShowHtmlFile = 'show_html_file',
OpenWithSystem = 'open_with_system',
ClearWebviewPreloads = 'clear_webview_preloads'
}

Expand Down Expand Up @@ -825,6 +847,7 @@ export class PositronUiComm extends PositronBaseComm {
this.onDidSetEditorSelections = super.createEventEmitter('set_editor_selections', ['selections']);
this.onDidShowUrl = super.createEventEmitter('show_url', ['url']);
this.onDidShowHtmlFile = super.createEventEmitter('show_html_file', ['path', 'title', 'is_plot', 'height']);
this.onDidOpenWithSystem = super.createEventEmitter('open_with_system', ['path']);
this.onDidClearWebviewPreloads = super.createEventEmitter('clear_webview_preloads', []);
}

Expand Down Expand Up @@ -927,6 +950,12 @@ export class PositronUiComm extends PositronBaseComm {
* Causes the HTML file to be shown in Positron.
*/
onDidShowHtmlFile: Event<ShowHtmlFileEvent>;
/**
* Open a file or folder with the system default application
*
* Open a file or folder with the system default application
*/
onDidOpenWithSystem: Event<OpenWithSystemEvent>;
/**
* Webview preloads should be flushed
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,15 @@ export class ActiveRuntimeSession extends Disposable {
}
});
}));
this._register(uiClient.onDidOpenWithSystem(event => {
this._onDidReceiveRuntimeEventEmitter.fire({
session_id: sessionId,
event: {
name: UiFrontendEvent.OpenWithSystem,
data: event
}
});
}));
this._register(uiClient.onDidClearWebviewPreloads(event => {
this._onDidReceiveRuntimeEventEmitter.fire({
session_id: sessionId,
Expand Down
Loading