编程 用 go-prompt 给 Go CLI 接入交互式输入:自动补全、历史记录与快捷键

2026-09-12 09:48:32

用 go-prompt 给 Go CLI 接入交互式输入:自动补全、历史记录与快捷键

写命令行工具时,如果用户每次都要输入完整命令,体验会比较差;给 CLI 加自动补全、历史记录、多行输入和快捷键,自己实现又容易出错。go-prompt 是一个 Go 的交互式命令行库,下面按接入路径记录它的用法。

1. 为什么需要 go-prompt

先说一个场景:写数据库查询工具。用标准库的实现:

scanner := bufio.NewScanner(os.Stdin)
for {
    fmt.Print("> ")
    scanner.Scan()
    query := scanner.Text()
    // 执行查询...
}

问题:

  • 没有自动补全
  • 没有历史记录
  • 没有快捷键支持
  • 用户体验差

用 go-prompt:

package main

import (
    "fmt"

    "github.com/c-bata/go-prompt"
)

func completer(d prompt.Document) []prompt.Suggest {
    s := []prompt.Suggest{
        {Text: "SELECT", Description: "查询数据"},
        {Text: "INSERT", Description: "插入数据"},
        {Text: "UPDATE", Description: "更新数据"},
        {Text: "DELETE", Description: "删除数据"},
        {Text: "FROM", Description: "指定表"},
        {Text: "WHERE", Description: "条件过滤"},
    }
    return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
}

func main() {
    p := prompt.New(
        executor,
        completer,
        prompt.Title("sql"),
        prompt.PrefixColor(prompt.Cyan),
    )
    p.Run()
}

func executor(s string) {
    fmt.Printf("执行:%s\n", s)
}

自动补全、历史记录、快捷键都在库内提供。

2. go-prompt 是什么

go-prompt 是 Go 语言的交互式命令行库。官方描述:

A library for building powerful interactive command line tools in Go.

它提供:

  • 交互式命令行库
  • 支持自动补全
  • 支持历史记录
  • 支持快捷键

核心定位

go-prompt 不是命令行参数解析库,也不是 TUI 框架。它是交互式输入库,用于构建智能命令行工具。

它做的事情:

  1. 提供交互式输入界面
  2. 支持自动补全
  3. 支持历史记录
  4. 支持快捷键

这些功能解决的是命令行工具的输入体验问题。

3. 核心特性:自动补全和交互

go-prompt 的主要特性是自动补全和交互。

自动补全

func completer(d prompt.Document) []prompt.Suggest {
    s := []prompt.Suggest{
        {Text: "users", Description: "用户表"},
        {Text: "orders", Description: "订单表"},
        {Text: "products", Description: "商品表"},
    }
    return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
}

多级补全

func completer(d prompt.Document) []prompt.Suggest {
    // 根据上下文提供不同的补全
    if d.Text() == "SELECT " {
        return []prompt.Suggest{
            {Text: "users", Description: "用户表"},
            {Text: "orders", Description: "订单表"},
        }
    }

    if strings.HasPrefix(d.Text(), "SELECT * FROM ") {
        return []prompt.Suggest{
            {Text: "WHERE", Description: "条件过滤"},
            {Text: "ORDER BY", Description: "排序"},
        }
    }

    return []prompt.Suggest{}
}

历史记录

p := prompt.New(
    executor,
    completer,
    prompt.History(history),
)

快捷键

支持 Emacs 风格的快捷键:

  • Ctrl+A:移动到行首
  • Ctrl+E:移动到行尾
  • Ctrl+K:删除到行尾
  • Ctrl+U:删除到行首
  • Ctrl+W:删除前一个单词

4. 安装和使用

安装:

go get github.com/c-bata/go-prompt

基本使用

1. 简单示例

package main

import (
    "fmt"

    "github.com/c-bata/go-prompt"
)

func executor(s string) {
    fmt.Printf("你输入了:%s\n", s)
}

func completer(d prompt.Document) []prompt.Suggest {
    return []prompt.Suggest{
        {Text: "help", Description: "显示帮助"},
        {Text: "exit", Description: "退出程序"},
        {Text: "clear", Description: "清屏"},
    }
}

func main() {
    p := prompt.New(
        executor,
        completer,
        prompt.PrefixColor(prompt.Cyan),
    )
    p.Run()
}

2. 动态补全

