Rust异步命令行编辑库rustyline-async的使用,提供高性能交互式终端输入和历史记录管理

RustyLine Async

一个支持多行和异步的最小化readline库。

特性

  • 在所有crossterm支持的平台上工作
  • 完整的Unicode支持(包括Grapheme Clusters)
  • 多行编辑
  • 历史记录 + 保存/加载API
  • Ctrl-C, Ctrl-D作为Ok(Interrupt)和Ok(Eof) ReadlineEvent返回
  • Ctrl-U清除光标前的行
  • Ctrl-left & right移动到下一个或前一个空格
  • Home/Ctrl-A和End/Ctrl-E跳转到输入的开始和结束(通过禁用"emacs"特性可以关闭Ctrl-A & Ctrl-E)
  • Ctrl-L清屏
  • Ctrl-W删除到前一个空格
  • 基于crossterm的event-stream特性的可扩展设计

欢迎PR添加更多特性!

示例

cargo run --example readline

rustyline-async

许可证

本软件采用The Unlicense许可证。

完整示例代码

use rustyline_async::{Readline, ReadlineEvent};
use tokio::stream::StreamExt;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 创建异步readline实例
    let mut rl = Readline::new("> ".to_string())?;
    
    println!("欢迎使用rustyline-async演示!");
    println!("输入一些内容或者按Ctrl-C/Ctrl-D退出");

    // 异步处理输入事件
    while let Some(event) = rl.next().await {
        match event {
            ReadlineEvent::Line(line) => {
                println!("你输入了: {}", line);
                // 将输入添加到历史记录
                rl.add_history_entry(line.clone());
            }
            ReadlineEvent::Eof => {
                println!("检测到Ctrl-D,退出");
                break;
            }
            ReadlineEvent::Interrupt => {
                println!("检测到Ctrl-C,退出");
                break;
            }
        }
    }
    
    // 保存历史记录到文件
    if let Err(e) = rl.save_history("history.txt") {
        eprintln!("保存历史记录失败: {}", e);
    }
    
    Ok(())
}

要运行这个示例,请确保在Cargo.toml中添加了依赖:

[dependencies]
rustyline-async = "0.4.7"
tokio = { version = "1.0", features = ["full"] }

这个示例展示了rustyline-async的基本用法:

  1. 创建异步readline实例
  2. 处理三种主要事件类型:Line(正常输入)、Eof(Ctrl-D)和Interrupt(Ctrl-C)
  3. 添加历史记录条目
  4. 保存历史记录到文件

rustyline-async提供了高性能的交互式终端输入和历史记录管理功能,非常适合构建命令行工具和REPL环境。


1 回复

Rust异步命令行编辑库rustyline-async使用指南

简介

rustyline-async是Rust的一个异步命令行编辑库,提供了高性能的交互式终端输入和历史记录管理功能。它是rustyline库的异步版本,特别适合在异步运行时环境中使用。

主要特性

  • 支持行编辑和历史记录
  • 异步I/O操作
  • 支持自动补全
  • 语法高亮
  • 多平台支持(Unix和Windows)
  • 可配置的行为

安装

在Cargo.toml中添加依赖:

[dependencies]
rustyline-async = "0.2"
tokio = { version = "1.0", features = ["full"] }

基本用法

use rustyline_async::{RustyLine, ReadlineError};
use tokio::runtime::Runtime;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (mut rl, mut writer) = RustyLine::new()?;
    
    loop {
        let readline = rl.readline(">> ");
        tokio::pin!(readline);
        
        match readline.await {
            Ok(line) => {
                println!("You entered: {}", line);
                writer.add_history_entry(line).await?;
            }
            Err(ReadlineError::Interrupted) => {
                println!("CTRL-C");
                break;
            }
            Err(ReadlineError::Eof) => {
                println!("CTRL-D");
                break;
            }
            Err(err) => {
                println!("Error: {:?}", err);
                break;
            }
        }
    }
    
    Ok(())
}

高级用法

1. 自定义补全器

use rustyline_async::{RustyLine, ReadlineError};
use rustyline_async::completion::Completer;
use rustyline_async::highlight::Highlighter;
use rustyline_async::hint::Hinter;
use std::borrow::Cow;

struct MyCompleter;

impl Completer for MyCompleter {
    type Candidate = String;

