ccstatusline 2.0.10 → 2.0.12

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.
package/README.md CHANGED
@@ -44,6 +44,16 @@
44
44
 
45
45
  ## 🆕 Recent Updates
46
46
 
47
+ ### v2.0.12 - Custom Text widget now supports emojis
48
+
49
+ - **👾 Emoji Support** - You can now paste emoji into the custom text widget. You can also turn on the merge option to get emoji labels for your widgets like this:
50
+
51
+ ![Emoji Support](https://raw.githubusercontent.com/sirmalloc/ccstatusline/main/screenshots/emojiSupport.png)
52
+
53
+ ### v2.0.11 - Unlimited Status Lines
54
+
55
+ - **🚀 No Line Limit** - Configure as many status lines as you need - the 3-line limitation has been removed
56
+
47
57
  ### v2.0.10 - Git Updates
48
58
 
49
59
  - **🌳 Git Worktree widget** - Shows the active worktree name when working with git worktrees
@@ -99,7 +109,7 @@
99
109
  - **📊 Real-time Metrics** - Display model name, git branch, token usage, session duration, block timer, and more
100
110
  - **🎨 Fully Customizable** - Choose what to display and customize colors for each element
101
111
  - **⚡ Powerline Support** - Beautiful Powerline-style rendering with arrow separators, caps, and custom fonts
102
- - **📐 Multi-line Support** - Configure up to 3 independent status lines
112
+ - **📐 Multi-line Support** - Configure multiple independent status lines
103
113
  - **🖥️ Interactive TUI** - Built-in configuration interface using React/Ink
104
114
  - **⚙️ Global Options** - Apply consistent formatting across all widgets (padding, separators, bold, background)
105
115
  - **🚀 Cross-platform** - Works seamlessly with both Bun and Node.js
@@ -123,7 +133,7 @@ bunx ccstatusline@latest
123
133
  ### Configure ccstatusline
124
134
 
125
135
  The interactive configuration tool provides a terminal UI where you can:
126
- - Configure up to 3 separate status lines
136
+ - Configure multiple separate status lines
127
137
  - Add/remove/reorder status line widgets
128
138
  - Customize colors for each widget
129
139
  - Configure flex separator behavior
@@ -31861,6 +31861,333 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
31861
31861
  }
31862
31862
  });
31863
31863
 
