Skip to content

External editor "tight integration" hard-codes VS Code and bypasses the configured ExternalEditor command #6309

Description

@Rider-Linden

Description

Component

Viewer — Scripting / External Script Editor (llscripteditorws.cpp, llpreviewscript.cpp, llfloaterscripting.cpp)

Summary

When ExternalEditorTightIntegration is enabled, "Edit in External Editor" ignores the editor the user configured in Preferences → Scripting (ExternalEditor) and instead unconditionally shells out to a hard-coded code executable with a VS Code-specific command line and a vscode:// URI. Users who have configured a different editor (or a non-default VS Code install that is not on PATH) get a launch failure or, worse, an editor they did not ask for. Additionally, "tight integration" and "WebSocket sync enabled" are two separate settings that are not actually independent, which produces a confusing UI and at least one setting-name mismatch bug.

Details

1. The editor command is hard-coded

llscripteditorws.cpp:371 builds its own LLProcess::Params rather than going through LLExternalEditor:

  • Windows: cmd.exe /c code --open-url "<uri>"
  • Other platforms: code --open-url <uri>

This means:

  • The ExternalEditor setting, the LL_SCRIPT_EDITOR environment variable, and the Browse… picker in the Scripting floater (llfloaterscripting.cpp) all have no effect on the tight-integration path.
  • The binary must be named code and be on PATH. VS Code Insiders (code-insiders), VSCodium (codium), portable installs, Cursor, and any other client of the vscode:///custom URI protocol cannot be used.
  • The launch path diverges from the legacy path in a second way: the legacy path uses LLExternalEditor::setCommand() / run() with the %s filename marker, validates that the binary exists, and reports localized errors (ExternalEditorNotSet, ExternalEditorNotFound, ExternalEditorCommandParseError, ExternalEditorFailedToRun). The tight path has none of that; it only emits a generic VSCodeLaunchFailed string that literally tells the user to put code on their PATH.
  • The Windows branch passes code --open-url "<uri>" as a single cmd.exe /c argument string specifically to work around & in the URI. Any user-supplied command would have to be re-quoted correctly for this to generalize, and today there is no code path that does so.

2. Setting name mismatch — the tight-integration toggle is read from a control that does not exist

  • The control declared in settings.xml is ExternalEditorTightIntegration.
  • The checkbox in floater_scripting_settings.xml binds to ExternalEditorTightIntegration.
  • LLFloaterScripting::postBuild() subscribes to ExternalEditorTightIntegration.
  • But llscripteditorws.cpp:285 reads ExternalWebsocketSyncTightIntegration, which is not declared anywhere.

So the UI toggle and the runtime check are reading two different controls. Depending on how LLCachedControl handles an undeclared control, the feature is effectively always off (or logs a control-not-found warning), and LLFloaterScripting::onTightIntegrationChanged() mixes both: it reads gSavedSettings.getBOOL("ExternalEditorTightIntegration") for the checkbox enable state but calls isTightIntegration() for the "force WebSocket on" branch — so those two lines can disagree within the same function.

3. "Tight integration" and "WebSocket sync enabled" are redundant

ExternalEditorTightIntegration cannot function without ExternalWebsocketSyncEnable: launchVSCode() calls ensureServerRunning(), which returns nullptr and bails out if isEnabled() is false. The UI already acknowledges this by force-setting ExternalWebsocketSyncEnable to true and disabling the WebSocket checkbox whenever tight integration is on. The result is two user-visible settings where only three of four combinations are reachable and one of them is silently rewritten. Users have no way to understand why a checkbox they own greys itself out and flips.

4. Inconsistent gating at call sites

The tight-integration check is applied inconsistently:

  • llpreviewscript.cpp:1162 — checks isTightIntegration() and branches.
  • llviewermenu.cpp:5866 — checks isTightIntegration() and branches.
  • llpanelcontents.cpp:427 — calls launchVSCode() unconditionally when there are no active connections, with no tight-integration check at all. Object content publishing therefore launches VS Code even for users who never enabled tight integration and who have a different editor configured.

Steps to Reproduce

  1. Open Preferences → Scripting settings.
  2. Use Browse… to set External Script Editor to an editor other than VS Code (e.g. notepad++.exe "%s" or gedit --new-window "%s").
  3. Tick VS Code Tight Integration. Observe that Enable WebSocket Sync is force-enabled and greyed out.
  4. Open a script in an object and click Edit (open in external editor).

Expected: The editor configured in step 2 is launched, or — if that editor cannot participate in the WebSocket protocol — the viewer clearly explains the requirement.

Actual: The viewer attempts to launch code from PATH. If VS Code is not installed or not on PATH, the user gets "Failed to launch VS Code. Ensure the 'code' command is available on your PATH." with no indication of why their configured editor was ignored.

Secondary repro for the setting mismatch: enable the checkbox in step 3 and observe that isTightIntegration() still reports false, so the branch in LLScriptEdCore::openInExternalEditor() takes the legacy temp-file path regardless of the checkbox state.

Impact

  • Users cannot use the editor they configured when tight integration is on.
  • Forks and alternative editors (VSCodium, Insiders, other protocol-handling editors) are locked out.
  • The advertised tight-integration toggle does not appear to take effect at all because of the control-name mismatch.
  • Two coupled settings present a misleading UI.
  • The object-content publishing path launches VS Code without any opt-in.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

triageFlags issues that need to be triaged

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions