golang现代化Shell交互式命令行UI开发插件库readline的使用

Golang 现代化 Shell 交互式命令行 UI 开发插件库 readline 的使用

简介

readline 是一个现代化的纯 Go 实现的 readline shell 库,具有完整的 .inputrc 和传统 readline 命令/选项支持,并扩展了现代 shell 中常见的各种命令、选项和工具。其架构和补全系统深受 Z-Shell 的启发。

特性

核心功能

  • 纯 Go 实现,几乎仅使用标准库
  • 跨平台支持 (Linux/MacOS/Windows)
  • 完整的 .inputrc 支持(所有命令/选项)
  • 广泛的测试套件和几乎全覆盖的核心代码
  • 扩展的命令/选项列表(编辑/补全/历史)
  • 完整的多行编辑/移动支持
  • $EDITOR/$VISUAL 中支持命令行编辑
  • 可编程 API,可安全访问核心组件
  • 支持任意数量的历史记录源

Emacs/标准模式

  • 原生 Emacs 命令
  • Emacs 风格的宏引擎(不跨多个调用工作)
  • 关键字切换(操作符、布尔值、十六进制/二进制/数字)带迭代
  • 命令/模式光标状态指示器
  • 完整的撤销/重做历史
  • 命令状态/参数/迭代提示显示

Vim 模式

  • 接近原生的 Vim 模式
  • Vim 文本对象(代码块、单词/空白/shell 单词)
  • 扩展的环绕选择/更改/添加功能,带高亮
  • Vim 可视/操作符挂起模式和光标样式指示
  • Vim 插入和替换(一次/多次)
  • 所有 Vim 寄存器,带补全支持
  • Vim 风格的宏录制 (q<a>) 和调用 (@<a>)

界面

  • 支持 PS1/PS2/RPROMPT/transient/tooltip 提示(兼容 oh-my-posh)
  • 扩展的补全系统,基于键映射且可配置,易于填充和使用
  • 多种补全显示样式,支持颜色
  • 补全和历史记录的增量搜索系统和高亮(模糊搜索)
  • 自动和上下文感知的后缀移除,实现高效的标志/路径/列表补全
  • 可选的异步自动补全
  • 内置和可编程的语法高亮

示例代码

下面是一个使用 readline 库的基本示例:

package main

import (
	"fmt"
	"github.com/reeflective/readline"
)

func main() {
	// 创建一个新的 readline shell 实例
	shell := readline.NewShell()

	// 设置提示符
	shell.SetPrompt("myapp> ")

	// 主循环
	for {
		// 读取用户输入
		line, err := shell.Readline()
		if err != nil {
			break
		}

		// 处理用户输入
		if line == "exit" {
			break
		}

		// 回显用户输入
		fmt.Printf("You entered: %s\n", line)
	}
}

高级示例 - 带补全功能

package main

import (
	"fmt"
	"github.com/reeflective/readline"
	"github.com/reeflective/readline/completion"
)

func main() {
	// 创建 shell 实例
	shell := readline.NewShell()
	shell.SetPrompt("demo> ")

	// 设置补全函数
	completer := completion.NewCompleter()
	
	// 添加命令补全
	commands := []string{"help", "exit", "list", "add", "delete"}
	completer.AddCommand(commands...)
	
	// 设置补全器
	shell.SetCompleter(completer)

	// 主循环
	for {
		line, err := shell.Readline()
		if err != nil {
			break
		}

		switch line {
		case "exit":
			return
		case "help":
			fmt.Println("Available commands:", commands)
		default:
			fmt.Println("Unknown command. Type 'help' for available commands.")
		}
	}
}

使用 Vim 模式

package main

import (
	"fmt"
	"github.com/reeflective/readline"
)

func main() {
	// 创建 shell 实例
	shell := readline.NewShell()
	
	// 启用 Vim 模式
	shell.SetKeyMap(readline.Vim)
	shell.SetPrompt("vim-mode> ")

	fmt.Println("Entering Vim mode. Press 'i' to insert, ESC to return to normal mode")

	for {
		line, err := shell.Readline()
		if err != nil {
			break
		}

		if line == "exit" {
			break
		}

		fmt.Printf("You entered: %s\n", line)
	}
}

历史记录功能示例

package main

import (
	"fmt"
	"github.com/reeflective/readline"
)

func main() {
	shell := readline.NewShell()
	shell.SetPrompt("history-demo> ")

	// 添加一些历史记录
	shell.AddHistory("first command")
	shell.AddHistory("second command")
	shell.AddHistory("third command")

	for {
		line, err := shell.Readline()
		if err != nil {
			break
		}

		if line == "exit" {
			break
		}

		// 将命令添加到历史记录
		shell.AddHistory(line)
		fmt.Printf("Executed: %s\n", line)
	}
}

多行编辑示例

package main

import (
	"fmt"
	"github.com/reeflective/readline"
)

func main() {
	shell := readline.NewShell()
	shell.SetPrompt("multiline> ")
	shell.SetMultiLine(true) // 启用多行模式

	fmt.Println("Enter multi-line input. Press Alt+Enter for new line, Enter to submit")

	for {
		lines, err := shell.Readline()
		if err != nil {
			break
		}

		if lines == "exit" {
			break
		}

		fmt.Printf("You entered:\n%s\n", lines)
	}
}

readline 库提供了丰富的功能来构建现代化的交互式命令行应用程序,包括高级编辑功能、补全系统、历史记录管理等。通过上述示例,您可以快速开始使用这个强大的库来增强您的命令行工具。