31864
+ // node_modules/pluralize/pluralize.js
31865
+ var require_pluralize = __commonJS((exports, module) => {
31866
+ (function(root, pluralize) {
31867
+ if (typeof exports === "object" && typeof module === "object") {
31868
+ module.exports = pluralize();
31869
+ } else if (typeof define === "function" && define.amd) {
31870
+ define(function() {
31871
+ return pluralize();
31872
+ });
31873
+ } else {
31874
+ root.pluralize = pluralize();
31875
+ }
31876
+ })(exports, function() {
31877
+ var pluralRules = [];
31878
+ var singularRules = [];
31879
+ var uncountables = {};
31880
+ var irregularPlurals = {};
31881
+ var irregularSingles = {};
31882
+ function sanitizeRule(rule) {
31883
+ if (typeof rule === "string") {
31884
+ return new RegExp("^" + rule + "$", "i");
31885
+ }
31886
+ return rule;
31887
+ }
31888
+ function restoreCase(word, token) {
31889
+ if (word === token)
31890
+ return token;
31891
+ if (word === word.toLowerCase())
31892
+ return token.toLowerCase();
31893
+ if (word === word.toUpperCase())
31894
+ return token.toUpperCase();
31895
+ if (word[0] === word[0].toUpperCase()) {
31896
+ return token.charAt(0).toUpperCase() + token.substr(1).toLowerCase();
31897
+ }
31898
+ return token.toLowerCase();
31899
+ }
31900
+ function interpolate(str, args) {
31901
+ return str.replace(/\$(\d{1,2})/g, function(match, index) {
31902
+ return args[index] || "";
31903
+ });
31904
+ }
31905
+ function replace(word, rule) {
31906
+ return word.replace(rule[0], function(match, index) {
31907
+ var result = interpolate(rule[1], arguments);
31908
+ if (match === "") {
31909
+ return restoreCase(word[index - 1], result);
31910
+ }
31911
+ return restoreCase(match, result);
31912
+ });
31913
+ }
31914
+ function sanitizeWord(token, word, rules) {
31915
+ if (!token.length || uncountables.hasOwnProperty(token)) {
31916
+ return word;
31917
+ }
31918
+ var len = rules.length;
31919
+ while (len--) {
31920
+ var rule = rules[len];
31921
+ if (rule[0].test(word))
31922
+ return replace(word, rule);
31923
+ }
31924
+ return word;
31925
+ }
31926
+ function replaceWord(replaceMap, keepMap, rules) {
31927
+ return function(word) {
31928
+ var token = word.toLowerCase();
31929
+ if (keepMap.hasOwnProperty(token)) {
31930
+ return restoreCase(word, token);
31931
+ }
31932
+ if (replaceMap.hasOwnProperty(token)) {
31933
+ return restoreCase(word, replaceMap[token]);
31934
+ }
31935
+ return sanitizeWord(token, word, rules);
31936
+ };
31937
+ }
31938
+ function checkWord(replaceMap, keepMap, rules, bool) {
31939
+ return function(word) {
31940
+ var token = word.toLowerCase();
31941
+ if (keepMap.hasOwnProperty(token))
31942
+ return true;
31943
+ if (replaceMap.hasOwnProperty(token))
31944
+ return false;
31945
+ return sanitizeWord(token, token, rules) === token;
31946
+ };
31947
+ }
31948
+ function pluralize(word, count, inclusive) {
31949
+ var pluralized = count === 1 ? pluralize.singular(word) : pluralize.plural(word);
31950
+ return (inclusive ? count + " " : "") + pluralized;
31951
+ }
31952
+ pluralize.plural = replaceWord(irregularSingles, irregularPlurals, pluralRules);
31953
+ pluralize.isPlural = checkWord(irregularSingles, irregularPlurals, pluralRules);
31954
+ pluralize.singular = replaceWord(irregularPlurals, irregularSingles, singularRules);
31955
+ pluralize.isSingular = checkWord(irregularPlurals, irregularSingles, singularRules);
31956
+ pluralize.addPluralRule = function(rule, replacement) {
31957
+ pluralRules.push([sanitizeRule(rule), replacement]);
31958
+ };
31959
+ pluralize.addSingularRule = function(rule, replacement) {
31960
+ singularRules.push([sanitizeRule(rule), replacement]);
31961
+ };
31962
+ pluralize.addUncountableRule = function(word) {
31963
+ if (typeof word === "string") {
31964
+ uncountables[word.toLowerCase()] = true;
31965
+ return;
31966
+ }
31967
+ pluralize.addPluralRule(word, "$0");
31968
+ pluralize.addSingularRule(word, "$0");
31969
+ };
31970
+ pluralize.addIrregularRule = function(single, plural) {
31971
+ plural = plural.toLowerCase();
31972
+ single = single.toLowerCase();
31973
+ irregularSingles[single] = plural;
31974
+ irregularPlurals[plural] = single;
31975
+ };
31976
+ [
31977
+ ["I", "we"],
31978
+ ["me", "us"],
31979
+ ["he", "they"],
31980
+ ["she", "they"],
31981
+ ["them", "them"],
31982
+ ["myself", "ourselves"],
31983
+ ["yourself", "yourselves"],
31984
+ ["itself", "themselves"],
31985
+ ["herself", "themselves"],
31986
+ ["himself", "themselves"],
31987
+ ["themself", "themselves"],
31988
+ ["is", "are"],
31989
+ ["was", "were"],
31990
+ ["has", "have"],
31991
+ ["this", "these"],
31992
+ ["that", "those"],
31993
+ ["echo", "echoes"],
31994
+ ["dingo", "dingoes"],
31995
+ ["volcano", "volcanoes"],
31996
+ ["tornado", "tornadoes"],
31997
+ ["torpedo", "torpedoes"],
31998
+ ["genus", "genera"],
31999
+ ["viscus", "viscera"],
32000
+ ["stigma", "stigmata"],
32001
+ ["stoma", "stomata"],
32002
+ ["dogma", "dogmata"],
32003
+ ["lemma", "lemmata"],
32004
+ ["schema", "schemata"],
32005
+ ["anathema", "anathemata"],
32006
+ ["ox", "oxen"],
32007
+ ["axe", "axes"],
32008
+ ["die", "dice"],
32009
+ ["yes", "yeses"],
32010
+ ["foot", "feet"],
32011
+ ["eave", "eaves"],
32012
+ ["goose", "geese"],
32013
+ ["tooth", "teeth"],
32014
+ ["quiz", "quizzes"],
32015
+ ["human", "humans"],
32016
+ ["proof", "proofs"],
32017
+ ["carve", "carves"],
32018
+ ["valve", "valves"],
32019
+ ["looey", "looies"],
32020
+ ["thief", "thieves"],
32021
+ ["groove", "grooves"],
32022
+ ["pickaxe", "pickaxes"],
32023
+ ["passerby", "passersby"]
32024
+ ].forEach(function(rule) {
32025
+ return pluralize.addIrregularRule(rule[0], rule[1]);
32026
+ });
32027
+ [
32028
+ [/s?$/i, "s"],
32029
+ [/[^\u0000-\u007F]$/i, "$0"],
32030
+ [/([^aeiou]ese)$/i, "$1"],
32031
+ [/(ax|test)is$/i, "$1es"],
32032
+ [/(alias|[^aou]us|t[lm]as|gas|ris)$/i, "$1es"],
32033
+ [/(e[mn]u)s?$/i, "$1s"],
32034
+ [/([^l]ias|[aeiou]las|[ejzr]as|[iu]am)$/i, "$1"],
32035
+ [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, "$1i"],
32036
+ [/(alumn|alg|vertebr)(?:a|ae)$/i, "$1ae"],
32037
+ [/(seraph|cherub)(?:im)?$/i, "$1im"],
32038
+ [/(her|at|gr)o$/i, "$1oes"],
32039
+ [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i, "$1a"],
32040
+ [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i, "$1a"],
32041
+ [/sis$/i, "ses"],
32042
+ [/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i, "$1$2ves"],
32043
+ [/([^aeiouy]|qu)y$/i, "$1ies"],
32044
+ [/([^ch][ieo][ln])ey$/i, "$1ies"],
32045
+ [/(x|ch|ss|sh|zz)$/i, "$1es"],
32046
+ [/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i, "$1ices"],
32047
+ [/\b((?:tit)?m|l)(?:ice|ouse)$/i, "$1ice"],
32048
+ [/(pe)(?:rson|ople)$/i, "$1ople"],
32049
+ [/(child)(?:ren)?$/i, "$1ren"],
32050
+ [/eaux$/i, "$0"],
32051
+ [/m[ae]n$/i, "men"],
32052
+ ["thou", "you"]
32053
+ ].forEach(function(rule) {
32054
+ return pluralize.addPluralRule(rule[0], rule[1]);
32055
+ });
32056
+ [
32057
+ [/s$/i, ""],
32058
+ [/(ss)$/i, "$1"],
32059
+ [/(wi|kni|(?:after|half|high|low|mid|non|night|[^\w]|^)li)ves$/i, "$1fe"],
32060
+ [/(ar|(?:wo|[ae])l|[eo][ao])ves$/i, "$1f"],
32061
+ [/ies$/i, "y"],
32062
+ [/\b([pl]|zomb|(?:neck|cross)?t|coll|faer|food|gen|goon|group|lass|talk|goal|cut)ies$/i, "$1ie"],
32063
+ [/\b(mon|smil)ies$/i, "$1ey"],
32064
+ [/\b((?:tit)?m|l)ice$/i, "$1ouse"],
32065
+ [/(seraph|cherub)im$/i, "$1"],
32066
+ [/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|t[lm]as|gas|(?:her|at|gr)o|[aeiou]ris)(?:es)?$/i, "$1"],
32067
+ [/(analy|diagno|parenthe|progno|synop|the|empha|cri|ne)(?:sis|ses)$/i, "$1sis"],
32068
+ [/(movie|twelve|abuse|e[mn]u)s$/i, "$1"],
32069
+ [/(test)(?:is|es)$/i, "$1is"],
32070
+ [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, "$1us"],
32071
+ [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i, "$1um"],
32072
+ [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i, "$1on"],
32073
+ [/(alumn|alg|vertebr)ae$/i, "$1a"],
32074
+ [/(cod|mur|sil|vert|ind)ices$/i, "$1ex"],
32075
+ [/(matr|append)ices$/i, "$1ix"],
32076
+ [/(pe)(rson|ople)$/i, "$1rson"],
32077
+ [/(child)ren$/i, "$1"],
32078
+ [/(eau)x?$/i, "$1"],
32079
+ [/men$/i, "man"]
32080
+ ].forEach(function(rule) {
32081
+ return pluralize.addSingularRule(rule[0], rule[1]);
32082
+ });
32083
+ [
32084
+ "adulthood",
32085
+ "advice",
32086
+ "agenda",
32087
+ "aid",
32088
+ "aircraft",
32089
+ "alcohol",
32090
+ "ammo",
32091
+ "analytics",
32092
+ "anime",
32093
+ "athletics",
32094
+ "audio",
32095
+ "bison",
32096
+ "blood",
32097
+ "bream",
32098
+ "buffalo",
32099
+ "butter",
32100
+ "carp",
32101
+ "cash",
32102
+ "chassis",
32103
+ "chess",
32104
+ "clothing",
32105
+ "cod",
32106
+ "commerce",
32107
+ "cooperation",
32108
+ "corps",
32109
+ "debris",
32110
+ "diabetes",
32111
+ "digestion",
32112
+ "elk",
32113
+ "energy",
32114
+ "equipment",
32115
+ "excretion",
32116
+ "expertise",
32117
+ "firmware",
32118
+ "flounder",
32119
+ "fun",
32120
+ "gallows",
32121
+ "garbage",
32122
+ "graffiti",
32123
+ "hardware",
32124
+ "headquarters",
32125
+ "health",
32126
+ "herpes",
32127
+ "highjinks",
32128
+ "homework",
32129
+ "housework",
32130
+ "information",
32131
+ "jeans",
32132
+ "justice",
32133
+ "kudos",
32134
+ "labour",
32135
+ "literature",
32136
+ "machinery",
32137
+ "mackerel",
32138
+ "mail",
32139
+ "media",
32140
+ "mews",
32141
+ "moose",
32142
+ "music",
32143
+ "mud",
32144
+ "manga",
32145
+ "news",
32146
+ "only",
32147
+ "personnel",
32148
+ "pike",
32149
+ "plankton",
32150
+ "pliers",
32151
+ "police",
32152
+ "pollution",
32153
+ "premises",
32154
+ "rain",
32155
+ "research",
32156
+ "rice",
32157
+ "salmon",
32158
+ "scissors",
32159
+ "series",
32160
+ "sewage",
32161
+ "shambles",
32162
+ "shrimp",
32163
+ "software",
32164
+ "species",
32165
+ "staff",
32166
+ "swine",
32167
+ "tennis",
32168
+ "traffic",
32169
+ "transportation",
32170
+ "trout",
32171
+ "tuna",
32172
+ "wealth",
32173
+ "welfare",
32174
+ "whiting",
32175
+ "wildebeest",
32176
+ "wildlife",
32177
+ "you",
32178
+ /pok[eé]mon$/i,
32179
+ /[^aeiou]ese$/i,
32180
+ /deer$/i,
32181
+ /fish$/i,
32182
+ /measles$/i,
32183
+ /o[iu]s$/i,
32184
+ /pox$/i,
32185
+ /sheep$/i
32186
+ ].forEach(pluralize.addUncountableRule);
32187
+ return pluralize;
32188
+ });
32189
+ });
32190
+
31864
32191
  // node_modules/picomatch/lib/constants.js