func completer(d prompt.Document) []prompt.Suggest {
    word := d.GetWordBeforeCursor()

    commands := []prompt.Suggest{
        {Text: "select", Description: "查询"},
        {Text: "insert", Description: "插入"},
        {Text: "update", Description: "更新"},
        {Text: "delete", Description: "删除"},
    }

    tables := []prompt.Suggest{
        {Text: "users", Description: "用户表"},
        {Text: "orders", Description: "订单表"},
        {Text: "products", Description: "商品表"},
    }

    // 根据输入位置提供不同的补全
    if strings.HasPrefix(d.TextBeforeCursor(), "select ") {
        return prompt.FilterHasPrefix(tables, word, true)
    }

    return prompt.FilterHasPrefix(commands, word, true)
}

3. 实时补全

type SQLCompleter struct {
    tables  []string
    columns map[string][]string
}

func (c *SQLCompleter) Complete(d prompt.Document) []prompt.Suggest {
    word := d.GetWordBeforeCursor()

    // 分析 SQL 上下文
    text := d.TextBeforeCursor()

    // SELECT 后面补表名
    if matched, _ := regexp.MatchString(`SELECT\s+\w*$`, text); matched {
        var suggestions []prompt.Suggest
        for _, table := range c.tables {
            suggestions = append(suggestions, prompt.Suggest{
                Text:        table,
                Description: "表",
            })
        }
        return prompt.FilterHasPrefix(suggestions, word, true)
    }

    // FROM 后面补表名
    if matched, _ := regexp.MatchString(`FROM\s+\w*$`, text); matched {
        var suggestions []prompt.Suggest
        for _, table := range c.tables {
            suggestions = append(suggestions, prompt.Suggest{
                Text:        table,
                Description: "表",
            })
        }
        return prompt.FilterHasPrefix(suggestions, word, true)
    }

    return []prompt.Suggest{}
}

4. 自定义样式

p := prompt.New(
    executor,
    completer,
    prompt.Title("my-app"),
    prompt.PrefixText("$ "),
    prompt.PrefixColor(prompt.Green),
    prompt.InputTextColor(prompt.White),
    prompt.SuggestionBGColor(prompt.Blue),
    prompt.SuggestionTextColor(prompt.White),
)

5. 核心功能

go-prompt 定位是交互式输入库,功能包括多行输入、密码输入、自定义快捷键和实时验证。

功能一:多行输入

func executor(s string) {
    // 支持多行输入
    lines := strings.Split(s, "\n")
    for _, line := range lines {
        fmt.Printf("行:%s\n", line)
    }
}

功能二:密码输入

p := prompt.New(
    func(s string) {
        fmt.Printf("密码长度:%d\n", len(s))
    },
    completer,
    prompt.OptionPrefixText("密码:"),
    prompt.OptionInputTextColor(prompt.Yellow),
)

功能三:自定义快捷键

func keyBindFunc(d prompt.Document) []prompt.KeyBind {
    return []prompt.KeyBind{
        {
            Key: prompt.ControlC,
            Fn: func(buf prompt.Buffer) {
                os.Exit(0)
            },
        },
        {
            Key: prompt.ControlL,
            Fn: func(buf prompt.Buffer) {
                // 清屏
                fmt.Print("\033[2J\033[H")
            },
        },
    }
}

功能四:实时验证

func validator(d prompt.Document) error {
    text := d.Text()

    if len(text) == 0 {
        return prompt.ValidationError("输入不能为空")
    }

    if len(text) > 100 {
        return prompt.ValidationError("输入太长")
    }

    return nil
}

p := prompt.New(
    executor,
    completer,
    prompt.OptionValidator(validator),
)

6. 实战场景

场景一:SQL 客户端

需要一个智能 SQL 客户端。

type SQLClient struct {
    db      *sql.DB
    tables  []string
    columns map[string][]string
}

func (c *SQLClient) completer(d prompt.Document) []prompt.Suggest {
    word := d.GetWordBeforeCursor()
    text := strings.ToLower(d.TextBeforeCursor())

    // SQL 关键字
    keywords := []prompt.Suggest{
        {Text: "SELECT", Description: "查询"},
        {Text: "INSERT", Description: "插入"},
        {Text: "UPDATE", Description: "更新"},
        {Text: "DELETE", Description: "删除"},
        {Text: "FROM", Description: "表"},
        {Text: "WHERE", Description: "条件"},
    }

    // 表名
    var tables []prompt.Suggest
    for _, t := range c.tables {
        tables = append(tables, prompt.Suggest{
            Text:        t,
            Description: "表",
        })
    }

    // 根据上下文选择补全
    if strings.Contains(text, "from ") {
        return prompt.FilterHasPrefix(tables, word, true)
    }

    return prompt.FilterHasPrefix(keywords, word, true)
}

