print!() 格式化带有终端转义码的字符串
print!() formatting for strings with terminal escape codes
我正在编写一个应该列出目录中文件的小程序。
类似于 'ls' 所做的,但需要做其他事情。
我 运行 在将文件列表打印到屏幕时遇到了问题。
所有文件都分类为目录、可执行文件、符号链接等,因此附有特殊的 ANSI 转义码。
{} 中的宽度格式存在问题。
┆ Library / ┆ prcomp / ┆.tmux.conf@
┆ Movies / ┆ quicklisp / ┆.viminfo
┆ Music / ┆.CFUserTextEncoding ┆.vimrc@
1 [Terminal Output]
/ = Directories , @ = symlinks
看到 .CFUserTextEncoding
和 .vimrc@
之间的尴尬间距了吗?
问题是不同的格式有不同的转义码和转义码本身的可变长度
┆[47;30m Library [0m/ ┆[47;30m prcomp [0m/ ┆[1;3;4;36m.tmux.conf[0m@
┆[47;30m Movies [0m/ ┆[47;30m quicklisp [0m/ ┆[1;38;5;15m.viminfo[0m
┆[47;30m Music [0m/ ┆[1;38;5;15m.CFUserTextEncoding[0m ┆[1;3;4;36m.vimrc[0m@
2 [Raw Output]
,îÜ = Escape character
因此,由于终端将转义字符转换为颜色并且字符串按预期显示 'trimmed',因此忽略它格式化的任何宽度。但这 'trimming' 导致格式缩短,整个格式看起来一团糟。
格式更改的所有地方都会导致格式混乱。
我试图通过使用填充向量 (vec![" "; space_count],join("")
) 来解决这个问题,但这是一个完全不同的混乱领域,这个本机格式化程序到目前为止给出了最干净的结果。
有没有办法绕过转义字符并对齐文本?
编辑:
一个简单的可重现示例:
use std::{env , fs, path::PathBuf};
use ansi_term::Colour;
use term_size;
fn main() {
println!("{esc}[2J{esc}[1;1H", esc = 27 as char); // clear
let term_cols = match term_size::dimensions() { Some((cols,_)) => cols, None => 0};
let mut files: Vec<PathBuf> = fs::read_dir(env::current_dir().unwrap())
.unwrap()
.filter_map(|file| Some(file.unwrap().path()))
.collect();
files.sort();
let max_columns = term_cols / 30 ;
let max_rows = (files.len() / max_columns) + 1;
for i in 0..max_rows {
for j in 0..max_columns {
let index = i+(max_rows*j);
if index >= files.len() { break; }
if files[index].is_dir() {
print!("|{:<1$}",format!("{}", Colour::Black.on(Colour::White)
.paint(files[index].file_name()
.unwrap()
.to_str()
.unwrap())), 25);
}
else if files[index].symlink_metadata().unwrap().file_type().is_symlink() {
print!("|{:<1$}", format!("{}",Colour::Cyan.bold()
.italic()
.underline()
.paint(files[index].file_name()
.unwrap()
.to_str()
.unwrap())), 25);
}
else {
print!("|{:<1$}", format!("{}", Colour::Fixed(15).bold()
.paint(files[index].file_name()
.unwrap()
.to_str()
.unwrap())), 25);
}
}
print!("\n");
}
}
Depends on
这是代码的简化版本。
fmt 格式化程序不知道 CSI 转义序列,因此您需要自己进行一些计算或使用专用的 crate。
在这里计算它并不难:
- 为每个单元格计算底层字符串(没有样式的字符串)的可见长度。您应该使用 unicode-width crate,这样当一个字符在屏幕上占用 2 个终端列或两个字符组合成仅占用一个终端列时,您的代码就不会中断
- 将每列的宽度计算为其单元格的最大值
- 在样式化的字符串之后 and/or 之前打印空格,以使总和等于列宽
现在,由现有的 crate 完成此计算也是合理的。有几个。我不会给出名字,因为我是其中一些作者的作者,我不想在答案中做广告,但您可以在 https://crates.io.
上搜索“终端”箱子
我正在编写一个应该列出目录中文件的小程序。 类似于 'ls' 所做的,但需要做其他事情。
我 运行 在将文件列表打印到屏幕时遇到了问题。
所有文件都分类为目录、可执行文件、符号链接等,因此附有特殊的 ANSI 转义码。
{} 中的宽度格式存在问题。
┆ Library / ┆ prcomp / ┆.tmux.conf@
┆ Movies / ┆ quicklisp / ┆.viminfo
┆ Music / ┆.CFUserTextEncoding ┆.vimrc@
1 [Terminal Output]
/ = Directories , @ = symlinks
看到 .CFUserTextEncoding
和 .vimrc@
之间的尴尬间距了吗?
问题是不同的格式有不同的转义码和转义码本身的可变长度
┆[47;30m Library [0m/ ┆[47;30m prcomp [0m/ ┆[1;3;4;36m.tmux.conf[0m@
┆[47;30m Movies [0m/ ┆[47;30m quicklisp [0m/ ┆[1;38;5;15m.viminfo[0m
┆[47;30m Music [0m/ ┆[1;38;5;15m.CFUserTextEncoding[0m ┆[1;3;4;36m.vimrc[0m@
2 [Raw Output]
,îÜ = Escape character
因此,由于终端将转义字符转换为颜色并且字符串按预期显示 'trimmed',因此忽略它格式化的任何宽度。但这 'trimming' 导致格式缩短,整个格式看起来一团糟。
格式更改的所有地方都会导致格式混乱。
我试图通过使用填充向量 (vec![" "; space_count],join("")
) 来解决这个问题,但这是一个完全不同的混乱领域,这个本机格式化程序到目前为止给出了最干净的结果。
有没有办法绕过转义字符并对齐文本?
编辑:
一个简单的可重现示例:
use std::{env , fs, path::PathBuf};
use ansi_term::Colour;
use term_size;
fn main() {
println!("{esc}[2J{esc}[1;1H", esc = 27 as char); // clear
let term_cols = match term_size::dimensions() { Some((cols,_)) => cols, None => 0};
let mut files: Vec<PathBuf> = fs::read_dir(env::current_dir().unwrap())
.unwrap()
.filter_map(|file| Some(file.unwrap().path()))
.collect();
files.sort();
let max_columns = term_cols / 30 ;
let max_rows = (files.len() / max_columns) + 1;
for i in 0..max_rows {
for j in 0..max_columns {
let index = i+(max_rows*j);
if index >= files.len() { break; }
if files[index].is_dir() {
print!("|{:<1$}",format!("{}", Colour::Black.on(Colour::White)
.paint(files[index].file_name()
.unwrap()
.to_str()
.unwrap())), 25);
}
else if files[index].symlink_metadata().unwrap().file_type().is_symlink() {
print!("|{:<1$}", format!("{}",Colour::Cyan.bold()
.italic()
.underline()
.paint(files[index].file_name()
.unwrap()
.to_str()
.unwrap())), 25);
}
else {
print!("|{:<1$}", format!("{}", Colour::Fixed(15).bold()
.paint(files[index].file_name()
.unwrap()
.to_str()
.unwrap())), 25);
}
}
print!("\n");
}
}
Depends on
这是代码的简化版本。
fmt 格式化程序不知道 CSI 转义序列,因此您需要自己进行一些计算或使用专用的 crate。
在这里计算它并不难:
- 为每个单元格计算底层字符串(没有样式的字符串)的可见长度。您应该使用 unicode-width crate,这样当一个字符在屏幕上占用 2 个终端列或两个字符组合成仅占用一个终端列时,您的代码就不会中断
- 将每列的宽度计算为其单元格的最大值
- 在样式化的字符串之后 and/or 之前打印空格,以使总和等于列宽
现在,由现有的 crate 完成此计算也是合理的。有几个。我不会给出名字,因为我是其中一些作者的作者,我不想在答案中做广告,但您可以在 https://crates.io.
上搜索“终端”箱子