31865
32192
  var require_constants3 = __commonJS((exports, module) => {
31866
32193
  var WIN_SLASH = "\\\\/";
@@ -50581,7 +50908,7 @@ var SettingsSchema_v1 = exports_external.object({
50581
50908
  });
50582
50909
  var SettingsSchema = exports_external.object({
50583
50910
  version: exports_external.number().default(CURRENT_VERSION),
50584
- lines: exports_external.array(exports_external.array(WidgetItemSchema)).min(1).max(3).default([
50911
+ lines: exports_external.array(exports_external.array(WidgetItemSchema)).min(1).default([
50585
50912
  [
50586
50913
  { id: "1", type: "model", color: "cyan" },
50587
50914
  { id: "2", type: "separator" },
@@ -50590,8 +50917,10 @@ var SettingsSchema = exports_external.object({
50590
50917
  { id: "5", type: "git-branch", color: "magenta" },
50591
50918
  { id: "6", type: "separator" },
50592
50919
  { id: "7", type: "git-changes", color: "yellow" }
50593
- ]
50594
- ]).transform((lines) => lines.slice(0, 3)),
50920
+ ],
50921
+ [],
50922
+ []
50923
+ ]),
50595
50924
  flexMode: FlexModeSchema.default("full-minus-40"),
50596
50925
  compactThreshold: exports_external.number().min(1).max(99).default(60),
50597
50926
  colorLevel: ColorLevelSchema.default(2),
@@ -51045,7 +51374,7 @@ import { execSync as execSync3 } from "child_process";
51045
51374
  import * as fs5 from "fs";
51046
51375
  import * as path4 from "path";
51047
51376
  var __dirname = "/Users/sirmalloc/Projects/Personal/ccstatusline/src/utils";
51048
- var PACKAGE_VERSION = "2.0.10";
51377
+ var PACKAGE_VERSION = "2.0.12";
51049
51378
  function getPackageVersion() {
51050
51379
  if (/^\d+\.\d+\.\d+/.test(PACKAGE_VERSION)) {
51051
51380
  return PACKAGE_VERSION;
@@ -52290,15 +52619,35 @@ function renderPowerlineStatusLine(widgets, settings, context, lineIndex = 0, gl
52290
52619
  return "";
52291
52620
  const autoAlign = config2.autoAlign;
52292
52621
  if (autoAlign) {
52622
+ let alignmentPos = 0;
52293
52623
  for (let i = 0;i < widgetElements.length; i++) {
52294
52624
  const element = widgetElements[i];
52295
- const maxWidth = preCalculatedMaxWidths[i];
52296
- if (element && maxWidth !== undefined) {
52297
- const currentLength = stringWidth(element.content.replace(ANSI_REGEX, ""));
52298
- const paddingNeeded = maxWidth - currentLength;
52299
- if (paddingNeeded > 0) {
52300
- element.content += " ".repeat(paddingNeeded);
52625
+ if (!element)
52626
+ continue;
52627
+ const prevWidget = i > 0 ? widgetElements[i - 1] : null;
52628
+ const isPreviousMerged = prevWidget?.widget.merge;
52629
+ if (!isPreviousMerged) {
52630
+ const maxWidth = preCalculatedMaxWidths[alignmentPos];
52631
+ if (maxWidth !== undefined) {
52632
+ let combinedLength = stringWidth(element.content.replace(ANSI_REGEX, ""));
52633
+ let j = i;
52634
+ while (j < widgetElements.length - 1 && widgetElements[j]?.widget.merge) {
52635
+ j++;
52636
+ const nextElement = widgetElements[j];
52637
+ if (nextElement) {
52638
+ combinedLength += stringWidth(nextElement.content.replace(ANSI_REGEX, ""));
52639
+ }
52640
+ }
52641
+ const paddingNeeded = maxWidth - combinedLength;
52642
+ if (paddingNeeded > 0) {
52643
+ const lastElement = widgetElements[j];
52644
+ if (lastElement) {
52645
+ lastElement.content += " ".repeat(paddingNeeded);
52646
+ }
52647
+ }
52648
+ i = j;
52301
52649
  }
52650
+ alignmentPos++;
52302
52651
  }
52303
52652
  }
52304
52653
  }
@@ -52497,17 +52846,32 @@ function calculateMaxWidthsFromPreRendered(preRenderedLines, settings) {
52497
52846
  const paddingLength = defaultPadding.length;
52498
52847
  for (const preRenderedLine of preRenderedLines) {
52499
52848
  const filteredWidgets = preRenderedLine.filter((w) => w.widget.type !== "separator" && w.widget.type !== "flex-separator" && w.content);
52500
- for (let pos = 0;pos < filteredWidgets.length; pos++) {
52501
- const widget = filteredWidgets[pos];
52849
+ let alignmentPos = 0;
52850
+ for (let i = 0;i < filteredWidgets.length; i++) {
52851
+ const widget = filteredWidgets[i];
52502
52852
  if (!widget)
52503
52853
  continue;
52504
- const totalWidth = widget.plainLength + paddingLength * 2;
52505
- const currentMax = maxWidths[pos];
52854
+ let totalWidth = widget.plainLength + paddingLength * 2;
52855
+ let j = i;
52856
+ while (j < filteredWidgets.length - 1 && filteredWidgets[j]?.widget.merge) {
52857
+ j++;
52858
+ const nextWidget = filteredWidgets[j];
52859
+ if (nextWidget) {
52860
+ if (filteredWidgets[j - 1]?.widget.merge === "no-padding") {
52861
+ totalWidth += nextWidget.plainLength;
52862
+ } else {
52863
+ totalWidth += nextWidget.plainLength + paddingLength * 2;
52864
+ }
52865
+ }
52866
+ }
52867
+ const currentMax = maxWidths[alignmentPos];
52506
52868
  if (currentMax === undefined) {
52507
- maxWidths[pos] = totalWidth;
52869
+ maxWidths[alignmentPos] = totalWidth;
52508
52870
  } else {
52509
- maxWidths[pos] = Math.max(currentMax, totalWidth);
52871
+ maxWidths[alignmentPos] = Math.max(currentMax, totalWidth);
52510
52872
  }
52873
+ i = j;
52874
+ alignmentPos++;
52511
52875
  }
52512
52876
  }
52513
52877
  return maxWidths;
@@ -52570,6 +52934,8 @@ function renderStatusLine(widgets, settings, context, preRenderedWidgets, preCal
52570
52934
  if (!widget)
52571
52935
  continue;
52572
52936
  if (widget.type === "separator") {
52937
+ if (i > 0 && !preRenderedWidgets[i - 1]?.content)
52938
+ continue;
52573
52939
  const sepChar = widget.character ?? (settings.defaultSeparator ?? "|");
52574
52940
  const formattedSep = formatSeparator(sepChar);
52575
52941
  let separatorColor = widget.color ?? "gray";
@@ -53150,49 +53516,107 @@ class CustomTextWidget {
53150
53516
  var CustomTextEditor = ({ widget, onComplete, onCancel }) => {
53151
53517
  const [text, setText] = import_react29.useState(widget.customText ?? "");
53152
53518
  const [cursorPos, setCursorPos] = import_react29.useState(text.length);
53519
+ const getGraphemes = (str) => {
53520
+ if ("Segmenter" in Intl) {
53521
+ const segmenter2 = new Intl.Segmenter(undefined, { granularity: "grapheme" });
53522
+ return Array.from(segmenter2.segment(str), (seg) => seg.segment);
53523
+ }
53524
+ return Array.from(str);
53525
+ };
53526
+ const graphemeToStringIndex = (str, graphemeIndex) => {
53527
+ const graphemes2 = getGraphemes(str);
53528
+ let stringIndex = 0;
53529
+ for (let i = 0;i < Math.min(graphemeIndex, graphemes2.length); i++) {
53530
+ const grapheme = graphemes2[i];
53531
+ if (grapheme) {
53532
+ stringIndex += grapheme.length;
53533
+ }
53534
+ }
53535
+ return stringIndex;
53536
+ };
53537
+ const stringToGraphemeIndex = (str, stringIndex) => {
53538
+ const graphemes2 = getGraphemes(str);
53539
+ let currentStringIndex = 0;
53540
+ for (let i = 0;i < graphemes2.length; i++) {
53541
+ if (currentStringIndex >= stringIndex)
53542
+ return i;
53543
+ const grapheme = graphemes2[i];
53544
+ if (grapheme) {
53545
+ currentStringIndex += grapheme.length;
53546
+ }
53547
+ }
53548
+ return graphemes2.length;
53549
+ };
53153
53550
  use_input_default((input, key) => {
53154
53551
  if (key.return) {
53155
53552
  onComplete({ ...widget, customText: text });
53156
53553
  } else if (key.escape) {
53157
53554
  onCancel();
53158
53555
  } else if (key.leftArrow) {
53159
- setCursorPos(Math.max(0, cursorPos - 1));
53556
+ const currentGraphemeIndex2 = stringToGraphemeIndex(text, cursorPos);
53557
+ if (currentGraphemeIndex2 > 0) {
53558
+ const newStringIndex = graphemeToStringIndex(text, currentGraphemeIndex2 - 1);
53559
+ setCursorPos(newStringIndex);
53560
+ }
53160
53561
  } else if (key.rightArrow) {
53161
- setCursorPos(Math.min(text.length, cursorPos + 1));
53562
+ const currentGraphemeIndex2 = stringToGraphemeIndex(text, cursorPos);
53563
+ const graphemeCount = getGraphemes(text).length;
53564
+ if (currentGraphemeIndex2 < graphemeCount) {
53565
+ const newStringIndex = graphemeToStringIndex(text, currentGraphemeIndex2 + 1);
53566
+ setCursorPos(newStringIndex);
53567
+ }
53162
53568
  } else if (key.ctrl && input === "ArrowLeft") {
53163
53569
  setCursorPos(0);
53164
53570
  } else if (key.ctrl && input === "ArrowRight") {
53165
53571
  setCursorPos(text.length);
53166
53572
  } else if (key.backspace) {
53167
53573
  if (cursorPos > 0) {
53168
- setText(text.slice(0, cursorPos - 1) + text.slice(cursorPos));
53169
- setCursorPos(cursorPos - 1);
53574
+ const currentGraphemeIndex2 = stringToGraphemeIndex(text, cursorPos);
53575
+ if (currentGraphemeIndex2 > 0) {
53576
+ const deleteFromIndex = graphemeToStringIndex(text, currentGraphemeIndex2 - 1);
53577
+ const deleteToIndex = graphemeToStringIndex(text, currentGraphemeIndex2);
53578
+ setText(text.slice(0, deleteFromIndex) + text.slice(deleteToIndex));
53579
+ setCursorPos(deleteFromIndex);
53580
+ }
53170
53581
  }
53171
53582
  } else if (key.delete) {
53172
53583
  if (cursorPos < text.length) {
53173
- setText(text.slice(0, cursorPos) + text.slice(cursorPos + 1));
53584
+ const currentGraphemeIndex2 = stringToGraphemeIndex(text, cursorPos);
53585
+ const graphemeCount = getGraphemes(text).length;
53586
+ if (currentGraphemeIndex2 < graphemeCount) {
53587
+ const deleteFromIndex = graphemeToStringIndex(text, currentGraphemeIndex2);
53588
+ const deleteToIndex = graphemeToStringIndex(text, currentGraphemeIndex2 + 1);
53589
+ setText(text.slice(0, deleteFromIndex) + text.slice(deleteToIndex));
53590
+ }
53174
53591
  }
53175
- } else if (input && input.length === 1) {
53176
- setText(text.slice(0, cursorPos) + input + text.slice(cursorPos));
53177
- setCursorPos(cursorPos + 1);
53592
+ } else if (input && !key.ctrl && !key.meta) {
53593
+ const newText = text.slice(0, cursorPos) + input + text.slice(cursorPos);
53594
+ setText(newText);
53595
+ setCursorPos(cursorPos + input.length);
53178
53596
  }
53179
53597
  });
53598
+ const graphemes = getGraphemes(text);
53599
+ const currentGraphemeIndex = stringToGraphemeIndex(text, cursorPos);
53600
+ let display = "Enter custom text: ";
53601
+ for (let i = 0;i < graphemes.length; i++) {
53602
+ const grapheme = graphemes[i];
53603
+ if (grapheme) {
53604
+ if (i === currentGraphemeIndex) {
53605
+ display += `\x1B[7m${grapheme}\x1B[0m`;
53606
+ } else {
53607
+ display += grapheme;
53608
+ }
53609
+ }
53610
+ }
53611
+ if (currentGraphemeIndex >= graphemes.length) {
53612
+ display += "\x1B[7m \x1B[0m";
53613
+ }
53180
53614
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
53181
53615
  flexDirection: "column",
53182
53616
  children: [
53183
53617
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
53184
- children: [
53185
- "Enter custom text:",
53186
- " ",
53187
- text.slice(0, cursorPos),
53188
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
53189
- backgroundColor: "gray",
53190
- color: "black",
53191
- children: text[cursorPos] ?? " "
53192
- }, undefined, false, undefined, this),
53193
- text.slice(cursorPos + 1)
53194
- ]
53195
- }, undefined, true, undefined, this),
53618
+ children: display
53619
+ }, undefined, false, undefined, this),
53196
53620
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
53197
53621
  dimColor: true,
53198
53622
  children: "←→ move cursor, Ctrl+←→ jump to start/end, Enter save, ESC cancel"
@@ -55246,26 +55670,73 @@ var ItemsEditor = ({ widgets, onUpdate, onBack, lineNumber, settings }) => {
55246
55670
  }, undefined, true, undefined, this);
55247
55671
  };
55248
55672
  // src/tui/components/LineSelector.tsx
55673
+ var import_pluralize = __toESM(require_pluralize(), 1);
55249
55674
  var import_react37 = __toESM(require_react(), 1);
55250
55675
  var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
55251
- var LineSelector = ({ lines, onSelect, onBack, initialSelection = 0, title, blockIfPowerlineActive = false, settings }) => {
55676
+ var LineSelector = ({
55677
+ lines,
55678
+ onSelect,
55679
+ onBack,
55680
+ onLinesUpdate,
55681
+ initialSelection = 0,
55682
+ title,
55683
+ blockIfPowerlineActive = false,
55684
+ settings,
55685
+ allowEditing = false
55686
+ }) => {
55252
55687
  const [selectedIndex, setSelectedIndex] = import_react37.useState(initialSelection);
55688
+ const [showDeleteDialog, setShowDeleteDialog] = import_react37.useState(false);
55689
+ const [localLines, setLocalLines] = import_react37.useState(lines);
55690
+ import_react37.useEffect(() => {
55691
+ setLocalLines(lines);
55692
+ }, [lines]);
55693
+ const selectedLine = import_react37.useMemo(() => localLines[selectedIndex], [localLines, selectedIndex]);
55694
+ const appendLine = () => {
55695
+ const newLines = [...localLines, []];
55696
+ setLocalLines(newLines);
55697
+ onLinesUpdate(newLines);
55698
+ setSelectedIndex(newLines.length - 1);
55699
+ };
55700
+ const deleteLine = (lineIndex) => {
55701
+ if (localLines.length <= 1) {
55702
+ return;
55703
+ }
55704
+ const newLines = [...localLines];
55705
+ newLines.splice(lineIndex, 1);
55706
+ setLocalLines(newLines);
55707
+ onLinesUpdate(newLines);
55708
+ };
55253
55709
  const powerlineEnabled = settings ? settings.powerline.enabled : false;
55254
55710
  const powerlineTheme = settings ? settings.powerline.theme : undefined;
55255
55711
  const isThemeManaged = blockIfPowerlineActive && powerlineEnabled && powerlineTheme && powerlineTheme !== "custom";
55256
55712
  use_input_default((input, key) => {
55713
+ if (showDeleteDialog) {
55714
+ return;
55715
+ }
55257
55716
  if (isThemeManaged) {
55258
55717
  onBack();
55259
55718
  return;
55260
55719
  }
55720
+ switch (input) {
55721
+ case "a":
55722
+ if (allowEditing) {
55723
+ appendLine();
55724
+ }
55725
+ return;
55726
+ case "d":
55727
+ if (allowEditing && localLines.length > 1) {
55728
+ setShowDeleteDialog(true);
55729
+ }
55730
+ return;
55731
+ }
55261
55732
  if (key.escape) {
55262
55733
  onBack();
55263
55734
  } else if (key.upArrow) {
55264
55735
  setSelectedIndex(Math.max(0, selectedIndex - 1));
55265
55736
  } else if (key.downArrow) {
55266
- setSelectedIndex(Math.min(3, selectedIndex + 1));
55737
+ setSelectedIndex(Math.min(localLines.length, selectedIndex + 1));
55267
55738
  } else if (key.return) {
55268
- if (selectedIndex === 3) {
55739
+ if (selectedIndex === localLines.length) {
55269
55740
  onBack();
55270
55741
  } else {
55271
55742
  onSelect(selectedIndex);
@@ -55320,69 +55791,130 @@ var LineSelector = ({ lines, onSelect, onBack, initialSelection = 0, title, bloc
55320
55791
  ]
55321
55792
  }, undefined, true, undefined, this);
55322
55793
  }
55323
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
55324
- flexDirection: "column",
55325
- children: [
55326
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55327
- bold: true,
55328
- children: title ?? "Select Line to Edit"
55329
- }, undefined, false, undefined, this),
55330
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55331
- dimColor: true,
55332
- children: "Choose which status line to configure (up to 3 lines supported)"
55333
- }, undefined, false, undefined, this),
55334
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55335
- dimColor: true,
55336
- children: "Press ESC to go back"
55337
- }, undefined, false, undefined, this),
55338
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
55339
- marginTop: 1,
55340
- flexDirection: "column",
55341
- children: [
55342
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
55343
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55344
- color: selectedIndex === 0 ? "green" : undefined,
55345
- children: [
55346
- selectedIndex === 0 ? "▶ " : " ",
55347
- "☰ Line 1",
55348
- lines[0] && lines[0].length > 0 ? ` (${lines[0].length} widgets)` : " (empty)"
55349
- ]
55350
- }, undefined, true, undefined, this)
55351
- }, undefined, false, undefined, this),
55352
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
55353
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55354
- color: selectedIndex === 1 ? "green" : undefined,
55355
- children: [
55356
- selectedIndex === 1 ? "▶ " : " ",
55357
- " Line 2",
55358
- lines[1] && lines[1].length > 0 ? ` (${lines[1].length} widgets)` : " (empty)"
55359
- ]
55360
- }, undefined, true, undefined, this)
55361
- }, undefined, false, undefined, this),
55362
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
55363
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55364
- color: selectedIndex === 2 ? "green" : undefined,
55365
- children: [
55366
- selectedIndex === 2 ? "▶ " : " ",
55367
- "☰ Line 3",
55368
- lines[2] && lines[2].length > 0 ? ` (${lines[2].length} widgets)` : " (empty)"
55369
- ]
55370
- }, undefined, true, undefined, this)
55371
- }, undefined, false, undefined, this),
55372
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
55373
- marginTop: 1,
55374
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55375
- color: selectedIndex === 3 ? "green" : undefined,
55376
- children: [
55377
- selectedIndex === 3 ? "▶ " : " ",
55378
- "← Back"
55379
- ]
55380
- }, undefined, true, undefined, this)
55794
+ if (showDeleteDialog && selectedLine) {
55795
+ const suffix = selectedLine.length > 0 ? import_pluralize.default("widget", selectedLine.length, true) : "empty";
55796
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
55797
+ flexDirection: "column",
55798
+ children: [
55799
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
55800
+ flexDirection: "column",
55801
+ gap: 1,
55802
+ children: [
55803
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55804
+ bold: true,
55805
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55806
+ children: [
55807
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55808
+ children: [
55809
+ "☰ Line",
55810
+ " ",
55811
+ selectedIndex + 1
55812
+ ]
55813
+ }, undefined, true, undefined, this),
55814
+ " ",
55815
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55816
+ dimColor: true,
55817
+ children: [
55818
+ "(",
55819
+ suffix,
55820
+ ")"
55821
+ ]
55822
+ }, undefined, true, undefined, this)
55823
+ ]
55824
+ }, undefined, true, undefined, this)
55825
+ }, undefined, false, undefined, this),
55826
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55827
+ bold: true,
55828
+ children: "Are you sure you want to delete line?"
55829
+ }, undefined, false, undefined, this)
55830
+ ]
55831
+ }, undefined, true, undefined, this),
55832
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
55833
+ marginTop: 1,
55834
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(ConfirmDialog, {
55835
+ inline: true,
55836
+ onConfirm: () => {
55837
+ deleteLine(selectedIndex);
55838
+ setSelectedIndex(Math.max(0, selectedIndex - 1));
55839
+ setShowDeleteDialog(false);
55840
+ },
55841
+ onCancel: () => {
55842
+ setShowDeleteDialog(false);
55843
+ }
55381
55844
  }, undefined, false, undefined, this)