func (c *SQLClient) executor(s string) {
    if strings.TrimSpace(s) == "" {
        return
    }

    rows, err := c.db.Query(s)
    if err != nil {
        fmt.Printf("错误:%v\n", err)
        return
    }
    defer rows.Close()

    // 显示结果...
}

func main() {
    client := &SQLClient{
        tables: []string{"users", "orders", "products"},
    }

    p := prompt.New(
        client.executor,
        client.completer,
        prompt.Title("sql"),
        prompt.PrefixText("sql> "),
    )
    p.Run()
}

效果:SQL 关键字、表名自动补全。

场景二:配置编辑器

需要一个智能配置编辑器。

type ConfigEditor struct {
    config map[string]interface{}
}

func (e *ConfigEditor) completer(d prompt.Document) []prompt.Suggest {
    word := d.GetWordBeforeCursor()

    var suggestions []prompt.Suggest
    for key := range e.config {
        suggestions = append(suggestions, prompt.Suggest{
            Text:        key,
            Description: fmt.Sprintf("当前值:%v", e.config[key]),
        })
    }

    return prompt.FilterHasPrefix(suggestions, word, true)
}

func (e *ConfigEditor) executor(s string) {
    parts := strings.SplitN(s, "=", 2)
    if len(parts) != 2 {
        fmt.Println("格式:key=value")
        return
    }

    key := strings.TrimSpace(parts[0])
    value := strings.TrimSpace(parts[1])

    e.config[key] = value
    fmt.Printf("设置 %s = %v\n", key, value)
}

效果:配置项自动补全,并显示当前值。

场景三:命令调试器

需要一个交互式命令调试器。

type Debugger struct {
    commands map[string]func([]string)
}

func (d *Debugger) completer(doc prompt.Document) []prompt.Suggest {
    word := doc.GetWordBeforeCursor()

    var suggestions []prompt.Suggest
    for cmd := range d.commands {
        suggestions = append(suggestions, prompt.Suggest{
            Text:        cmd,
            Description: "命令",
        })
    }

    return prompt.FilterHasPrefix(suggestions, word, true)
}

func (d *Debugger) executor(s string) {
    parts := strings.Fields(s)
    if len(parts) == 0 {
        return
    }

    cmd := parts[0]
    args := parts[1:]

    if fn, ok := d.commands[cmd]; ok {
        fn(args)
    } else {
        fmt.Printf("未知命令:%s\n", cmd)
    }
}

效果:命令自动补全,支持交互式调试。

7. 设计亮点

亮点一:纯 Go 实现

不依赖外部库,纯 Go 实现。好处:跨平台、易于集成、性能优秀。

亮点二:灵活补全

支持动态、上下文感知的补全。好处:智能补全、适应场景、用户体验好。

亮点三:丰富功能

支持历史记录、快捷键、多行输入等。好处:功能全面、一个库搞定、减少依赖。

亮点四:自定义样式

支持自定义颜色、提示符等。好处:个性化、品牌一致、视觉友好。

亮点五:活跃维护

社区活跃,持续更新。好处:Bug 修复快、新特性多、文档完善。

8. 和类似方案对比

vs. readline

维度go-promptreadline
Star8K1K
自动补全强大基础
颜色支持丰富有限
维护活跃较少

结论:go-prompt 功能更全。

vs. promptui

维度go-promptpromptui
Star8K3K
定位交互式输入表单输入
补全强大
场景CLI 工具交互式表单

结论:go-prompt 适合 CLI,promptui 适合表单。

vs. 标准库

维度go-promptbufio.Scanner
自动补全支持
历史记录支持
快捷键支持
用户体验优秀基础

结论:go-prompt 体验更好。

9. 局限性

  1. 学习曲线:API 较多,需要学习
  2. 性能开销:大量补全时可能较慢
  3. 平台限制:某些平台支持有限
  4. 调试困难:交互式调试较难
  5. 文档简单:某些高级功能文档不足
  6. 依赖终端:依赖终端能力

10. 总结

从 go-prompt 可以看到几个点:

  1. 用户体验很重要:CLI 也需要好体验
  2. 自动补全是刚需:减少输入错误
  3. 交互式工具有价值:提高生产力
  4. 纯 Go 实现是优势:跨平台,易集成
  5. 灵活设计很重要:适应不同场景

go-prompt 解决的是 CLI 输入体验这个具体问题。如果还在用标准库写 CLI 工具,可以试一下 go-prompt。也许有一天 Go 标准库会提供更强大的交互式输入功能,但在那之前,go-prompt 已经覆盖了这类交互式 CLI 的常见需求。


参考资料

复制全文 生成海报 Go go-prompt CLI 交互式命令行 自动补全

推荐文章

程序员茄子在线接单