readline

readline library for MoonBit

libedit
readline
repl
terminal
moon add Kaida-Amethyst/readline@0.1.1
Download zip
Version
0.1.1
License
Apache-2.0
Last updated
3 days ago
Downloads
31
README

#Kaida-Amethyst/readline

面向 MoonBit native 后端的行编辑库。项目 API 仍在早期阶段;当前提供由私有 native owner 管理的 LineEditorHistory,支持同步行读取、prompt、Ctrl-C 恢复、 application name、Emacs/Vi 编辑模式、editrc 配置以及 History 文件持久化。复制公开 handle 只会共享同一个 owner;最后一个引用消失时自动释放底层资源,不需要调用 close()

///|
test {
let config = @readline.LineEditorConfig::new(
application_name="example-repl",
editing_mode=@readline.Emacs,
editrc=@readline.Disabled,
)
let history = @readline.History::new(capacity=100)
history.add("help")
let editor = @readline.LineEditor::open(config~, history~)
inspect(editor.history().length(), content="1")
}

#同步 REPL

read_line() 返回 Some(line) 时由应用决定是否加入 History;None 只表示 EOF。 Ctrl-C 会抛出可恢复的 Interrupted,当前未提交输入会被丢弃,同一个 editor 可以继续 读取。

///|
fn repl_loop(
editor : @readline.LineEditor,
history : @readline.History,
) -> Unit raise @readline.ReadlineError {
for ;; {
let input = editor.read_line(prompt=">> ") catch {
@readline.Interrupted => continue
error => raise error
}
match input {
None => break
Some(line) => {
history.add(line)
println("\{line} (\{history.length()})")
}
}
}
}

///|
fn run_repl_example() -> Unit raise @readline.ReadlineError {
let history = @readline.History::new()
let editor = @readline.LineEditor::open(history~)
repl_loop(editor, history)
}

#History 持久化

History 文件的加载和保存由应用显式控制;读取一行不会自动写入内存或文件。

///|
fn persist_history() -> Unit raise @readline.ReadlineError {
let history = @readline.History::new(capacity=1000)
history.add("help")
history.save("session.history")

let restored = @readline.History::load("session.history", capacity=1000)
println(restored.length())
}

预编译库默认缓存到:

$MOON_HOME/cache/lib/Kaida-Amethyst/readline.mbt/...

项目自身使用 Apache-2.0;随包分发的 libedit 源码及二进制保留上游许可, 详见 THIRD_PARTY_NOTICES.mdvendor/libedit/COPYING

#
ReadlineError

pub(all) suberror ReadlineError {
EmbeddedNul
UnsupportedLocale
InitializationFailed
ConfigurationFailed
InvalidHistoryCapacity(Int)
HistoryUpdateFailed
HistoryLoadFailed(String, Int?)
HistorySaveFailed(String, Int?)
InvalidUtf8
ReadFailed(Int)
Busy
Interrupted
} derive(
Debug
)

描述构造或配置行编辑器时可能发生的错误。

#
EditingMode

pub(all) enum EditingMode {
Emacs
Vi
} derive(
Debug
)

选择行编辑器使用的编辑命令集。

#
Editrc

pub(all) enum Editrc {
Disabled
Default
File(String)
} derive(
Debug
)

决定是否读取 editrc 命令,以及从何处读取。

#
History

pub struct History {
// private fields
}

表示一个有界的命令历史记录集合。

History 是可复制的共享 handle。不同副本共享记录和容量,不会复制底层资源。

Construction:

请使用 History::new 构造。

Lifecycle:

最后一个引用消失后,底层资源会被自动释放;调用者不需要也不能显式关闭。

#
History::add

fn History::add(self : History, line : StringView) -> Unit raise ReadlineError

原样添加一条记录。

空字符串和重复记录都会被保留;此操作不会 trim 或去重。超过容量时会自动丢弃最旧的 记录。

Errors:

  • line 包含 NUL 时抛出 EmbeddedNul
  • 无法更新 History 时抛出 HistoryUpdateFailed
  • 当前 History 正被行读取使用时抛出 Busy

#
History::clear

fn History::clear(self : History) -> Unit raise ReadlineError

清空当前 History 中的全部记录。

Errors:

当前 History 正被行读取使用时抛出 Busy

#
History::length

fn History::length(self : History) -> Int raise ReadlineError

返回当前保存的记录条数。

Errors:

  • 无法查询底层 History 状态时抛出 HistoryUpdateFailed
  • 当前 History 正被行读取使用时抛出 Busy

#
History::load

fn History::load(path : StringView, capacity? : Int) -> History raise ReadlineError

创建一个新的 History,并从文件加载记录。

加载成功后,超过容量的旧记录不会被保留。加载失败时不会返回半初始化对象,也不会 修改任何已有 History。

Parameters:

  • path:要读取的 History 文件路径。
  • capacity:最多保留的记录条数,必须大于零,默认为 1000。

Errors:

  • capacity 不大于零时抛出 InvalidHistoryCapacity
  • path 包含 NUL 时抛出 EmbeddedNul
  • 无法创建或配置底层资源时抛出 InitializationFailed HistoryUpdateFailed
  • 无法读取或解析文件时抛出 HistoryLoadFailed;错误中包含路径,以及底层能够可靠 提供时的 OS error code。

Side effects:

此操作会读取 path 指向的文件。

#
History::new

fn History::new(capacity? : Int) -> History raise ReadlineError

创建一个空的、有界的 History。

Parameters:

  • capacity:最多保留的记录条数,必须大于零,默认为 1000。

Errors:

  • capacity 不大于零时抛出 InvalidHistoryCapacity
  • 无法创建底层资源时抛出 InitializationFailed
  • 无法设置容量时抛出 HistoryUpdateFailed

Lifecycle:

返回值的所有副本共享同一个私有 owner,并由 finalizer 自动释放底层资源。

Examples:

test {
let history = History::new(capacity=100)
inspect(history.length(), content="0")
}

#
History::save

fn History::save(self : History, path : StringView) -> Unit raise ReadlineError

将当前全部记录保存到文件,并覆盖目标文件原有内容。

保存失败不会改变内存中的 History,但目标文件可能已经被创建或截断。

Parameters:

  • path:要覆盖写入的文件路径。

Errors:

  • path 包含 NUL 时抛出 EmbeddedNul
  • 无法写入文件时抛出 HistorySaveFailed;错误中包含路径,以及底层能够可靠提供时的 OS error code。
  • 当前 History 正被行读取使用时抛出 Busy

Side effects:

此操作会创建或覆盖 path 指向的文件。

#
LineEditor

pub struct LineEditor {
// private fields
}

表示一个可长期持有的行编辑上下文。

同一个 LineEditor 的不同副本共享私有 owner 和编辑状态,不会创建额外的 native 编辑器。

Construction:

请使用 LineEditor::open 构造,也可以传入可重复使用的 LineEditorConfig History

Lifecycle:

最后一个引用消失后,native 编辑器会被自动释放。调用者不需要也不能显式关闭 LineEditor,任一别名也不会使其他别名失效。

#
LineEditor::history

fn LineEditor::history(self : LineEditor) -> History

返回当前连接的 History 共享 handle。

返回值与构造时传入或默认创建的 History 共享同一个 owner。修改任一 handle 都会影响 行编辑器后续使用的历史记录。

#
LineEditor::open

fn LineEditor::open(config? : LineEditorConfig, history? : History) -> LineEditor raise ReadlineError

基于进程的标准输入输出创建并完整配置一个行编辑器。

配置按以下顺序应用:建立默认配置、读取所选 editrc、应用显式编辑模式、连接 History。 默认 editrc 不存在时会被忽略;显式指定的文件不存在时会报告错误。

Parameters:

  • config:不可变的构造选项。省略时,应用名称为 readline.mbt,存在默认 editrc 时读取该文件,并且不使用显式编辑模式覆盖配置。
  • history:要连接的 History。省略时创建容量为 1000 的空 History。