55382
- ]
55383
- }, undefined, true, undefined, this)
55384
- ]
55385
- }, undefined, true, undefined, this);
55845
+ }, undefined, false, undefined, this)
55846
+ ]
55847
+ }, undefined, true, undefined, this);
55848
+ }
55849
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
55850
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
55851
+ flexDirection: "column",
55852
+ children: [
55853
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55854
+ bold: true,
55855
+ children: title ?? "Select Line to Edit"
55856
+ }, undefined, false, undefined, this),
55857
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55858
+ dimColor: true,
55859
+ children: "Choose which status line to configure"
55860
+ }, undefined, false, undefined, this),
55861
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55862
+ dimColor: true,
55863
+ children: allowEditing ? localLines.length > 1 ? "(a) to append new line, (d) to delete line, ESC to go back" : "(a) to append new line, ESC to go back" : "ESC to go back"
55864
+ }, undefined, false, undefined, this),
55865
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
55866
+ marginTop: 1,
55867
+ flexDirection: "column",
55868
+ children: [
55869
+ localLines.map((line, index) => {
55870
+ const isSelected = selectedIndex === index;
55871
+ const suffix = line.length ? import_pluralize.default("widget", line.length, true) : "empty";
55872
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
55873
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55874
+ color: isSelected ? "green" : undefined,
55875
+ children: [
55876
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55877
+ children: isSelected ? "▶ " : " "
55878
+ }, undefined, false, undefined, this),
55879
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55880
+ children: [
55881
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55882
+ children: [
55883
+ "☰ Line",
55884
+ " ",
55885
+ index + 1
55886
+ ]
55887
+ }, undefined, true, undefined, this),
55888
+ " ",
55889
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55890
+ dimColor: !isSelected,
55891
+ children: [
55892
+ "(",
55893
+ suffix,
55894
+ ")"
55895
+ ]
55896
+ }, undefined, true, undefined, this)
55897
+ ]
55898
+ }, undefined, true, undefined, this)
55899
+ ]
55900
+ }, undefined, true, undefined, this)
55901
+ }, index, false, undefined, this);
55902
+ }),
55903
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
55904
+ marginTop: 1,
55905
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
55906
+ color: selectedIndex === localLines.length ? "green" : undefined,
55907
+ children: [
55908
+ selectedIndex === localLines.length ? "▶ " : " ",
55909
+ "← Back"
55910
+ ]
55911
+ }, undefined, true, undefined, this)
55912
+ }, undefined, false, undefined, this)
55913
+ ]
55914
+ }, undefined, true, undefined, this)
55915
+ ]
55916
+ }, undefined, true, undefined, this)
55917
+ }, undefined, false, undefined, this);
55386
55918
  };
