简体   繁体   中英

Rust run command interactively

I'm currently building a console application in Rust that can be used for easily building commits. Think half git add -i with a better user interface and half system for easily and consistently building quality commit messages.

Currently, I have an app which does the adding and the commit messages, and it's great. However, as I often used the patch functionality of git add , I'd like to add that to my application.

As I have it now, after calling my program, the user will see a list of changed files, the same as you would if you ran git add -i , but with all files in one list -- untracked aren't separated out. Now, this is just a checkbox list, you use arrow keys to go up and down. If you hover over a file, the user will have actions they can take with that file, like seeing the unstaged changes or running git add --patch against it, rather than staging the entire file.

Now, I've gotten the unstaged changes view working great. This is the code:

let unstaged_changes = Command::new("git")
    .current_dir(&project_root)
    .args(&["diff", "--"])
    .args(file_name)
    .stdout(Stdio::piped())
    .spawn()?;

Command::new("less")
    .current_dir(&project_root)
    .stdin(unstaged_changes.stdout.ok_or_else(|| {
        io::Error::new(io::ErrorKind::Other, "failed to get stdout")
    })?)
    .status()?;

I use less as not using it locks up the terminal and causes all sorts of issues. Scrolling will be awkward and inconsistent, if I scroll up too far the entire diff log will just vanish, and sometimes it just merges with the text above it, causing all sorts of visual issues. Now maybe that's a problem with my terminal, but either, way, can't have it. Using less solves these issues perfectly.

I want to do something similar for git add --patch . I can't run the command straight up, as it causes all sorts of issues on top of being non-interactive, ie, I can't give the commands to tool.

I can't pass it into less either, as less can't handle that sort of input. I need to be able to handle the full suite of patch options:

Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?

(though even that is missing a couple that only appear in context).

Is there any simple tool out there for doing this? I guess I wouldn't mind another terminal window temporarily opening, so long as I could close it as soon as the patch is complete and it'll work cross platform.

Have you tried running your command without piping anything?

[edit]

From the docs: you may be looking for Command::status() :

Executes a command as a child process, waiting for it to finish and collecting its exit status.

By default, stdin, stdout and stderr are inherited from the parent.

If you want to explicitly share "your" stdin with the child command:

.stdin(Stdio::inherit())

You haven't said which library you are using to manage the terminal.

If you are using curses or a library that provides an abstraction to curses, it looks like the normal approach is toleave curses mode before calling out to an external command and then restore the terminal state after the command completes. From the link (this is C, but a Rust interface likely uses the same names):

    addstr("Running git");
    def_prog_mode();           /* save current tty modes */
    endwin();                  /* restore original tty modes */
    system("git add --patch"); /* run git */
    addstr("done\n");          /* prepare return message */
    refresh();                 /* restore save modes, repaint screen */

For some other terminal-management library, you can look at what the library does to restore the terminal state when your application exits. You basically want to request the same actions to happen before you execute a command that will update the terminal.

For crossterm , it might be enough to run something like the following before executing your command:

execute!(stdout(),
    terminal::LeaveAlternateScreen,
    style::ResetColor,
    event::DisableMouseCapture,
    terminal::Clear(ClearType::All),
    cursor::MoveTo(1, 1)
)?;
terminal::disable_raw_mode()?;

Not all of those commands may be needed, especially if your application never touches that terminal state.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM