2 releases

0.1.1 Sep 5, 2024
0.1.0 Aug 16, 2024

#17 in #open-file

Download history 224/week @ 2024-08-14 17/week @ 2024-08-21 32/week @ 2024-08-28 174/week @ 2024-09-04 24/week @ 2024-09-11

252 downloads per month
Used in 2 crates (via slumber_tui)

MIT license

16KB
199 lines

editor-command

Test CI crates.io docs.rs

Load a user's preferred file editing command from the VISUAL or EDITOR environment variables.

use editor_command::EditorCommand;
use std::process::Command;

std::env::set_var("VISUAL", "vim");
let mut command: Command = EditorCommand::edit_file("file.txt").unwrap();
command.spawn();

lib.rs:

Get an executable [Command] to open a particular file in the user's configured editor.

Features

  • Load editor command from the VISUAL or EDITOR environment variables
  • Specify high-priority override and low-priority default commands to use
  • Pass one or more paths to be opened by the editor
  • Flexible builder pattern

Examples

The simplest usage looks like this:

use editor_command::EditorBuilder;
use std::process::Command;

std::env::set_var("VISUAL", "vim");
let command: Command = EditorBuilder::edit_file("file.txt").unwrap();
assert_eq!(command.get_program(), "vim");

Here's an example of using the builder pattern to provide both an override and a fallback command to [EditorBuilder]:

use editor_command::EditorBuilder;
use std::process::Command;

// In your app, this could be an optional field from a config object
let override_command = Some("code --wait");
let command: Command = EditorBuilder::new()
    // In this case, the override is always populated so it will always win.
    // In reality it would be an optional user-provided field.
    .source(override_command)
    .environment()
    // If both VISUAL and EDITOR are undefined, we'll fall back to this
    .source(Some("vi"))
    .build()
    .unwrap();
assert_eq!(format!("{command:?}"), "\"code\" \"--wait\"");

This pattern is useful for apps that have a way to configure an app-specific editor. For example, git has the core.editor config field.

Tokio

[EditorBuilder] returns a std [Command], which will execute synchronously. If you want to run your editor subprocess asynchronously via tokio, use the From<std::process::Command> impl on tokio::process::Command. For example:

let command: tokio::process::Command =
    EditorBuilder::edit_file("file.yaml").unwrap().into();

Syntax

The syntax of the command is meant to resemble command syntax for common shells. The first word is the program name, and subsequent tokens (separated by spaces) are arguments to that program. Single and double quotes can be used to join multiple tokens together into a single argument.

Command parsing is handled by the crate [shellish_parse] (with default [ParseOptions]). Refer to those docs for exact details on the syntax.

Lifetimes

[EditorBuilder] accepts a lifetime parameter, which is bound to the string data it contains (both command strings and paths). This is to prevent unnecessary cloning when building commands/paths from &strs. If you need the instance of [EditorBuilder] to be 'static, e.g. so it can be returned from a function, you can simply use EditorBuilder<'static>. Internally, all strings are stored as [Cow]s, so clones will be made as necessary.

use editor_command::EditorBuilder;

/// This is a contrived example of returning a command with owned data
fn get_editor_builder<'a>(command: &'a str) -> EditorBuilder<'static> {
    // The lifetime bounds enforce the .to_owned() call
    EditorBuilder::new().source(Some(command.to_owned()))
}

let command = get_editor_builder("vim").build().unwrap();
assert_eq!(command.get_program(), "vim");

Resources

For more information on the VISUAL and EDITOR environment variables, check out this thread.

Dependencies

~36KB