golang支持Emacs键绑定和语法高亮的命令行编辑插件go-readline-ny的使用

Golang 支持 Emacs 键绑定和语法高亮的命令行编辑插件 go-readline-ny 的使用

go-readline-ny 是一个为 Go 语言编写的 CUI 应用程序提供单行输入的库。它具有以下特性:

  • Emacs 风格的键绑定
  • 输入历史记录
  • 单词补全(文件名、命令名或给定数组中的任何名称)
  • 语法高亮
  • 支持平台:Windows 和 Linux
  • 完整的 Unicode (UTF8) 支持

简单示例

package main

import (
    "context"
    "fmt"

    "github.com/nyaosorg/go-readline-ny"
)

func main() {
    var editor readline.Editor
    text, err := editor.ReadLine(context.Background())
    if err != nil {
        fmt.Printf("ERR=%s\n", err.Error())
    } else {
        fmt.Printf("TEXT=%s\n", text)
    }
}

微型 Shell 示例

这个示例演示了提示符更改、语法高亮、文件名补全、历史记录浏览和操作系统剪贴板访问:

package main

import (
    "context"
    "fmt"
    "io"
    "os"
    "os/exec"
    "regexp"
    "strings"

    "github.com/atotto/clipboard"
    "github.com/mattn/go-colorable"

    "github.com/nyaosorg/go-readline-ny"
    "github.com/nyaosorg/go-readline-ny/completion"
    "github.com/nyaosorg/go-readline-ny/keys"
    "github.com/nyaosorg/go-readline-ny/simplehistory"
)

type OSClipboard struct{}

func (OSClipboard) Read() (string, error) {
    return clipboard.ReadAll()
}

func (OSClipboard) Write(s string) error {
    return clipboard.WriteAll(s)
}

func main() {
    history := simplehistory.New()

    editor := &readline.Editor{
        PromptWriter: func(w io.Writer) (int, error) {
            return io.WriteString(w, "\x1B[36;22m$ ") // 用青色打印 `$ `
        },
        Writer:  colorable.NewColorableStdout(),
        History: history,
        Highlight: []readline.Highlight{
            {Pattern: regexp.MustCompile("&"), Sequence: "\x1B[33;49;22m"},
            {Pattern: regexp.MustCompile(`"[^"]*"`), Sequence: "\x1B[35;49;22m"},
            {Pattern: regexp.MustCompile(`%[^%]*%`), Sequence: "\x1B[36;49;1m"},
            {Pattern: regexp.MustCompile("\u3000"), Sequence: "\x1B[37;41;22m"},
        },
        HistoryCycling: true,
        PredictColor:   [...]string{"\x1B[3;22;34m", "\x1B[23;39m"},
        ResetColor:     "\x1B[0m",
        DefaultColor:   "\x1B[33;49;1m",

        Clipboard: OSClipboard{},
    }

    editor.BindKey(keys.CtrlI, &completion.CmdCompletionOrList2{
        // 这里列出的字符将从补全中排除
        Delimiter: "&|><;",
        // 当候选包含空格时,用这些字符包围
        Enclosure: `"'`,
        // 当只有一个候选时附加的字符串
        Postfix: " ",
        // 用于列出候选的函数
        Candidates: completion.PathComplete,
    })

    fmt.Println("Tiny Shell. Type Ctrl-D to quit.")
    for {
        text, err := editor.ReadLine(context.Background())

        if err != nil {
            fmt.Printf("ERR=%s\n", err.Error())
            return
        }

        fields := strings.Fields(text)
        if len(fields) <= 0 {
            continue
        }
        cmd := exec.Command(fields[0], fields[1:]...)
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
        cmd.Stdin = os.Stdin

        cmd.Run()

        history.Add(text)
    }
}

自定义补全示例

package main

import (
    "context"
    "fmt"
    "io"
    "os"

    "github.com/nyaosorg/go-readline-ny"
    "github.com/nyaosorg/go-readline-ny/completion"
    "github.com/nyaosorg/go-readline-ny/keys"
)

