pi-tui-status-beautifier 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -247,75 +247,88 @@ export function beautifyStatus(
247
247
 
248
248
  /**
249
249
  * Calculates the visual column cell width of a string (Finding 5), taking into account
250
- * full-width Unicode characters, East Asian characters, and emojis.
250
+ * full-width Unicode characters, East Asian ideographs, Kana, Hangul, emojis, and zero-width joiners.
251
251
  */
252
252
  export function getStringWidth(str: string): number {
253
253
  if (!str) return 0;
254
- // Strip ANSI escape sequences & control codes before counting visual width
255
254
  const clean = str.replace(ANSI_STRIP_REGEX, "");
256
255
  let width = 0;
257
- for (let i = 0; i < clean.length; i++) {
258
- const code = clean.charCodeAt(i);
256
+
257
+ for (const char of clean) {
258
+ const code = char.codePointAt(0) || 0;
259
+
260
+ // Zero-width characters (ZWJ, variation selectors, combining marks)
261
+ if (
262
+ code === 0x200d ||
263
+ code === 0xfe0f ||
264
+ code === 0xfe0e ||
265
+ (code >= 0x0300 && code <= 0x036f) ||
266
+ (code >= 0x200b && code <= 0x200f)
267
+ ) {
268
+ continue;
269
+ }
270
+
259
271
  if (isFullWidth(code)) {
260
272
  width += 2;
261
273
  } else {
262
274
  width += 1;
263
275
  }
264
276
  }
277
+
265
278
  return width;
266
279
  }
267
280
 
268
281
  /**
269
282
  * Checks if a character unicode code point is full-width (East Asian Width).
270
283
  */
271
- function isFullWidth(code: number): boolean {
284
+ export function isFullWidth(code: number): boolean {
272
285
  if (isNaN(code)) return false;
273
286
  return (
274
- code >= 0x1100 &&
275
- (code <= 0x115f ||
276
- code === 0x2329 ||
277
- code === 0x232a ||
278
- // CJK Radicals Supplement .. Enclosed CJK Letters and Months
279
- (code >= 0x2e80 && code <= 0x3247 && code !== 0x303f) ||
280
- // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A
281
- (code >= 0x3250 && code <= 0x4dbf) ||
282
- // CJK Unified Ideographs .. Yi Radicals
283
- (code >= 0x4e00 && code <= 0xa4c6) ||
284
- // Hangul Jamo Extended-A
285
- (code >= 0xa960 && code <= 0xa97c) ||
286
- // Hangul Syllables
287
- (code >= 0xac00 && code <= 0xd7a3) ||
288
- // CJK Compatibility Ideographs
289
- (code >= 0xf900 && code <= 0xfaff) ||
290
- // Vertical Forms
291
- (code >= 0xfe10 && code <= 0xfe19) ||
292
- // CJK Compatibility Forms .. Small Form Variants
293
- (code >= 0xfe30 && code <= 0xfe6b) ||
294
- // Halfwidth and Fullwidth Forms
295
- (code >= 0xff01 && code <= 0xff60) ||
296
- (code >= 0xffe0 && code <= 0xffe6) ||
297
- // East Asian Wide characters beyond BMP
298
- (code >= 0x1b000 && code <= 0x1b001) ||
299
- (code >= 0x1f200 && code <= 0x1f251) ||
300
- (code >= 0x20000 && code <= 0x3fffd))
287
+ (code >= 0x1100 && code <= 0x115f) || // Hangul Jamo
288
+ code === 0x2329 ||
289
+ code === 0x232a ||
290
+ (code >= 0x2e80 && code <= 0x3247 && code !== 0x303f) || // CJK Radicals
291
+ (code >= 0x3040 && code <= 0x309f) || // Hiragana
292
+ (code >= 0x30a0 && code <= 0x30ff) || // Katakana
293
+ (code >= 0x3250 && code <= 0x4dbf) || // CJK Extension A
294
+ (code >= 0x4e00 && code <= 0xa4c6) || // CJK Unified Ideographs .. Yi
295
+ (code >= 0xa960 && code <= 0xa97c) || // Hangul Jamo Extended-A
296
+ (code >= 0xac00 && code <= 0xd7af) || // Hangul Syllables
297
+ (code >= 0xf900 && code <= 0xfaff) || // CJK Compatibility Ideographs
298
+ (code >= 0xfe10 && code <= 0xfe19) || // Vertical Forms
299
+ (code >= 0xfe30 && code <= 0xfe6b) || // CJK Compatibility Forms
300
+ (code >= 0xff01 && code <= 0xff60) || // Fullwidth Forms
301
+ (code >= 0xffe0 && code <= 0xffe6) || // Fullwidth Symbols
302
+ (code >= 0x1f300 && code <= 0x1f9ff) || // Emojis & Pictographs
303
+ (code >= 0x1f600 && code <= 0x1f64f) || // Emoticons
304
+ (code >= 0x1f680 && code <= 0x1f6ff) || // Transport & Map
305
+ (code >= 0x2600 && code <= 0x27bf) || // Dingbats & Misc
306
+ (code >= 0x1fa70 && code <= 0x1faff) || // Symbols Extended-A
307
+ (code >= 0x20000 && code <= 0x3fffd) // CJK Extensions B/C/D
301
308
  );
302
309
  }
303
310
 
304
311
  /**
305
- * Slices a string to a maximum visual cell width.
312
+ * Slices a string to a maximum visual cell width without cutting surrogate pairs in half.
306
313
  */
307
314
  export function sliceToVisualWidth(str: string, maxWidth: number): string {
308
315
  let width = 0;
309
316
  let sliced = "";
310
- for (let i = 0; i < str.length; i++) {
311
- const code = str.charCodeAt(i);
312
- const charWidth = isFullWidth(code) ? 2 : 1;
317
+
318
+ for (const char of str) {
319
+ const code = char.codePointAt(0) || 0;
320
+ const charWidth = (
321
+ code === 0x200d || code === 0xfe0f || code === 0xfe0e ||
322
+ (code >= 0x0300 && code <= 0x036f) || (code >= 0x200b && code <= 0x200f)
323
+ ) ? 0 : (isFullWidth(code) ? 2 : 1);
324
+
313
325
  if (width + charWidth > maxWidth) {
314
326
  break;
315
327
  }
316
328
  width += charWidth;
317
- sliced += str.charAt(i);
329
+ sliced += char;
318
330
  }
331
+
319
332
  return sliced;
320
333
  }
321
334
 
@@ -412,14 +425,16 @@ export default function (pi: ExtensionAPI) {
412
425
  ctx.ui.setStatus(key, val);
413
426
  }
414
427
 
415
- // 3. Command-Driven Disk I/O: Persist settings to disk asynchronously outside core paint loop
428
+ // 3. Command-Driven Disk I/O: Persist settings to disk atomically outside core paint loop
416
429
  try {
417
430
  if (fs.existsSync(settingsPath)) {
418
431
  const data = fs.readFileSync(settingsPath, "utf-8");
419
432
  const config = JSON.parse(data);
420
433
  if (!config.beautifier) config.beautifier = {};
421
434
  config.beautifier.style = styleKey;
422
- fs.writeFileSync(settingsPath, JSON.stringify(config, null, 2), "utf-8");
435
+ const tempSettings = `${settingsPath}.tmp.${process.pid}.${Date.now()}`;
436
+ fs.writeFileSync(tempSettings, JSON.stringify(config, null, 2), "utf-8");
437
+ fs.renameSync(tempSettings, settingsPath);
423
438
  }
424
439
  } catch (e) {
425
440
  // Silently fail disk write to avoid crashing active user sessions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-tui-status-beautifier",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Premium brand & layout status bar beautifier for Pi terminal TUI.",
5
5
  "main": "extensions/tui-status-beautifier.ts",
6
6
  "keywords": [