use std::io::{self, Read, Write};
use std::time::Duration;
use anyhow::{Context, Result};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use crate::daemon::protocol::{CursorPos, Request, Response};
use crate::daemon::server::{ensure_daemon, send_request};
pub async fn run(initial_name: String) -> Result<()> {
ensure_daemon()?;
let mut tty = RawTerminal::enter()?;
loop {
let sessions = get_session_names().await.unwrap_or_default();
if !sessions.is_empty() {
break;
}
draw_waiting_screen()?;
if let Some(Key::Quit) = tty.read_key(Duration::from_millis(250))? {
drop(tty);
return Ok(());
}
}
let sessions = get_session_names().await.unwrap_or_default();
let mut current_idx = sessions
.iter()
.position(|s| s == &initial_name)
.unwrap_or(0);
let result = run_loop(&mut tty, &mut current_idx).await;
drop(tty);
result
}
const FRAME_INTERVAL_MS: u64 = 33;
async fn run_loop(tty: &mut RawTerminal, current_idx: &mut usize) -> Result<()> {
let mut last_rows: Option<Vec<String>> = None;
let mut last_term_size = get_terminal_size();
let mut last_fetch = std::time::Instant::now() - Duration::from_secs(10); let mut last_change = std::time::Instant::now();
let mut needs_clear = true;
let mut prev_frame: Option<Vec<String>> = None;
let mut tab_hits: Vec<TabHit> = Vec::new();
let fetch_interval = Duration::from_millis(FRAME_INTERVAL_MS);
loop {
let term_size = get_terminal_size();
if term_size != last_term_size {
last_term_size = term_size;
last_rows = None;
needs_clear = true;
prev_frame = None;
last_fetch = std::time::Instant::now() - fetch_interval; }
if last_fetch.elapsed() >= fetch_interval {
last_fetch = std::time::Instant::now();
let sessions = get_session_names().await.unwrap_or_default();
if sessions.is_empty() {
draw_waiting_screen()?;
needs_clear = true;
prev_frame = None;
match tty.read_key(Duration::from_millis(FRAME_INTERVAL_MS))? {
Some(Key::Quit) => break,
_ => continue,
}
}
if *current_idx >= sessions.len() {
*current_idx = sessions.len() - 1;
}
let session_name = &sessions[*current_idx];
match send_request(&Request::ScreenshotCells {
name: session_name.clone(),
})
.await
{
Ok(Response::ScreenshotCells {
rows_ansi,
rows,
cols,
mouse_cursor,
mouse_held,
}) => {
let changed = last_rows.as_ref() != Some(&rows_ansi);
if changed {
last_change = std::time::Instant::now();
last_rows = Some(rows_ansi.clone());
}
let new_frame = build_frame_strings(
&sessions,
*current_idx,
&rows_ansi,
rows,
cols,
term_size,
last_change.elapsed(),
mouse_cursor,
mouse_held,
);
emit_frame_diff(
needs_clear,
prev_frame.as_deref(),
&new_frame.lines,
&sessions[*current_idx],
)?;
needs_clear = false;
tab_hits = new_frame.tab_hits;
prev_frame = Some(new_frame.lines);
}
Ok(Response::Error { message: _ }) => {
last_rows = None;
needs_clear = true;
prev_frame = None;
}
_ => {}
}
}
match tty.read_key(Duration::from_millis(FRAME_INTERVAL_MS))? {
Some(Key::Quit) => break,
Some(Key::Left) if *current_idx > 0 => {
*current_idx -= 1;
last_rows = None;
needs_clear = true;
prev_frame = None;
last_fetch = std::time::Instant::now() - fetch_interval; }
Some(Key::Right) => {
let sessions = get_session_names().await.unwrap_or_default();
if *current_idx + 1 < sessions.len() {
*current_idx += 1;
last_rows = None;
needs_clear = true;
prev_frame = None;
last_fetch = std::time::Instant::now() - fetch_interval;
}
}
Some(Key::Click { row, col }) => {
if let Some(name) = tab_hits
.iter()
.find(|h| h.term_row == row && col >= h.start_col && col <= h.end_col)
.map(|h| h.name.clone())
{
let sessions = get_session_names().await.unwrap_or_default();
if let Some(idx) = sessions.iter().position(|s| s == &name) {
if idx != *current_idx {
*current_idx = idx;
last_rows = None;
needs_clear = true;
prev_frame = None;
last_fetch = std::time::Instant::now() - fetch_interval;
}
}
}
}
Some(Key::Left) | None => {}
}
}
Ok(())
}
async fn get_session_names() -> Result<Vec<String>> {
match send_request(&Request::List).await? {
Response::SessionList { sessions } => Ok(sessions.into_iter().map(|s| s.name).collect()),
_ => Ok(vec![]),
}
}
fn draw_waiting_screen() -> Result<()> {
let (cols, rows) = get_terminal_size();
let mut out = io::stdout().lock();
write!(out, "\x1b[H\x1b[2J")?;
let line1 = "terminal-use";
let line2 = "Waiting for sessions...";
let line3 = "Ctrl+C to quit";
let box_width = 32;
let pad_x = (cols as usize).saturating_sub(box_width) / 2;
let mid_row = (rows / 2).saturating_sub(2).max(1);
let p = " ".repeat(pad_x);
write!(
out,
"\x1b[{};1H{p}\x1b[90m┌{}┐\x1b[0m",
mid_row,
"─".repeat(box_width - 2)
)?;
write!(
out,
"\x1b[{};1H{p}\x1b[90m│\x1b[0m\x1b[1m{:^w$}\x1b[0m\x1b[90m│\x1b[0m",
mid_row + 1,
line1,
w = box_width - 2
)?;
write!(
out,
"\x1b[{};1H{p}\x1b[90m│{:^w$}│\x1b[0m",
mid_row + 2,
line2,
w = box_width - 2
)?;
write!(
out,
"\x1b[{};1H{p}\x1b[90m│{:^w$}│\x1b[0m",
mid_row + 3,
"",
w = box_width - 2
)?;
write!(
out,
"\x1b[{};1H{p}\x1b[90m│\x1b[2m{:^w$}\x1b[0m\x1b[90m│\x1b[0m",
mid_row + 4,
line3,
w = box_width - 2
)?;
write!(
out,
"\x1b[{};1H{p}\x1b[90m└{}┘\x1b[0m",
mid_row + 5,
"─".repeat(box_width - 2)
)?;
out.flush()?;
Ok(())
}
fn get_terminal_size() -> (u16, u16) {
unsafe {
let mut ws: nix::libc::winsize = std::mem::zeroed();
if nix::libc::ioctl(1, nix::libc::TIOCGWINSZ, &mut ws) == 0 && ws.ws_col > 0 {
(ws.ws_col, ws.ws_row)
} else {
(80, 24)
}
}
}
fn format_elapsed(d: Duration) -> String {
let secs = d.as_secs();
if secs < 60 {
format!("{}s ago", secs)
} else if secs < 3600 {
format!("{}m ago", secs / 60)
} else {
format!("{}h ago", secs / 3600)
}
}
struct Frame {
lines: Vec<String>,
tab_hits: Vec<TabHit>,
}
#[allow(clippy::too_many_arguments)]
fn build_frame_strings(
sessions: &[String],
active_idx: usize,
rows_ansi: &[String],
sess_rows: u16,
sess_cols: u16,
term_size: (u16, u16),
since_last_change: Duration,
mouse_cursor: Option<CursorPos>,
mouse_held: bool,
) -> Frame {
let (term_cols, term_rows) = term_size;
let multi = sessions.len() > 1;
let (tab_lines, tab_hits) = if multi {
layout_tabs(sessions, active_idx, term_cols, 2)
} else {
(Vec::new(), Vec::new())
};
let frame_width = sess_cols as usize + 2;
let tcols = term_cols as usize;
let cropped_right = tcols < frame_width;
let header_rows = 1 + tab_lines.len() as u16 + 1;
let available_content_rows = term_rows.saturating_sub(header_rows + 1);
let content_rows_to_show = sess_rows.min(available_content_rows);
let cropped_bottom = content_rows_to_show < sess_rows;
let mut frame: Vec<String> = Vec::with_capacity(term_rows as usize);
let elapsed = format_elapsed(since_last_change);
let status = if multi {
format!("terminal-use monitor · last change {elapsed} · ← → or click · Ctrl+C detach")
} else {
format!("terminal-use monitor · last change {elapsed} · Ctrl+C detach")
};
let status = truncate_ansi_visible(&status, tcols);
frame.push(format!("\x1b[90m{status}\x1b[0m"));
for line in tab_lines {
let clipped = truncate_ansi_visible(&line, tcols);
frame.push(format!("{clipped}\x1b[0m"));
}
{
let title = format!(" {} [{}x{}] ", sessions[active_idx], sess_cols, sess_rows);
let prefix_width = 2 + title.width();
let line = if cropped_right {
let dash_space = tcols.saturating_sub(prefix_width);
let (dashes, suffix) = if dash_space > 3 {
("─".repeat(dash_space - 3), "···")
} else {
("─".repeat(dash_space), "")
};
format!("\x1b[90m┌─\x1b[0m\x1b[1m{title}\x1b[0m\x1b[90m{dashes}{suffix}\x1b[0m")
} else {
let dashes = "─".repeat(frame_width.saturating_sub(prefix_width + 1));
format!("\x1b[90m┌─\x1b[0m\x1b[1m{title}\x1b[0m\x1b[90m{dashes}┐\x1b[0m")
};
frame.push(line);
}
let fade_start = if cropped_bottom {
content_rows_to_show.saturating_sub(3) as usize
} else {
usize::MAX
};
for r in 0..content_rows_to_show as usize {
let line = rows_ansi.get(r).map(|s| s.as_str()).unwrap_or("");
let left_border = if r >= fade_start { "·" } else { "│" };
let mut row_str = if cropped_right {
let max_visible = tcols.saturating_sub(2);
let clipped = truncate_ansi_visible(line, max_visible);
format!("\x1b[90m{left_border}\x1b[0m{clipped}\x1b[0m")
} else {
let right_border = if r >= fade_start { "·" } else { "│" };
format!(
"\x1b[90m{left_border}\x1b[0m{line}\x1b[0m\x1b[{col}G\x1b[90m{right_border}\x1b[0m",
col = frame_width,
)
};
if let Some(cursor) = mouse_cursor {
if cursor.row as usize == r && cursor.col < sess_cols {
let term_row = frame.len() as u16 + 1; let term_col = 2 + cursor.col; let max_col = if term_cols >= 2 { term_cols } else { 1 };
if term_col <= max_col {
row_str.push_str(&format!(
"\x1b[{term_row};{term_col}H{}\x1b[0m",
mouse_cursor_glyph(mouse_held)
));
}
}
}
frame.push(row_str);
}
if !cropped_bottom {
let line = if cropped_right {
let dash_space = tcols.saturating_sub(1);
let (dashes, suffix) = if dash_space > 3 {
("─".repeat(dash_space - 3), "···")
} else {
("─".repeat(dash_space), "")
};
format!("\x1b[90m└{dashes}{suffix}\x1b[0m")
} else {
let dashes = "─".repeat(frame_width.saturating_sub(2));
format!("\x1b[90m└{dashes}┘\x1b[0m")
};
frame.push(line);
}
Frame {
lines: frame,
tab_hits,
}
}
fn truncate_ansi_visible(line: &str, max_visible: usize) -> String {
if max_visible == 0 {
return String::new();
}
let mut out = String::with_capacity(line.len());
let mut visible = 0usize;
let mut chars = line.chars().peekable();
while let Some(c) = chars.next() {
if c == '\x1b' {
out.push(c);
let Some(&next) = chars.peek() else { break };
chars.next();
out.push(next);
match next {
'[' => {
while let Some(&p) = chars.peek() {
chars.next();
out.push(p);
let b = p as u32;
if (0x40..=0x7E).contains(&b) {
break;
}
}
}
']' => {
while let Some(&p) = chars.peek() {
chars.next();
out.push(p);
if p == '\x07' {
break;
}
if p == '\x1b' {
if let Some(&q) = chars.peek() {
chars.next();
out.push(q);
if q == '\\' {
break;
}
}
}
}
}
_ => {}
}
} else {
let w = UnicodeWidthChar::width(c).unwrap_or(0);
if visible + w > max_visible {
break;
}
out.push(c);
visible += w;
}
}
out
}
fn mouse_cursor_glyph(held: bool) -> &'static str {
if held {
"\x1b[1;48;5;201;97m△"
} else {
"\x1b[1;38;5;201m△"
}
}
fn emit_frame_diff(
needs_clear: bool,
prev: Option<&[String]>,
new: &[String],
session_name: &str,
) -> Result<()> {
let mut out = io::stdout().lock();
if needs_clear {
write!(out, "\x1b[2J\x1b[H")?;
write!(out, "\x1b]0;tu monitor: {session_name}\x07")?;
}
let prev_len = if needs_clear {
0
} else {
prev.map(|p| p.len()).unwrap_or(0)
};
for (i, line) in new.iter().enumerate() {
let unchanged = !needs_clear
&& prev
.and_then(|p| p.get(i))
.map(|s| s == line)
.unwrap_or(false);
if unchanged {
continue;
}
let row = i + 1;
write!(out, "\x1b[{row};1H\x1b[2K{line}")?;
}
if new.len() < prev_len {
write!(out, "\x1b[{};1H\x1b[J", new.len() + 1)?;
}
out.flush()?;
Ok(())
}
#[derive(Clone, Debug, PartialEq)]
struct TabHit {
term_row: u16,
start_col: u16,
end_col: u16,
name: String,
}
fn layout_tabs(
sessions: &[String],
active_idx: usize,
term_cols: u16,
base_row: u16,
) -> (Vec<String>, Vec<TabHit>) {
let max = (term_cols.max(1)) as usize;
let mut lines: Vec<String> = Vec::new();
let mut hits: Vec<TabHit> = Vec::new();
let mut cur = String::new();
let mut col = 0usize;
for (i, name) in sessions.iter().enumerate() {
let label_w = UnicodeWidthStr::width(name.as_str()) + 2; let sep_w = if col > 0 { 3 } else { 0 }; if col > 0 && col + sep_w + label_w > max {
lines.push(std::mem::take(&mut cur));
col = 0;
}
if col > 0 {
cur.push_str("\x1b[90m │ \x1b[0m");
col += 3;
}
let start_col = col + 1; if i == active_idx {
cur.push_str(&format!("\x1b[1;7m {} \x1b[0m", name));
} else {
cur.push_str(&format!("\x1b[2m {} \x1b[0m", name));
}
col += label_w;
hits.push(TabHit {
term_row: base_row + lines.len() as u16,
start_col: start_col as u16,
end_col: col as u16, name: name.clone(),
});
}
lines.push(cur);
(lines, hits)
}
enum Key {
Quit,
Left,
Right,
Click {
row: u16,
col: u16,
},
}
struct RawTerminal {
original_termios: nix::sys::termios::Termios,
}
impl RawTerminal {
fn enter() -> Result<Self> {
use nix::sys::termios::{self, InputFlags, LocalFlags};
if !std::io::IsTerminal::is_terminal(&io::stdin()) {
anyhow::bail!("monitor requires a real terminal (TTY)");
}
let original = termios::tcgetattr(io::stdin()).context("tcgetattr")?;
let mut raw = original.clone();
raw.local_flags &= !(LocalFlags::ICANON | LocalFlags::ECHO | LocalFlags::ISIG);
raw.input_flags &= !(InputFlags::IXON | InputFlags::ICRNL);
raw.control_chars[nix::sys::termios::SpecialCharacterIndices::VMIN as usize] = 0;
raw.control_chars[nix::sys::termios::SpecialCharacterIndices::VTIME as usize] = 0;
termios::tcsetattr(io::stdin(), termios::SetArg::TCSANOW, &raw).context("tcsetattr raw")?;
print!("\x1b[?1049h\x1b[?25l\x1b[?1000h\x1b[?1006h");
io::stdout().flush()?;
Ok(Self {
original_termios: original,
})
}
fn read_key(&self, timeout: Duration) -> Result<Option<Key>> {
use std::os::fd::AsRawFd;
let stdin_fd = io::stdin().as_raw_fd();
let mut pollfd = nix::poll::PollFd::new(
unsafe { std::os::fd::BorrowedFd::borrow_raw(stdin_fd) },
nix::poll::PollFlags::POLLIN,
);
let timeout_ms = timeout.as_millis() as u16;
let ready = nix::poll::poll(std::slice::from_mut(&mut pollfd), timeout_ms).unwrap_or(0);
if ready == 0 {
return Ok(None);
}
let mut buf = [0u8; 32];
let n = io::stdin().lock().read(&mut buf).unwrap_or(0);
if n == 0 {
return Ok(None);
}
if buf[0] == 0x03 || buf[0] == b'q' {
return Ok(Some(Key::Quit));
}
if n >= 4 && buf[0] == 0x1b && buf[1] == b'[' && buf[2] == b'<' {
return Ok(parse_sgr_mouse(&buf[3..n]));
}
if n >= 3 && buf[0] == 0x1b {
if (buf[1] == b'[' || buf[1] == b'O') && buf[2] == b'C' {
return Ok(Some(Key::Right));
}
if (buf[1] == b'[' || buf[1] == b'O') && buf[2] == b'D' {
return Ok(Some(Key::Left));
}
}
Ok(None)
}
}
fn parse_sgr_mouse(body: &[u8]) -> Option<Key> {
let term_pos = body.iter().position(|&c| c == b'M' || c == b'm')?;
let is_press = body[term_pos] == b'M';
let nums = std::str::from_utf8(&body[..term_pos]).ok()?;
let mut parts = nums.split(';');
let button: u32 = parts.next()?.parse().ok()?;
let col: u16 = parts.next()?.parse().ok()?;
let row: u16 = parts.next()?.parse().ok()?;
if parts.next().is_some() {
return None; }
const MODIFIERS: u32 = 4 | 8 | 16;
if is_press && (button & !MODIFIERS) == 0 {
Some(Key::Click { row, col })
} else {
None
}
}
impl Drop for RawTerminal {
fn drop(&mut self) {
print!("\x1b[?1000l\x1b[?1006l\x1b[?25h\x1b[?1049l");
let _ = io::stdout().flush();
let _ = nix::sys::termios::tcsetattr(
io::stdin(),
nix::sys::termios::SetArg::TCSANOW,
&self.original_termios,
);
}
}
#[cfg(test)]
mod tests {
use super::{build_frame_strings, layout_tabs, parse_sgr_mouse, truncate_ansi_visible, Key};
fn names(n: usize) -> Vec<String> {
(1..=n).map(|i| format!("win{i}")).collect()
}
fn visible_width(line: &str) -> usize {
let mut count = 0;
let mut chars = line.chars().peekable();
while let Some(c) = chars.next() {
if c == '\x1b' {
if chars.peek() == Some(&'[') {
chars.next();
while let Some(&p) = chars.peek() {
chars.next();
if (0x40..=0x7E).contains(&(p as u32)) {
break;
}
}
}
} else {
count += 1;
}
}
count
}
#[test]
fn header_lines_never_exceed_terminal_width() {
let sessions = names(15);
let rows_ansi: Vec<String> = (0..40).map(|_| String::new()).collect();
let frame = build_frame_strings(
&sessions,
0,
&rows_ansi,
40,
120,
(60, 44),
std::time::Duration::from_secs(5),
None,
false,
);
for line in &frame.lines {
assert!(
visible_width(line) <= 60,
"row exceeds terminal width: {line:?}"
);
}
}
#[test]
fn layout_single_line_when_it_fits() {
let sessions = names(3);
let (lines, hits) = layout_tabs(&sessions, 0, 200, 2);
assert_eq!(lines.len(), 1);
assert_eq!(hits.len(), 3);
assert!(hits.iter().all(|h| h.term_row == 2));
assert_eq!((hits[0].start_col, hits[0].end_col), (1, 6));
assert_eq!(hits[1].start_col, 10);
}
#[test]
fn layout_wraps_when_too_many_tabs() {
let sessions = names(12);
let (lines, hits) = layout_tabs(&sessions, 0, 30, 2);
assert!(
lines.len() > 1,
"expected wrapping, got {} line(s)",
lines.len()
);
assert_eq!(hits.len(), 12);
assert!(hits.iter().any(|h| h.term_row > 2));
assert!(hits.iter().all(|h| h.start_col >= 1 && h.start_col <= 30));
for w in hits.windows(2) {
assert!(w[1].term_row >= w[0].term_row);
}
}
#[test]
fn layout_hit_columns_match_label_width() {
let sessions = names(1);
let (_lines, hits) = layout_tabs(&sessions, 0, 80, 2);
assert_eq!((hits[0].start_col, hits[0].end_col), (1, 6));
}
#[test]
fn parse_left_press_returns_click() {
match parse_sgr_mouse(b"0;12;3M") {
Some(Key::Click { row, col }) => {
assert_eq!((row, col), (3, 12));
}
other => panic!("expected Click, got {:?}", other.is_some()),
}
}
#[test]
fn parse_release_is_ignored() {
assert!(parse_sgr_mouse(b"0;12;3m").is_none());
}
#[test]
fn parse_non_left_button_is_ignored() {
assert!(parse_sgr_mouse(b"2;12;3M").is_none());
assert!(parse_sgr_mouse(b"64;12;3M").is_none());
assert!(parse_sgr_mouse(b"32;12;3M").is_none());
}
#[test]
fn parse_modified_left_click_returns_click() {
for b in [4u32, 8, 16, 28] {
match parse_sgr_mouse(format!("{b};9;2M").as_bytes()) {
Some(Key::Click { row, col }) => assert_eq!((row, col), (2, 9)),
_ => panic!("expected Click for modified left press (button {b})"),
}
}
}
#[test]
fn truncate_counts_wide_chars_by_display_width() {
let line = "日本語";
let out = truncate_ansi_visible(line, 5);
assert_eq!(out, "日本");
assert_eq!(truncate_ansi_visible(line, 6), "日本語");
}
#[test]
fn layout_wraps_on_display_width_for_wide_names() {
let sessions = vec!["日本語".to_string(), "한국어".to_string()];
let (lines, hits) = layout_tabs(&sessions, 0, 12, 2);
assert_eq!(lines.len(), 2, "wide names should wrap by display width");
assert_eq!(hits[0].term_row, 2);
assert_eq!(hits[1].term_row, 3);
assert_eq!((hits[0].start_col, hits[0].end_col), (1, 8));
}
#[test]
fn parse_coalesced_press_release_takes_first() {
match parse_sgr_mouse(b"0;5;7M\x1b[<0;5;7m") {
Some(Key::Click { row, col }) => assert_eq!((row, col), (7, 5)),
_ => panic!("expected Click from leading press"),
}
}
#[test]
fn parse_malformed_is_ignored() {
assert!(parse_sgr_mouse(b"garbage").is_none());
assert!(parse_sgr_mouse(b"0;12M").is_none()); }
#[test]
fn truncate_passes_short_lines_through_unchanged() {
let line = "\x1b[31mhello\x1b[0m";
assert_eq!(truncate_ansi_visible(line, 10), line);
}
#[test]
fn truncate_clips_visible_chars_only() {
let line = "\x1b[31mhello\x1b[32mworld\x1b[0m";
let out = truncate_ansi_visible(line, 5);
assert!(out.contains("hello"));
assert!(!out.contains("world"));
assert!(out.contains("\x1b[31m"));
}
#[test]
fn truncate_preserves_csi_state_changes_within_window() {
let line = "\x1b[31ma\x1b[32mb\x1b[33mc";
let out = truncate_ansi_visible(line, 2);
assert!(out.contains("\x1b[31m"));
assert!(out.contains("\x1b[32m"));
assert!(out.contains('a'));
assert!(out.contains('b'));
assert!(!out.contains('c'));
}
#[test]
fn truncate_max_zero_returns_empty() {
assert_eq!(truncate_ansi_visible("\x1b[31mxxx", 0), "");
}
#[test]
fn truncate_handles_osc_with_st() {
let line = "\x1b]0;title\x1b\\hello world";
let out = truncate_ansi_visible(line, 5);
assert!(out.contains("\x1b]0;title\x1b\\"));
assert!(out.contains("hello"));
assert!(!out.contains("world"));
}
#[test]
fn truncate_handles_osc_with_bel() {
let line = "\x1b]0;title\x07hello world";
let out = truncate_ansi_visible(line, 5);
assert!(out.contains("\x1b]0;title\x07"));
assert!(out.contains("hello"));
assert!(!out.contains("world"));
}
#[test]
fn truncate_140_to_80_bounds_visible_count() {
let body: String = (0..140).map(|_| 'X').collect();
let line = format!("\x1b[31m{body}\x1b[0m");
let out = truncate_ansi_visible(&line, 79);
let mut count = 0;
let mut chars = out.chars().peekable();
while let Some(c) = chars.next() {
if c == '\x1b' {
if chars.peek() == Some(&'[') {
chars.next();
while let Some(&p) = chars.peek() {
chars.next();
if (p as u32) >= 0x40 && (p as u32) <= 0x7E {
break;
}
}
}
} else {
count += 1;
}
}
assert_eq!(count, 79);
}
}