func mains() error {
    var editor readline.Editor

    editor.PromptWriter = func(w io.Writer) (int, error) {
        return io.WriteString(w, "menu> ")
    }
    candidates := []string{"list", "say", "pewpew", "help", "exit", "Space Command"}

    editor.BindKey(keys.CtrlI, &completion.CmdCompletionOrList2{
        // 这里列出的字符将从补全中排除
        Delimiter: "&|><;",
        // 当候选包含空格时,用这些字符包围
        Enclosure: `"'`,
        // 当只有一个候选时附加的字符串
        Postfix: " ",
        // 用于列出候选的函数
        Candidates: func(field []string) (forComp []string, forList []string) {
            if len(field) <= 1 {
                return candidates, candidates
            }
            return nil, nil
        },
    })
    ctx := context.Background()
    for {
        line, err := editor.ReadLine(ctx)
        if err != nil {
            return err
        }
        fmt.Printf("TEXT=%#v\n", line)
    }
    return nil
}

func main() {
    if err := mains(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

特性截图

Zero-Width-Joiner sample on Windows-Terminal

Color Command Line

许可证

MIT 许可证


更多关于golang支持Emacs键绑定和语法高亮的命令行编辑插件go-readline-ny的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang支持Emacs键绑定和语法高亮的命令行编辑插件go-readline-ny的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Go-Readline-NY 使用指南

go-readline-ny 是一个支持 Emacs 键绑定和语法高亮的 Go 语言命令行编辑库,它是 GNU Readline 库的一个替代品。

安装

go get github.com/chzyer/readline

基本用法

package main

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

func main() {
	// 创建配置
	rl, err := readline.NewEx(&readline.Config{
		Prompt:          ">>> ",
		HistoryFile:     "/tmp/readline.tmp",
		AutoComplete:    completer, // 自动补全器
		InterruptPrompt: "^C",
		EOFPrompt:       "exit",
	})
	if err != nil {
		panic(err)
	}
	defer rl.Close()

	// 读取循环
	for {
		line, err := rl.Readline()
		if err != nil { // io.EOF, readline.ErrInterrupt
			break
		}
		fmt.Println("你输入的是:", line)
	}
}

Emacs 键绑定支持

go-readline-ny 默认支持 Emacs 风格的键绑定:

  • Ctrl+A - 移动到行首
  • Ctrl+E - 移动到行尾
  • Ctrl+B - 向后移动一个字符
  • Ctrl+F - 向前移动一个字符
  • Ctrl+D - 删除当前字符
  • Ctrl+K - 删除到行尾
  • Ctrl+U - 删除到行首
  • Ctrl+W - 删除前一个单词
  • Ctrl+Y - 粘贴
  • Ctrl+R - 反向搜索历史
  • Ctrl+L - 清屏
  • Tab - 自动补全

语法高亮配置

func main() {
	// 创建带语法高亮的配置
	rl, err := readline.NewEx(&readline.Config{
		Prompt: ">>> ",
		// 定义高亮规则
		Highlight: func(line []rune) []readline.Highlight {
			var highlights []readline.Highlight
			
			// 示例:高亮数字
			for i := 0; i < len(line); i++ {
				if line[i] >= '0' && line[i] <= '9' {
					start := i
					for ; i < len(line) && line[i] >= '0' && line[i] <= '9'; i++ {
					}
					highlights = append(highlights, readline.Highlight{
						Start: start,
						End:   i,
						Color: readline.Red,
					})
					i--
				}
			}
			return highlights
		},
	})
	
	// ... 其余代码同上
}

自动补全实现

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"),
	),
)

// 或者在配置中使用自定义补全函数
func completer(line []rune, pos int) (newLine [][]rune, length int) {
	// 实现自定义补全逻辑
	return nil, 0
}

高级配置示例

func main() {
	rl, err := readline.NewEx(&readline.Config{
		Prompt:          "\033[31m»\033[0m ",
		HistoryFile:    "/tmp/readline.tmp",
		HistoryLimit:   1000,
		AutoComplete:   completer,
		InterruptPrompt: "^C",
		EOFPrompt:      "exit",
		HistorySearchFold: true,  // 启用历史搜索折叠
		FuncFilterInputRune: filterInput, // 输入过滤
	})
	// ...
}

// 输入过滤函数
func filterInput(r rune) (rune, bool) {
	switch r {
	// 阻止输入控制字符
	case readline.CharCtrlZ, readline.CharCtrlY:
		return r, false
	}
	return r, true
}

注意事项

  1. 在 Windows 上可能需要额外的配置才能正常工作
  2. 复杂的语法高亮可能会影响性能
  3. 历史记录文件需要确保有写入权限

go-readline-ny 提供了丰富的功能来创建交互式命令行应用,同时保持了 Go 语言的简洁性。通过合理配置,可以创建出既美观又实用的命令行界面。

回到顶部