    fn complete(&self, line: &str, pos: usize) -> rustyline_async::Result<(usize, Vec<String>)> {
        let completions = vec!["hello", "world", "foo", "bar"]
            .iter()
            .filter(|s| s.starts_with(line))
            .map(|s| s.to_string())
            .collect();
        Ok((0, completions))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = rustyline_async::Config::builder()
        .auto_add_history(true)
        .build();
    
    let (mut rl, _) = RustyLine::with_config(config)?;
    rl.set_completer(Some(Box::new(MyCompleter)));
    
    // 使用方式与基本示例相同
}

2. 使用历史记录

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (mut rl, mut writer) = RustyLine::new()?;
    
    // 加载历史记录
    if let Err(e) = writer.load_history("history.txt").await {
        eprintln!("Failed to load history: {}", e);
    }
    
    loop {
        let readline = rl.readline(">> ");
        tokio::pin!(readline);
        
        match readline.await {
            Ok(line) => {
                println!("You entered: {}", line);
                writer.add_history_entry(line).await?;
            }
            Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break,
            Err(err) => return Err(err.into()),
        }
    }
    
    // 保存历史记录
    if let Err(e) = writer.save_history("history.txt").await {
        eprintln!("Failed to save history: {}", e);
    }
    
    Ok(())
}

3. 自定义提示符

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = rustyline_async::Config::builder()
        .prompt("user> ")
        .build();
    
    let (mut rl, _) = RustyLine::with_config(config)?;
    
    // 使用方式与基本示例相同
}

完整示例

下面是一个完整的示例,结合了基本用法、自定义补全器和历史记录功能:

use rustyline_async::{RustyLine, ReadlineError};
use rustyline_async::completion::{Completer, Completion};
use tokio::fs;

// 自定义补全器
struct MyCompleter;

impl Completer for MyCompleter {
    type Candidate = String;

    fn complete(
        &self,
        line: &str,
        _pos: usize,
    ) -> rustyline_async::Result<(usize, Vec<String>)> {
        let commands = vec![
            "help", "exit", "history", 
            "clear", "save", "load"
        ];
        
        let matches = commands
            .iter()
            .filter(|cmd| cmd.starts_with(line))
            .map(|s| s.to_string())
            .collect();
            
        Ok((0, matches))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 创建配置,启用自动历史记录
    let config = rustyline_async::Config::builder()
        .prompt("cmd> ")
        .auto_add_history(true)
        .max_history_size(100)
        .build();
        
    // 初始化rustyline
    let (mut rl, mut writer) = RustyLine::with_config(config)?;
    rl.set_completer(Some(Box::new(MyCompleter)));
    
    // 尝试加载历史记录
    if fs::try_exists("history.txt").await.unwrap_or(false) {
        writer.load_history("history.txt").await?;
    }
    
    println!("输入'help'获取帮助,'exit'退出");
    
    loop {
        let readline = rl.readline("cmd> ");
        tokio::pin!(readline);
        
        match readline.await {
            Ok(line) => {
                match line.trim() {
                    "help" => {
                        println!("可用命令:");
                        println!("help    - 显示帮助");
                        println!("exit    - 退出程序");
                        println!("history - 显示历史记录");
                        println!("clear   - 清除历史记录");
                    }
                    "exit" => break,
                    "history" => {
                        let entries = writer.history_entries().await?;
                        for (i, entry) in entries.iter().enumerate() {
                            println!("{}: {}", i + 1, entry);
                        }
                    }
                    "clear" => {
                        writer.clear_history().await?;
                        println!("历史记录已清除");
                    }
                    _ => {
                        println!("未知命令: {}", line);
                    }
                }
            }
            Err(ReadlineError::Interrupted) => {
                println!("输入CTRL-C退出");
                break;
            }
            Err(ReadlineError::Eof) => {
                println!("输入CTRL-D退出");
                break;
            }
            Err(err) => {
                eprintln!("错误: {:?}", err);
                break;
            }
        }
    }
    
    // 保存历史记录
    writer.save_history("history.txt").await?;
    
    Ok(())
}

性能优化建议

  1. 对于大量历史记录,考虑设置历史记录大小限制:

    let config = rustyline_async::Config::builder()
        .max_history_size(1000)
        .build();
    
  2. 如果不需要历史记录功能,可以禁用:

    let config = rustyline_async::Config::builder()
        .history_ignore_space(true)
        .build();
    
  3. 对于自定义补全器,确保实现高效,避免在complete方法中执行耗时操作。

注意事项

  1. rustyline-async需要与异步运行时(如tokio)配合使用
  2. 在Windows平台上可能需要启用特定功能
  3. 确保正确处理中断信号(CTRL-C)和文件结束符(CTRL-D)

rustyline-async为Rust异步应用程序提供了强大的命令行交互能力,特别适合构建CLI工具、REPL环境或需要复杂用户交互的终端应用。

回到顶部