更多关于golang现代化Shell交互式命令行UI开发插件库readline的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang现代化Shell交互式命令行UI开发插件库readline的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang 现代化 Shell 交互式命令行 UI 开发 - readline 库使用指南

在 Go 中开发交互式命令行应用时,readline 是一个功能强大的库,它提供了类似 Bash 的行编辑功能、历史记录、自动补全等特性。下面我将详细介绍如何使用这个库来构建现代化的命令行界面。

readline 库简介

readline 库是 Go 中一个流行的命令行交互库,主要特点包括:

  • 行编辑功能(移动光标、删除字符等)
  • 历史记录管理
  • 自动补全支持
  • 自定义提示符
  • 支持彩色输出

安装

go get github.com/chzyer/readline

基础用法

package main

import (
	"fmt"
	"strings"
	
	"github.com/chzyer/readline"
)

func main() {
	// 创建 readline 实例
	rl, err := readline.New("> ")
	if err != nil {
		panic(err)
	}
	defer rl.Close()

	for {
		// 读取用户输入
		line, err := rl.Readline()
		if err != nil { // io.EOF 或其他错误
			break
		}

		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}

		// 处理命令
		switch line {
		case "exit", "quit":
			fmt.Println("Bye!")
			return
		default:
			fmt.Printf("You typed: %q\n", line)
		}
	}
}

高级功能实现

1. 添加历史记录功能

func main() {
	// 配置 readline
	rl, err := readline.NewEx(&readline.Config{
		Prompt:          "> ",
		HistoryFile:     "/tmp/readline-history.tmp", // 历史记录文件
		HistoryLimit:    1000,                        // 历史记录最大条数
		AutoComplete:    completer,                   // 自动补全器
		InterruptPrompt: "^C",                        // Ctrl+C 提示
		EOFPrompt:       "exit",                      // EOF 提示
	})
	if err != nil {
		panic(err)
	}
	defer rl.Close()

	// 使用方式与之前相同
}

2. 实现自动补全

var completer = readline.NewPrefixCompleter(
	readline.PcItem("mode",
		readline.PcItem("vi"),
		readline.PcItem("emacs"),
	),
	readline.PcItem("login"),
	readline.PcItem("say",
		readline.PcItem("hello"),
		readline.PcItem("world"),
	),
	readline.PcItem("set"),
	readline.PcItem("exit"),
	readline.PcItem("quit"),
	readline.PcItem("help"),
)

3. 自定义提示符(带颜色)

func dynamicPrompt() string {
	return fmt.Sprintf("\033[31m> \033[32m%s\033[0m ", time.Now().Format("15:04:05"))
}

func main() {
	rl, err := readline.NewEx(&readline.Config{
		Prompt:          dynamicPrompt(),
		HistoryFile:     "/tmp/readline-history.tmp",
		AutoComplete:    completer,
		InterruptPrompt: "^C",
		EOFPrompt:       "exit",
		// 启用动态提示
		FuncFilterInputRune: func(r rune) (rune, bool) {
			if r == readline.CharCtrlZ {
				return r, false
			}
			return r, true
		},
	})
	if err != nil {
		panic(err)
	}
	defer rl.Close()

	// 每行输入后更新提示
	rl.Config.Listener = &readline.DefaultListener{}
	
	// 使用方式与之前相同
}

4. 密码输入(隐藏输入)

func passwordInput(prompt string) (string, error) {
	rl, err := readline.NewEx(&readline.Config{
		Prompt:          prompt,
		EnableMask:      true,
		MaskRune:       '*',
		HistoryLimit:   -1, // 不记录历史
	})
	if err != nil {
		return "", err
	}
	defer rl.Close()
	
	line, err := rl.Readline()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(line), nil
}

实际应用示例

下面是一个更完整的示例,模拟一个简单的 shell:

package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/chzyer/readline"
)

var completer = readline.NewPrefixCompleter(
	readline.PcItem("help"),
	readline.PcItem("time"),
	readline.PcItem("echo"),
	readline.PcItem("exit"),
)

func main() {
	rl, err := readline.NewEx(&readline.Config{
		Prompt:          "\033[31m»\033[0m ",
		HistoryFile:     "/tmp/readline-demo.tmp",
		AutoComplete:    completer,
		InterruptPrompt: "^C",
		EOFPrompt:       "exit",
	})
	if err != nil {
		panic(err)
	}
	defer rl.Close()

	fmt.Println("Simple Shell v0.1")
	fmt.Println("Type 'help' for more information")

	for {
		line, err := rl.Readline()
		if err != nil { // io.EOF, readline.ErrInterrupt
			break
		}

		line = strings.TrimSpace(line)
		switch line {
		case "":
			continue
		case "help":
			fmt.Println("Available commands:")
			fmt.Println("  help - show this help")
			fmt.Println("  time - show current time")
			fmt.Println("  echo - echo back the input")
			fmt.Println("  exit - exit the program")
		case "time":
			fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
		case "echo":
			fmt.Println("You said: " + line)
		case "exit":
			fmt.Println("Goodbye!")
			return
		default:
			fmt.Println("Unknown command:", line)
		}
	}
}

最佳实践

  1. 错误处理:始终检查 readline 初始化错误
  2. 资源清理:使用 defer 关闭 readline 实例
  3. 历史记录:合理设置历史记录大小和存储位置
  4. 性能考虑:对于频繁更新的提示,考虑性能影响
  5. 跨平台:测试在不同终端下的表现

readline 库为 Go 命令行应用提供了强大的交互能力,结合其他库如 termuitview 可以构建更丰富的终端界面。

回到顶部