55387
55919
  // src/tui/components/MainMenu.tsx
55388
55920
  var import_react38 = __toESM(require_react(), 1);
@@ -57037,9 +57569,6 @@ var App2 = () => {
57037
57569
  getExistingStatusLine().then(setExistingStatusLine);
57038
57570
  loadSettings().then((loadedSettings) => {
57039
57571
  source_default.level = loadedSettings.colorLevel;
57040
- while (loadedSettings.lines.length < 3) {
57041
- loadedSettings.lines.push([]);
57042
- }
57043
57572
  setSettings(loadedSettings);
57044
57573
  setOriginalSettings(JSON.parse(JSON.stringify(loadedSettings)));
57045
57574
  });
@@ -57144,6 +57673,9 @@ var App2 = () => {
57144
57673
  newLines[lineIndex] = widgets;
57145
57674
  setSettings({ ...settings, lines: newLines });
57146
57675
  };
57676
+ const updateLines = (newLines) => {
57677
+ setSettings({ ...settings, lines: newLines });
57678
+ };
57147
57679
  const handleLineSelect = (lineIndex) => {
57148
57680
  setSelectedLine(lineIndex);
57149
57681
  setScreen("items");
@@ -57209,12 +57741,14 @@ var App2 = () => {
57209
57741
  setMenuSelections({ ...menuSelections, lines: line });
57210
57742
  handleLineSelect(line);
57211
57743
  },
57744
+ onLinesUpdate: updateLines,
57212
57745
  onBack: () => {
57213
57746
  setMenuSelections({ ...menuSelections, main: 0 });
57214
57747
  setScreen("main");
57215
57748
  },
57216
57749
  initialSelection: menuSelections.lines,
57217
- title: "Select Line to Edit Items"
57750
+ title: "Select Line to Edit Items",
57751
+ allowEditing: true
57218
57752
  }, undefined, false, undefined, this),
