@giovannijecha/jecode 0.3.1 → 0.4.0

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/dist/tui/keys.js CHANGED
@@ -5,6 +5,7 @@
5
5
  // guessed at; and a bracketed paste arrives as a delimited run that must not be
6
6
  // interpreted key by key, or a pasted newline submits half the paste.
7
7
  const ESC = String.fromCharCode(27);
8
+ const BS = String.fromCharCode(8);
8
9
  const DEL = String.fromCharCode(127);
9
10
  const PASTE_START = "[200~";
10
11
  const PASTE_END = "[201~";
@@ -35,10 +36,18 @@ const SEQUENCES = {
35
36
  "[7~": "home",
36
37
  "[8~": "end",
37
38
  "[3~": "delete",
39
+ "[3;5~": "deletewordright",
38
40
  "[5~": "pageup",
39
41
  "[6~": "pagedown",
40
42
  "[1;5C": "wordright",
41
43
  "[1;5D": "wordleft",
44
+ // Explicit modified-key forms used by terminals with CSI-u support.
45
+ "[127;5u": "deletewordleft",
46
+ "[8;5u": "deletewordleft",
47
+ // Readline-compatible Alt bindings. VS Code also sends Alt+D for
48
+ // Ctrl+Delete and Ctrl+W for Ctrl+Backspace in its integrated terminal.
49
+ d: "deletewordright",
50
+ [DEL]: "deletewordleft",
42
51
  "[Z": "backtab",
43
52
  // alt+enter, which arrives as an escape followed by the return itself. It is
44
53
  // how a multi-line message gets written when enter is what sends one.
@@ -48,10 +57,10 @@ const CONTROL = {
48
57
  "\r": "enter",
49
58
  "\n": "enter",
50
59
  "\t": "tab",
51
- "\b": "backspace",
60
+ [BS]: "backspace",
52
61
  [DEL]: "backspace",
53
62
  };
54
- export function decoder() {
63
+ export function decoder(options = {}) {
55
64
  let held = "";
56
65
  let pasting = false;
57
66
  let pasted = "";
@@ -121,7 +130,9 @@ export function decoder() {
121
130
  keys.push({ name: "escape", text: "", ctrl: false });
122
131
  continue;
123
132
  }
124
- const named = CONTROL[ch];
133
+ const named = ch === BS && options.ctrlBackspaceIsBs === true
134
+ ? "deletewordleft"
135
+ : CONTROL[ch];
125
136
  if (named !== undefined) {
126
137
  held = held.slice(1);
127
138
  keys.push({ name: named, text: "", ctrl: false });
@@ -47,6 +47,12 @@ function handlePicker(open, key) {
47
47
  case "down":
48
48
  open.picker = picker.move(open.picker, 1);
49
49
  break;
50
+ case "left":
51
+ open.picker = picker.adjust(open.picker, -1);
52
+ break;
53
+ case "right":
54
+ open.picker = picker.adjust(open.picker, 1);
55
+ break;
50
56
  case "home":
51
57
  open.picker = picker.edge(open.picker, "home");
52
58
  break;
@@ -19,6 +19,12 @@ export function edge(picker, end) {
19
19
  export function page(picker, direction, rows = WINDOW) {
20
20
  return move(picker, direction * Math.max(1, rows));
21
21
  }
22
+ export function adjust(picker, step) {
23
+ const index = selected(picker);
24
+ return index === undefined || picker.adjust === undefined
25
+ ? picker
26
+ : picker.adjust(index, step);
27
+ }
22
28
  export function type(picker, text) {
23
29
  if (picker.searchable !== true || text === "")
24
30
  return picker;
@@ -45,18 +51,21 @@ export function byKey(picker, text) {
45
51
  const found = picker.options.findIndex((option) => option.key === typed);
46
52
  return found === -1 ? undefined : found;
47
53
  }
48
- export function panel(picker, width, pal, maxRows = WINDOW + 3) {
54
+ export function panel(picker, width, pal, maxRows = (picker.visible ?? WINDOW) + 3) {
49
55
  return layout(picker, width, pal, maxRows).rows;
50
56
  }
51
57
  /** Caret for the shared query row, relative to the unframed picker body. */
52
- export function caret(picker, width, maxRows = WINDOW + 3) {
58
+ export function caret(picker, width, maxRows = (picker.visible ?? WINDOW) + 3) {
53
59
  return layout(picker, width, undefined, maxRows).cursor;
54
60
  }
55
61
  function layout(picker, width, pal, maxRows) {
56
62
  const found = matches(picker);
57
63
  const note = picker.description ?? picker.footer;
58
- const fixed = 1 + (note === undefined ? 0 : 1) + (picker.searchable === true ? 1 : 0);
59
- const optionRoom = Math.max(1, Math.min(WINDOW, maxRows - fixed));
64
+ const hasTitle = picker.title.length > 0;
65
+ const fixed = (hasTitle ? 1 : 0) +
66
+ (note === undefined ? 0 : 1) +
67
+ (picker.searchable === true ? 1 : 0);
68
+ const optionRoom = Math.max(1, Math.min(picker.visible ?? WINDOW, maxRows - fixed));
60
69
  const selectedAt = Math.max(0, found.findIndex((entry) => entry.index === picker.index));
61
70
  const { first, last } = menuWindow(found.length, selectedAt, optionRoom);
62
71
  const shown = found.slice(first, last);
@@ -67,14 +76,19 @@ function layout(picker, width, pal, maxRows) {
67
76
  ? [row(width, [{ text: "no matches", fg: colors.ink.muted }])]
68
77
  : renderMenuRows(shown.map(({ option, index }) => ({
69
78
  label: option.label,
79
+ description: option.description,
70
80
  hint: option.hint,
81
+ value: option.value,
82
+ adjustable: option.adjustable,
71
83
  selected: index === picker.index,
72
84
  })), width, colors);
73
85
  const progress = found.length > shown.length || picker.searchable === true
74
86
  ? `${found.length === 0 ? "0" : `${first + 1}–${first + shown.length}`} / ${found.length}` +
75
87
  (found.length === picker.options.length ? "" : ` · ${picker.options.length} total`)
76
88
  : "";
77
- const titleRight = picker.right ?? (picker.searchable === true ? undefined : progress);
89
+ const titleRight = hasTitle
90
+ ? picker.right ?? (picker.searchable === true ? undefined : progress)
91
+ : undefined;
78
92
  const visibleTitleRight = titleRight === undefined
79
93
  ? undefined
80
94
  : elide(titleRight, Math.max(1, Math.floor(width * 0.55)));
@@ -87,14 +101,16 @@ function layout(picker, width, pal, maxRows) {
87
101
  const queryCursor = picker.searchable === true
88
102
  ? promptCursor(picker.query ?? "", (picker.query ?? "").length, width, { right: progress })
89
103
  : undefined;
90
- const queryOffset = 1 + (note === undefined ? 0 : 1);
104
+ const queryOffset = (hasTitle ? 1 : 0) + (note === undefined ? 0 : 1);
91
105
  return {
92
106
  rows: colors === undefined
93
107
  ? []
94
108
  : [
95
- row(width, picker.title, visibleTitleRight === undefined || visibleTitleRight === ""
96
- ? []
97
- : [{ text: visibleTitleRight, fg: colors.ink.muted }]),
109
+ ...(hasTitle
110
+ ? [row(width, picker.title, visibleTitleRight === undefined || visibleTitleRight === ""
111
+ ? []
112
+ : [{ text: visibleTitleRight, fg: colors.ink.dim }])]
113
+ : []),
98
114
  ...(note === undefined ? [] : [row(width, [{ text: note, fg: colors.ink.muted }])]),
99
115
  ...(queryRow === undefined ? [] : [queryRow.row]),
100
116
  ...options,
@@ -113,7 +129,7 @@ export function heading(label, about, pal) {
113
129
  function matches(picker) {
114
130
  const query = (picker.query ?? "").trim().toLocaleLowerCase();
115
131
  return picker.options.flatMap((option, index) => {
116
- const haystack = `${option.label} ${option.hint ?? ""}`.toLocaleLowerCase();
132
+ const haystack = `${option.label} ${option.hint ?? ""} ${option.value ?? ""}`.toLocaleLowerCase();
117
133
  return query === "" || haystack.includes(query) ? [{ option, index }] : [];
118
134
  });
119
135
  }
@@ -28,13 +28,13 @@ export function turnFailure(session, error, aborted) {
28
28
  if (/\b401\b/.test(text)) {
29
29
  const auth = session.provider.auth;
30
30
  if (auth.kind === "oauth") {
31
- text += ` · reconnect ${auth.label} in /settings`;
31
+ text += ` · reconnect ${auth.label} in /providers`;
32
32
  }
33
33
  else {
34
34
  const source = credentialSource(auth.keyVar);
35
35
  text += source === "environment"
36
36
  ? ` · update ${auth.keyVar} in the environment and restart`
37
- : " · check credentials with /settings";
37
+ : " · check access with /providers";
38
38
  }
39
39
  }
40
40
  return { kind: "notice", text, tone: "error" };
package/dist/tui/view.js CHANGED
@@ -79,7 +79,7 @@ function tooSmall(height, width, view) {
79
79
  ]);
80
80
  if (middle + 1 < height) {
81
81
  rows[middle + 1] = row(width, [
82
- { text: elide(`need ${MIN_COLS}×${MIN_ROWS}`, Math.max(1, width)), fg: view.pal.ink.muted },
82
+ { text: elide(`need ${MIN_COLS}×${MIN_ROWS}`, Math.max(1, width)), fg: view.pal.ink.dim },
83
83
  ]);
84
84
  }
85
85
  return { rows, maxScroll: 0 };
package/dist/ui/inline.js CHANGED
@@ -21,7 +21,7 @@ export function inline(text, base, pal, bold = false) {
21
21
  }
22
22
  const [, mono, strong, strongAlt, emphasis, label] = match;
23
23
  if (mono !== undefined)
24
- segs.push({ text: mono, fg: pal.accentSoft });
24
+ segs.push({ text: mono, fg: pal.technical });
25
25
  else if (strong !== undefined)
26
26
  segs.push({ text: strong, fg: ink.bright, bold: true });
27
27
  else if (strongAlt !== undefined)
@@ -29,7 +29,7 @@ export function inline(text, base, pal, bold = false) {
29
29
  else if (emphasis !== undefined)
30
30
  segs.push({ text: emphasis, fg: ink.bright });
31
31
  else if (label !== undefined)
32
- segs.push({ text: label, fg: pal.accent });
32
+ segs.push({ text: label, fg: pal.technical });
33
33
  last = match.index + match[0].length;
34
34
  }
35
35
  if (last < text.length) {
@@ -63,7 +63,7 @@ export function markdown(text, max, pal, proseMax = max) {
63
63
  const depth = Math.min(2, Math.floor(bullet[1].length / 2));
64
64
  const mark = [
65
65
  { text: " ".repeat(depth) },
66
- { text: "- ", fg: pal.accent },
66
+ { text: "- ", fg: pal.technical },
67
67
  ];
68
68
  const hang = [{ text: `${" ".repeat(depth)} ` }];
69
69
  rows.push(...emit(inline(bullet[2], ink.fg, pal), prose - depth * 2 - 2, mark, hang));
@@ -72,7 +72,7 @@ export function markdown(text, max, pal, proseMax = max) {
72
72
  const ordered = ORDERED.exec(line);
73
73
  if (ordered !== null) {
74
74
  const label = `${ordered[2]}. `;
75
- const mark = [{ text: label, fg: pal.accent }];
75
+ const mark = [{ text: label, fg: pal.technical }];
76
76
  const hang = [{ text: " ".repeat(label.length) }];
77
77
  rows.push(...emit(inline(ordered[3], ink.fg, pal), prose - label.length, mark, hang));
78
78
  continue;
@@ -120,13 +120,13 @@ function code(lines, start, max, pal, out) {
120
120
  break;
121
121
  body.push(elide(line.replace(/\t/g, " "), max - 2));
122
122
  }
123
- out.push({ segs: [{ text: `\`\`\`${lang}`, fg: ink.muted }] });
123
+ out.push({ segs: [{ text: `\`\`\`${lang}`, fg: ink.dim }] });
124
124
  const roles = {
125
- plain: ink.added,
126
- comment: ink.muted,
125
+ plain: ink.fg,
126
+ comment: ink.dim,
127
127
  string: ink.added,
128
128
  number: ink.attention,
129
- keyword: pal.accent,
129
+ keyword: pal.technical,
130
130
  };
131
131
  for (const tokens of highlight(body, lang)) {
132
132
  out.push({
@@ -136,7 +136,7 @@ function code(lines, start, max, pal, out) {
136
136
  ],
137
137
  });
138
138
  }
139
- out.push({ segs: [{ text: "```", fg: ink.muted }] });
139
+ out.push({ segs: [{ text: "```", fg: ink.dim }] });
140
140
  return i;
141
141
  }
142
142
  /** Flow one inline run into rows, with an opener and a hanging indent. */
package/dist/ui/theme.js CHANGED
@@ -5,20 +5,20 @@
5
5
  // Components depend on these roles rather than embedding presentation values.
6
6
  export const STEEL = {
7
7
  accent: [102, 155, 210],
8
- accentSoft: [131, 213, 245],
8
+ technical: [78, 201, 232],
9
9
  focus: [102, 155, 210],
10
10
  rule: [53, 80, 110],
11
11
  ink: {
12
- fg: [212, 218, 225],
12
+ fg: [220, 224, 229],
13
13
  bright: [235, 239, 244],
14
- muted: [112, 124, 137],
14
+ muted: [156, 169, 183],
15
+ dim: [112, 124, 137],
15
16
  attention: [230, 191, 95],
16
17
  added: [134, 203, 146],
17
18
  removed: [232, 112, 112],
18
19
  },
19
20
  surface: {
20
21
  subtle: [31, 38, 47],
21
- inset: [18, 24, 31],
22
22
  added: [22, 55, 34],
23
23
  removed: [62, 24, 27],
24
24
  attention: [62, 50, 19],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@giovannijecha/jecode",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "An owned coding agent with zero external runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "repository": {