Editor: open scripts in an external editor

Stage-10 editor-UX follow-up, second half of in-editor script authoring:

- "✏ Edit" button on the Script inspector section (enabled once a
  source is assigned) and double-click on any script in the Project
  panel open the .rhai in the user's editor. Non-script assets
  double-click through xdg-open.
- Resolution order: the new External Editor preference command (spawned
  detached as `<command> <file>`, flags allowed), else $VISUAL/$EDITOR
  in a new Terminal-panel tab (TUI editors work in-editor via the
  existing PTY widget), else xdg-open.
- New `editor.external_editor` settings section (ExternalEditorPrefs)
  registered by EditorState, persisted with the preferences file, with
  a rich row in the Preferences window; registration covered by a unit
  test. Editor crate now depends on serde directly for its own
  sections.

Saved edits flow back through the file watcher's live reload, including
into a playing scene. GUI piece — needs an eye-check before promotion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Homer
2026-07-10 20:41:37 +02:00
parent fd25677631
commit 608898411a
3 changed files with 200 additions and 13 deletions
+28
View File
@@ -209,6 +209,20 @@ impl Default for UiDoc {
}
}
/// The settings section holding [`ExternalEditorPrefs`].
pub const EXTERNAL_EDITOR_SECTION: &str = "editor.external_editor";
/// Preferences for opening a script (or other text asset) in an editor —
/// registered as the [`EXTERNAL_EDITOR_SECTION`] settings section, editable in
/// Preferences, persisted to `~/.config/oxide/editor.ron` like the bindings.
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct ExternalEditorPrefs {
/// Command to launch, invoked as `<command> <file>` (whitespace-split; may
/// carry its own flags, e.g. `"code -g"`). **Empty (the default) = auto**:
/// run `$VISUAL`/`$EDITOR` in an editor Terminal tab, else `xdg-open`.
pub command: String,
}
impl EditorState {
/// A blank state with an empty scene, no open project, and the editor's
/// default action bindings registered (`F` toggle, WASD/QE move, Shift
@@ -224,6 +238,7 @@ impl EditorState {
bindings::register_defaults(&mut actions);
let mut settings = Settings::new();
settings.register::<ActionOverrides>(bindings::SETTINGS_SECTION);
settings.register::<ExternalEditorPrefs>(EXTERNAL_EDITOR_SECTION);
let mut registry = TypeRegistry::new();
register_builtin_types(&mut registry);
// Seed a small, generally-useful set of named layers (besides the
@@ -590,4 +605,17 @@ mod tests {
.get::<oxide_physics::RigidBody>(restored)
.is_some());
}
#[test]
fn external_editor_section_is_registered_and_defaults_to_auto() {
let state = EditorState::new();
let prefs = state
.settings
.get::<ExternalEditorPrefs>(EXTERNAL_EDITOR_SECTION)
.expect("external-editor settings section must be registered");
assert!(
prefs.command.is_empty(),
"default is empty = auto ($VISUAL/$EDITOR terminal tab, else xdg-open)"
);
}
}