57219
57753
  screen === "items" && /* @__PURE__ */ jsx_dev_runtime17.jsxDEV(ItemsEditor, {
57220
57754
  widgets: settings.lines[selectedLine] ?? [],
@@ -57230,6 +57764,7 @@ var App2 = () => {
57230
57764
  }, undefined, false, undefined, this),
57231
57765
  screen === "colorLines" && /* @__PURE__ */ jsx_dev_runtime17.jsxDEV(LineSelector, {
57232
57766
  lines: settings.lines,
57767
+ onLinesUpdate: updateLines,
57233
57768
  onSelect: (line) => {
57234
57769
  setMenuSelections({ ...menuSelections, lines: line });
57235
57770
  setSelectedLine(line);
@@ -57242,7 +57777,8 @@ var App2 = () => {
57242
57777
  initialSelection: menuSelections.lines,
57243
57778
  title: "Select Line to Edit Colors",
57244
57779
  blockIfPowerlineActive: true,
57245
- settings
57780
+ settings,
57781
+ allowEditing: false
57246
57782
  }, undefined, false, undefined, this),
57247
57783
  screen === "colors" && /* @__PURE__ */ jsx_dev_runtime17.jsxDEV(ColorMenu, {
57248
57784
  widgets: settings.lines[selectedLine] ?? [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccstatusline",
3
- "version": "2.0.10",
3
+ "version": "2.0.12",
4
4
  "description": "A customizable status line formatter for Claude Code CLI",
5
5
  "module": "src/ccstatusline.ts",
6
6
  "type": "module",
@@ -12,9 +12,9 @@
12
12
  ],
13
13
  "scripts": {
14
14
  "start": "bun run src/ccstatusline.ts",
15
- "statusline": "bun run src/ccstatusline.ts",
16
15
  "build": "rm -rf dist/* && bun build src/ccstatusline.ts --target=node --outfile=dist/ccstatusline.js --target-version=14",
17
16
  "postbuild": "bun run scripts/replace-version.ts",
17
+ "example": "cat scripts/payload.example.json | bun start",
18
18
  "prepublishOnly": "bun run build",
19
19
  "lint": "bun tsc --noEmit; eslint . --config eslint.config.js --max-warnings=999999 --fix",
20
20
  "test": "bun vitest"
@@ -41,7 +41,9 @@
41
41
  "typescript": "^5.9.2",
42
42
  "typescript-eslint": "^8.39.1",
43
43
  "vitest": "^3.2.4",
44
- "zod": "^4.0.17"
44
+ "zod": "^4.0.17",
45
+ "pluralize": "^8.0.0",
46
+ "@types/pluralize": "^0.0.33"
45
47
  },
46
48
  "keywords": [
47
49
  "claude",