From b10adb92cb00d76b37665a97620de7f84bffe421 Mon Sep 17 00:00:00 2001 From: can1357 Date: Sun, 1 Feb 2026 10:28:33 +0100 Subject: [PATCH] chore: bump version to 9.6.3 --- Cargo.lock | 2 +- Cargo.toml | 2 +- bun.lock | 14 ++--- crates/pi-natives/src/text.rs | 93 +++++++++++++++--------------- packages/agent/package.json | 2 +- packages/ai/package.json | 2 +- packages/coding-agent/package.json | 2 +- packages/natives/package.json | 2 +- packages/stats/package.json | 2 +- packages/tui/bench/width.ts | 4 -- packages/tui/package.json | 2 +- packages/tui/src/utils.ts | 9 ++- packages/utils/package.json | 2 +- 13 files changed, 69 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index daea95c10..e1e32d4cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -790,7 +790,7 @@ dependencies = [ [[package]] name = "pi-natives" -version = "9.6.2" +version = "9.6.3" dependencies = [ "bstr", "globset", diff --git a/Cargo.toml b/Cargo.toml index d657157c2..1e0175332 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["crates/*"] resolver = "3" [workspace.package] -version = "9.6.2" +version = "9.6.3" edition = "2024" license = "MIT" authors = ["Can Boluk"] diff --git a/bun.lock b/bun.lock index a6e4938e1..0690fe1b1 100644 --- a/bun.lock +++ b/bun.lock @@ -22,7 +22,7 @@ }, "packages/agent": { "name": "@oh-my-pi/pi-agent-core", - "version": "9.6.2", + "version": "9.6.3", "dependencies": { "@oh-my-pi/pi-ai": "workspace:*", "@oh-my-pi/pi-tui": "workspace:*", @@ -35,7 +35,7 @@ }, "packages/ai": { "name": "@oh-my-pi/pi-ai", - "version": "9.6.2", + "version": "9.6.3", "bin": { "pi-ai": "./src/cli.ts", }, @@ -65,7 +65,7 @@ }, "packages/coding-agent": { "name": "@oh-my-pi/pi-coding-agent", - "version": "9.6.2", + "version": "9.6.3", "bin": { "omp": "src/cli.ts", }, @@ -100,7 +100,7 @@ }, "packages/natives": { "name": "@oh-my-pi/pi-natives", - "version": "9.6.2", + "version": "9.6.3", "dependencies": { "@oh-my-pi/pi-utils": "workspace:*", }, @@ -128,7 +128,7 @@ }, "packages/stats": { "name": "@oh-my-pi/omp-stats", - "version": "9.6.2", + "version": "9.6.3", "bin": { "omp-stats": "./src/index.ts", }, @@ -148,7 +148,7 @@ }, "packages/tui": { "name": "@oh-my-pi/pi-tui", - "version": "9.6.2", + "version": "9.6.3", "dependencies": { "@oh-my-pi/pi-natives": "workspace:*", "@types/mime-types": "^3.0.1", @@ -163,7 +163,7 @@ }, "packages/utils": { "name": "@oh-my-pi/pi-utils", - "version": "9.6.2", + "version": "9.6.3", "dependencies": { "winston": "^3.19.0", "winston-daily-rotate-file": "^5.0.0", diff --git a/crates/pi-natives/src/text.rs b/crates/pi-natives/src/text.rs index 2303d9e23..94f88f0b8 100644 --- a/crates/pi-natives/src/text.rs +++ b/crates/pi-natives/src/text.rs @@ -8,6 +8,8 @@ //! - Ellipsis decoded lazily //! - truncateToWidth returns the original `JsString` when possible +use std::cell::RefCell; + use napi::{JsString, bindgen_prelude::*}; use napi_derive::napi; use unicode_segmentation::UnicodeSegmentation; @@ -293,7 +295,7 @@ fn ansi_seq_len_u16(data: &[u16], pos: usize) -> Option { // '[' CSI for (i, b) in data[pos + 2..].iter().enumerate() { if (0x40..=0x7e).contains(b) { - return Some(i + 1 - pos); + return Some(i + 3); } } None @@ -302,10 +304,10 @@ fn ansi_seq_len_u16(data: &[u16], pos: usize) -> Option { // ']' OSC for (i, &b) in data[pos + 2..].iter().enumerate() { if b == 0x07 { - return Some(i + 1 - pos); + return Some(i + 3); } - if b == ESC && data.get(i + 1) == Some(&0x5c) { - return Some(i + 2 - pos); + if b == ESC && data.get(pos + 2 + i + 1) == Some(&0x5c) { + return Some(i + 4); } } None @@ -353,13 +355,17 @@ fn grapheme_width_str(g: &str) -> usize { UnicodeWidthStr::width(g) } +thread_local! { + static SCRATCH: RefCell = const { RefCell::new(String::new()) }; +} + /// Iterate graphemes in a UTF-16 segment with: /// - ASCII fast path (no UTF-8 conversion) /// - non-ASCII slow path using a reused scratch String /// /// Callback returns `true` to continue, `false` to stop early. #[inline] -fn for_each_grapheme_u16(segment: &[u16], scratch: &mut String, mut f: F) -> bool +fn for_each_grapheme_u16(segment: &[u16], mut f: F) -> bool where F: FnMut(&[u16], usize) -> bool, { @@ -378,31 +384,33 @@ where } // Slow path: decode into scratch once, reuse allocation - scratch.clear(); - scratch.reserve(segment.len()); + SCRATCH.with_borrow_mut(|scratch| { + scratch.clear(); + scratch.reserve(segment.len()); - for r in std::char::decode_utf16(segment.iter().copied()) { - scratch.push(r.unwrap_or('\u{FFFD}')); - } - - let mut utf16_pos = 0usize; - for g in scratch.graphemes(true) { - let w = grapheme_width_str(g); - - let g_u16_len: usize = g.chars().map(|c| c.len_utf16()).sum(); - let u16_slice = &segment[utf16_pos..utf16_pos + g_u16_len]; - utf16_pos += g_u16_len; - - if !f(u16_slice, w) { - return false; + for r in std::char::decode_utf16(segment.iter().copied()) { + scratch.push(r.unwrap_or('\u{FFFD}')); } - } - true + let mut utf16_pos = 0usize; + for g in scratch.graphemes(true) { + let w = grapheme_width_str(g); + + let g_u16_len: usize = g.chars().map(|c| c.len_utf16()).sum(); + let u16_slice = &segment[utf16_pos..utf16_pos + g_u16_len]; + utf16_pos += g_u16_len; + + if !f(u16_slice, w) { + return false; + } + } + + true + }) } /// Visible width, with early-exit if width exceeds `limit`. -fn visible_width_u16_up_to(data: &[u16], limit: usize, scratch: &mut String) -> (usize, bool) { +fn visible_width_u16_up_to(data: &[u16], limit: usize) -> (usize, bool) { let mut width = 0usize; let mut i = 0usize; @@ -424,7 +432,7 @@ fn visible_width_u16_up_to(data: &[u16], limit: usize, scratch: &mut String) -> } let seg = &data[start..i]; - let ok = for_each_grapheme_u16(seg, scratch, |_, w| { + let ok = for_each_grapheme_u16(seg, |_, w| { width += w; width <= limit }); @@ -436,8 +444,8 @@ fn visible_width_u16_up_to(data: &[u16], limit: usize, scratch: &mut String) -> (width, width > limit) } -fn visible_width_u16(data: &[u16], scratch: &mut String) -> usize { - visible_width_u16_up_to(data, usize::MAX, scratch).0 +fn visible_width_u16(data: &[u16]) -> usize { + visible_width_u16_up_to(data, usize::MAX).0 } // ============================================================================ @@ -462,10 +470,8 @@ pub fn truncate_to_width( let text_u16 = text.into_utf16()?; let text = text_u16.as_slice(); - let mut scratch = String::new(); - // Fast path: early-exit width check - let (text_w, exceeded) = visible_width_u16_up_to(text, max_width, &mut scratch); + let (text_w, exceeded) = visible_width_u16_up_to(text, max_width); if !exceeded { if !pad { // Return original JsString handle: zero output allocation. @@ -501,7 +507,7 @@ pub fn truncate_to_width( if target_w == 0 { let mut out = Vec::with_capacity(ellipsis.len().min(max_width * 2)); let mut w = 0usize; - let _ = for_each_grapheme_u16(ellipsis, &mut scratch, |gu16, gw| { + let _ = for_each_grapheme_u16(ellipsis, |gu16, gw| { if w + gw > max_width { return false; } @@ -543,7 +549,7 @@ pub fn truncate_to_width( } let seg = &text[start..i]; - let keep_going = for_each_grapheme_u16(seg, &mut scratch, |gu16, gw| { + let keep_going = for_each_grapheme_u16(seg, |gu16, gw| { if w + gw > target_w { return false; } @@ -594,8 +600,6 @@ fn slice_with_width_impl( // store pending ANSI ranges (pos,len) to avoid copying until needed let mut pending_ansi: Vec<(usize, usize)> = Vec::new(); - let mut scratch = String::new(); - while i < line.len() && current_col < end_col { if line[i] == ESC { if let Some(len) = ansi_seq_len_u16(line, i) { @@ -621,7 +625,7 @@ fn slice_with_width_impl( } let seg = &line[start..i]; - let _ = for_each_grapheme_u16(seg, &mut scratch, |gu16, gw| { + let _ = for_each_grapheme_u16(seg, |gu16, gw| { if current_col >= end_col { return false; } @@ -704,8 +708,6 @@ fn extract_segments_impl( let mut after_started = false; let mut state = AnsiState::new(); - let mut scratch = String::new(); - while i < line.len() { let done = if after_len == 0 { current_col >= before_end @@ -750,7 +752,7 @@ fn extract_segments_impl( } let seg = &line[start..i]; - let _ = for_each_grapheme_u16(seg, &mut scratch, |gu16, gw| { + let _ = for_each_grapheme_u16(seg, |gu16, gw| { let done_inner = if after_len == 0 { current_col >= before_end } else { @@ -825,8 +827,7 @@ pub fn extract_segments( #[napi(js_name = "visibleWidth")] pub fn visible_width_napi(text: JsString) -> Result { let text_u16 = text.into_utf16()?; - let mut scratch = String::new(); - Ok(clamp_u32(visible_width_u16(text_u16.as_slice(), &mut scratch))) + Ok(clamp_u32(visible_width_u16(text_u16.as_slice()))) } #[cfg(test)] @@ -839,11 +840,10 @@ mod tests { #[test] fn test_visible_width() { - let mut scratch = String::new(); - assert_eq!(visible_width_u16(&to_u16("hello"), &mut scratch), 5); - assert_eq!(visible_width_u16(&to_u16("\x1b[31mhello\x1b[0m"), &mut scratch), 5); - assert_eq!(visible_width_u16(&to_u16("\x1b[38;5;196mred\x1b[0m"), &mut scratch), 3); - assert_eq!(visible_width_u16(&to_u16("a\tb"), &mut scratch), 1 + TAB_WIDTH + 1); + assert_eq!(visible_width_u16(&to_u16("hello")), 5); + assert_eq!(visible_width_u16(&to_u16("\x1b[31mhello\x1b[0m")), 5); + assert_eq!(visible_width_u16(&to_u16("\x1b[38;5;196mred\x1b[0m")), 3); + assert_eq!(visible_width_u16(&to_u16("a\tb")), 1 + TAB_WIDTH + 1); } #[test] @@ -881,8 +881,7 @@ mod tests { #[test] fn test_early_exit() { let data = to_u16(&"a]b".repeat(1000)); - let mut scratch = String::new(); - let (w, exceeded) = visible_width_u16_up_to(&data, 10, &mut scratch); + let (w, exceeded) = visible_width_u16_up_to(&data, 10); assert!(exceeded); assert!(w > 10); } diff --git a/packages/agent/package.json b/packages/agent/package.json index b43c79863..9b7932830 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,6 +1,6 @@ { "name": "@oh-my-pi/pi-agent-core", - "version": "9.6.2", + "version": "9.6.3", "description": "General-purpose agent with transport abstraction, state management, and attachment support", "type": "module", "main": "./src/index.ts", diff --git a/packages/ai/package.json b/packages/ai/package.json index 9d4b98cd3..2188f277a 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -1,6 +1,6 @@ { "name": "@oh-my-pi/pi-ai", - "version": "9.6.2", + "version": "9.6.3", "description": "Unified LLM API with automatic model discovery and provider configuration", "type": "module", "main": "./src/index.ts", diff --git a/packages/coding-agent/package.json b/packages/coding-agent/package.json index bbcbadb03..3ae21375d 100644 --- a/packages/coding-agent/package.json +++ b/packages/coding-agent/package.json @@ -1,6 +1,6 @@ { "name": "@oh-my-pi/pi-coding-agent", - "version": "9.6.2", + "version": "9.6.3", "description": "Coding agent CLI with read, bash, edit, write tools and session management", "type": "module", "ompConfig": { diff --git a/packages/natives/package.json b/packages/natives/package.json index 6e67ffcb5..3820f7522 100644 --- a/packages/natives/package.json +++ b/packages/natives/package.json @@ -1,6 +1,6 @@ { "name": "@oh-my-pi/pi-natives", - "version": "9.6.2", + "version": "9.6.3", "description": "Native Rust functionality via N-API", "type": "module", "main": "./src/index.ts", diff --git a/packages/stats/package.json b/packages/stats/package.json index c83e7e9ca..3e5ecc98b 100644 --- a/packages/stats/package.json +++ b/packages/stats/package.json @@ -1,6 +1,6 @@ { "name": "@oh-my-pi/omp-stats", - "version": "9.6.2", + "version": "9.6.3", "description": "Local observability dashboard for pi AI usage statistics", "type": "module", "main": "./src/index.ts", diff --git a/packages/tui/bench/width.ts b/packages/tui/bench/width.ts index cf2f138ef..be9b87f45 100644 --- a/packages/tui/bench/width.ts +++ b/packages/tui/bench/width.ts @@ -57,10 +57,6 @@ function bunStringWidth(str: string): number { if (str.includes("\t")) { clean = clean.replace(/\t/g, " "); } - if (clean.includes("\x1b")) { - clean = clean.replace(/\x1b\[[0-9;]*[mGKHJ]/g, ""); - clean = clean.replace(/\x1b\]8;;[^\x07]*\x07/g, ""); - } return Bun.stringWidth(clean); } diff --git a/packages/tui/package.json b/packages/tui/package.json index ab991cbb2..78704df2a 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -1,6 +1,6 @@ { "name": "@oh-my-pi/pi-tui", - "version": "9.6.2", + "version": "9.6.3", "description": "Terminal User Interface library with differential rendering for efficient text-based applications", "type": "module", "main": "./src/index.ts", diff --git a/packages/tui/src/utils.ts b/packages/tui/src/utils.ts index e01d5c9c4..6e7f8a0c5 100644 --- a/packages/tui/src/utils.ts +++ b/packages/tui/src/utils.ts @@ -25,8 +25,8 @@ export function getSegmenter(): Intl.Segmenter { } // Cache for non-ASCII strings -const WIDTH_CACHE_SIZE = 512; -const widthCache = new Map(); +//const WIDTH_CACHE_SIZE = 512; +//const widthCache = new Map(); /** * Calculate the visible width of a string in terminal columns. @@ -60,7 +60,11 @@ export function visibleWidth(str: string): number { if (str.length === 0) { return 0; } + return visibleWidthRaw(str); + // === Disabled cache === + + /* // Check cache const cached = widthCache.get(str); if (cached !== undefined) { @@ -77,6 +81,7 @@ export function visibleWidth(str: string): number { widthCache.set(str, width); return width; + */ } const WRAP_OPTIONS = { wordWrap: true, hard: true, trim: false } as const; diff --git a/packages/utils/package.json b/packages/utils/package.json index 548d7da9d..4424de5f5 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@oh-my-pi/pi-utils", - "version": "9.6.2", + "version": "9.6.3", "description": "Shared utilities for pi packages", "type": "module", "main": "./src/index.ts",