Errors:

  • 应用名称或显式 editrc 路径包含 NUL 时抛出 EmbeddedNul
  • 无法创建线程局部的 UTF-8 locale 时抛出 UnsupportedLocale
  • 无法创建底层编辑器时抛出 InitializationFailed
  • 无法应用 editrc、显式编辑模式或 History 连接时抛出 ConfigurationFailed
  • 无法创建默认 History 时抛出相应的 History 构造错误。

Lifecycle:

返回值的所有副本共享同一个私有 owner 和已连接的 History。editor owner 在底层编辑器 存活期间强引用 History owner,并在释放底层编辑器后才释放该引用。两种资源都不提供 公开的关闭操作。

Side effects:

启用 editrc 时,构造过程会打开相应文件;此操作不会修改进程全局 locale。

Thread safety:

当前不保证可以跨线程使用。同步终端读取的并发约束将在读取接口中说明。

Examples:

test {
let config = LineEditorConfig::new(editrc=Disabled)
let history = History::new(capacity=100)
let editor = LineEditor::open(config~, history~)
inspect(editor.history().length(), content="0")
}

#
LineEditor::read_line

fn LineEditor::read_line(self : LineEditor, prompt? : StringView) -> String? raise ReadlineError

同步读取并编辑一行文本。

返回 Some(line) 表示用户提交了一行,返回 None 只表示正常 EOF。直接回车返回 Some(""),不会与 EOF 混淆。返回文本由 MoonBit 独立持有,不包含末尾换行符。 此方法不会自动将返回值加入 History。

Parameters:

  • prompt:本次读取期间显示的提示符,默认为空字符串,不会成为长期配置。

Errors:

  • prompt 包含 NUL,或底层行数据包含 NUL 时抛出 EmbeddedNul
  • 底层行数据不是合法 UTF-8 时抛出 InvalidUtf8
  • 同一进程已有活跃的同步读取,或所连接的 History 正在使用时抛出 Busy
  • 用户按下 Ctrl-C 中断本次输入时抛出 Interrupted;未提交输入会被丢弃,editor 可以继续读取。
  • 发生读取错误时抛出 ReadFailed,并携带 C 边界立即保存的 OS error code;零表示 没有可靠错误码。

Lifecycle:

prompt 及底层返回缓冲区只在本次调用期间使用。方法返回或抛错前会复制结果,并恢复 editor、History、locale、signal handler、signal mask 和进程级读取 guard。

Side effects:

此方法会阻塞并暂时接管标准终端的行编辑状态。

Thread safety:

多个 LineEditor 可以共存和顺序读取,但整个进程同一时刻只允许一个活跃读取;冲突 不等待,直接抛出 Busy

Examples:

let editor = LineEditor::open()
match editor.read_line(prompt=">> ") {
Some(line) => println(line)
None => println("EOF")
}

#
LineEditorConfig

pub struct LineEditorConfig {
// private fields
}

保存用于构造 LineEditor 的不可变选项。

配置中只包含由 MoonBit 持有的值,因此可以安全复制,也可以重复用于创建彼此独立的 编辑器。

Construction:

请使用 LineEditorConfig::new 构造;所有字段均有意保持私有。

#
LineEditorConfig::new

fn LineEditorConfig::new(application_name? : StringView, editing_mode? : EditingMode, editrc? : Editrc) -> LineEditorConfig

创建用于构造行编辑器的不可变选项。

Parameters:

  • application_name:标识当前应用,并供 editrc 条件命令匹配。
  • editing_mode:在读取 editrc 后显式选择 Emacs 或 Vi 命令集;省略时由默认配置或 editrc 决定。
  • editrc:控制是否以及如何读取 editrc;默认按照 EDITRCHOME 的标准规则 查找。

Examples:

test {
let config = LineEditorConfig::new(
application_name="example-repl",
editing_mode=Vi,
editrc=Disabled,
)
config |> ignore
}