reslop 0.2.7 → 0.3.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.
Files changed (158) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/README.md +16 -14
  3. package/bin/reslop.js +8 -5
  4. package/lib/agents/agents.js +504 -0
  5. package/lib/agents/allow.js +71 -0
  6. package/lib/agents/allow.json +16 -0
  7. package/lib/agents/models.js +460 -0
  8. package/lib/api.js +15 -12
  9. package/lib/cli.js +43 -68
  10. package/lib/{files.js → common/files.js} +48 -55
  11. package/lib/{find.js → common/find.js} +1 -1
  12. package/lib/common/format.js +101 -0
  13. package/lib/{utilities.js → common/process.js} +7 -95
  14. package/lib/common/tasks.json +27 -0
  15. package/lib/common/utilities.js +112 -0
  16. package/lib/dashboard/git.js +9 -16
  17. package/lib/dashboard/model.js +90 -49
  18. package/lib/dashboard/npm.js +17 -13
  19. package/lib/dashboard/tree.js +27 -30
  20. package/lib/{session → dashboard}/watch.js +36 -31
  21. package/lib/{workspace.js → dashboard/workspace.js} +23 -38
  22. package/lib/{deps.js → deps/deps.js} +31 -45
  23. package/lib/{deps-diff.js → deps/diff.js} +35 -25
  24. package/lib/{manifest.js → deps/manifest.js} +47 -30
  25. package/lib/diff/align.js +1 -2
  26. package/lib/diff/diff.js +2 -2
  27. package/lib/diff/display.js +29 -5
  28. package/lib/diff/inline.js +10 -14
  29. package/lib/diff/items.js +6 -4
  30. package/lib/diff/patch.js +39 -3
  31. package/lib/diff/unit.js +45 -16
  32. package/lib/{git-branches.js → git/branches.js} +53 -48
  33. package/lib/{git-deps.js → git/deps.js} +123 -96
  34. package/lib/git/git.js +71 -0
  35. package/lib/git/worktree.js +475 -0
  36. package/lib/highlight/bash.js +1 -2
  37. package/lib/highlight/csv.js +1 -2
  38. package/lib/highlight/dot.js +1 -2
  39. package/lib/{highlight.js → highlight/highlight.js} +42 -11
  40. package/lib/highlight/html.js +20 -18
  41. package/lib/highlight/json.js +1 -2
  42. package/lib/highlight/log.js +1 -2
  43. package/lib/highlight/txt.js +1 -2
  44. package/lib/{session → input}/actions.js +111 -48
  45. package/lib/{editor.js → input/editor.js} +7 -8
  46. package/lib/{keys.js → input/keys.js} +15 -43
  47. package/lib/input/line-edit.js +77 -0
  48. package/lib/input/menu-pick.js +70 -0
  49. package/lib/render/agents.js +640 -0
  50. package/lib/render/branches.js +4 -5
  51. package/lib/render/commits.js +54 -64
  52. package/lib/render/{dash-activity.js → dashboard/activity.js} +25 -40
  53. package/lib/render/{dash-blocks.js → dashboard/blocks.js} +131 -137
  54. package/lib/render/{dashboard.js → dashboard/dashboard.js} +25 -22
  55. package/lib/render/diff.js +166 -45
  56. package/lib/render/field.js +131 -0
  57. package/lib/render/files.js +9 -14
  58. package/lib/render/footer.js +383 -0
  59. package/lib/render/frame.js +123 -25
  60. package/lib/render/header.js +176 -0
  61. package/lib/render/log.js +319 -0
  62. package/lib/render/menu.js +60 -0
  63. package/lib/render/notes.js +16 -11
  64. package/lib/render/npm.js +322 -196
  65. package/lib/render/packages.js +9 -13
  66. package/lib/render/primitives.js +101 -15
  67. package/lib/render/render.js +7 -11
  68. package/lib/render/repos.js +13 -25
  69. package/lib/render/status.js +370 -0
  70. package/lib/render/status.json +85 -0
  71. package/lib/render/{dash-table.js → table.js} +13 -60
  72. package/lib/render/tiles.js +109 -48
  73. package/lib/render/unit.js +32 -13
  74. package/lib/{report-model.js → report/model.js} +4 -6
  75. package/lib/{report-parse.js → report/parse.js} +9 -14
  76. package/lib/{report-render.js → report/render.js} +46 -14
  77. package/lib/{report-run.js → report/run.js} +42 -30
  78. package/lib/{report-test.js → report/test.js} +56 -55
  79. package/lib/{report-tools.js → report/tools.js} +24 -33
  80. package/lib/review/agent-instructions.md +9 -0
  81. package/lib/review/format.js +426 -0
  82. package/lib/{review-merge.js → review/merge.js} +9 -8
  83. package/lib/review/review.js +190 -0
  84. package/lib/review/store.js +281 -0
  85. package/lib/review/templates.js +83 -0
  86. package/lib/runs/commands.js +14 -0
  87. package/lib/runs/logs.js +299 -0
  88. package/lib/runs/output.js +174 -0
  89. package/lib/runs/process.js +113 -0
  90. package/lib/{runs.js → runs/runs.js} +84 -36
  91. package/lib/runs/scripts.js +150 -0
  92. package/lib/session/agents/agents.js +958 -0
  93. package/lib/session/agents/job.js +320 -0
  94. package/lib/session/agents/jobs.js +355 -0
  95. package/lib/session/agents/options.js +153 -0
  96. package/lib/session/agents/plans.js +83 -0
  97. package/lib/session/branches.js +33 -39
  98. package/lib/session/change-actions.js +34 -100
  99. package/lib/session/commit.js +91 -52
  100. package/lib/session/compose.js +181 -145
  101. package/lib/session/dashboard.js +23 -22
  102. package/lib/session/dispatch.js +85 -21
  103. package/lib/session/files-pane.js +47 -26
  104. package/lib/session/find.js +16 -29
  105. package/lib/session/import.js +82 -0
  106. package/lib/session/items.js +8 -17
  107. package/lib/session/lifecycle.js +75 -72
  108. package/lib/session/load.js +2 -4
  109. package/lib/session/navigation.js +6 -3
  110. package/lib/session/npm/history.js +249 -0
  111. package/lib/session/npm/npm.js +815 -0
  112. package/lib/session/{packages.js → npm/packages.js} +21 -14
  113. package/lib/session/ops.js +125 -34
  114. package/lib/session/pointer.js +202 -10
  115. package/lib/session/prefs.js +112 -0
  116. package/lib/session/reads.js +109 -0
  117. package/lib/session/review.js +28 -10
  118. package/lib/session/run-panel.js +40 -0
  119. package/lib/{session.js → session/session.js} +156 -63
  120. package/lib/session/tasks.js +320 -65
  121. package/lib/session/templates.js +1 -1
  122. package/lib/session/terminal.js +94 -12
  123. package/lib/session/unit.js +35 -30
  124. package/lib/session/update.js +194 -6
  125. package/lib/session/view.js +65 -65
  126. package/lib/session/workspace.js +39 -78
  127. package/lib/{capabilities.js → source/capabilities.js} +3 -12
  128. package/lib/{github.js → source/github.js} +102 -32
  129. package/lib/{gitlab.js → source/gitlab.js} +87 -10
  130. package/lib/{remote.js → source/remote.js} +144 -40
  131. package/lib/source/review-threads.graphql +20 -0
  132. package/lib/source/source.js +74 -0
  133. package/lib/term/ansi.js +396 -0
  134. package/lib/{clipboard.js → term/clipboard.js} +3 -6
  135. package/lib/{select.js → term/select.js} +2 -2
  136. package/lib/term/themes/dark.json +122 -0
  137. package/lib/term/themes/light.json +122 -0
  138. package/lib/term/wide.txt +21 -0
  139. package/lib/{wrap.js → term/wrap.js} +4 -4
  140. package/package.json +2 -2
  141. package/lib/ansi.js +0 -545
  142. package/lib/detect.js +0 -34
  143. package/lib/diff/edit.js +0 -33
  144. package/lib/git-worktree.js +0 -455
  145. package/lib/git.js +0 -79
  146. package/lib/npm-commands.js +0 -522
  147. package/lib/render/chrome.js +0 -806
  148. package/lib/render/present.js +0 -19
  149. package/lib/render/report.js +0 -43
  150. package/lib/render/search.js +0 -25
  151. package/lib/review.js +0 -745
  152. package/lib/session/accessors.js +0 -26
  153. package/lib/session/edit-click.js +0 -57
  154. package/lib/session/npm.js +0 -452
  155. package/lib/session/progress.js +0 -41
  156. package/lib/source.js +0 -36
  157. package/lib/update.js +0 -192
  158. /package/lib/{git-reword-todo.js → git/reword-todo.js} +0 -0
@@ -1,6 +1,13 @@
1
1
  'use strict';
2
2
 
3
- const LIST_PANES = ['files', 'branches', 'commits', 'npm', 'packages'];
3
+ const LIST_PANES = [
4
+ 'files',
5
+ 'branches',
6
+ 'commits',
7
+ 'npm',
8
+ 'packages',
9
+ 'agents',
10
+ ];
4
11
 
5
12
  const ACTIONS = {
6
13
  add: ['a'],
@@ -51,37 +58,63 @@ const NPM_ACTIONS = {
51
58
  npmNew: ['insert'],
52
59
  npmDrop: ['delete'],
53
60
  npmLogs: ['l'],
54
- npmStop: ['escape'],
61
+ npmStop: ['s'],
55
62
  npmVerbose: ['v'],
56
63
  npmRerun: ['r'],
57
64
  };
58
65
 
59
- const DASH_ACTIONS = {
60
- dashFiles: ['f'],
61
- dashDiffs: ['d'],
62
- dashTasks: ['t'],
63
- dashBranches: ['b'],
64
- dashCommits: ['c'],
65
- dashRun: ['r'],
66
- dashNpm: ['n'],
66
+ const AGENT_ACTIONS = {
67
+ agentModel: ['m'],
68
+ agentEffort: ['e'],
69
+ agentFast: ['a'],
70
+ agentContext: ['t'],
71
+ agentReview: ['p'],
72
+ agentStop: ['s'],
73
+ agentRerun: ['r'],
67
74
  };
68
- const DASH_IDS = Object.keys(DASH_ACTIONS);
69
- const DASH_BLOCKS = {
70
- dashFiles: 'files',
71
- dashDiffs: 'diffs',
72
- dashCommits: 'commits',
73
- dashBranches: 'branches',
74
- dashNpm: 'npm',
75
- dashRun: 'run',
76
- dashTasks: 'tasks',
75
+
76
+ const AGENT_LIST_ACTIONS = {
77
+ agentModel: AGENT_ACTIONS.agentModel,
78
+ agentEffort: AGENT_ACTIONS.agentEffort,
79
+ agentFast: AGENT_ACTIONS.agentFast,
80
+ agentContext: AGENT_ACTIONS.agentContext,
81
+ agentReview: AGENT_ACTIONS.agentReview,
77
82
  };
78
- const DASH_ALLOWED = [...DASH_IDS, 'pull', 'push', 'quit', 'theme'];
83
+
84
+ const DASH_TILES = [
85
+ { id: 'files', key: 'f', action: 'dashFiles', footer: true },
86
+ { id: 'diffs', key: 'd', action: 'dashDiffs', footer: true },
87
+ { id: 'tasks', key: 't', action: 'dashTasks', footer: true },
88
+ { id: 'branches', key: 'b', action: 'dashBranches', footer: true },
89
+ { id: 'commits', key: 'c', action: 'dashCommits', footer: true },
90
+ { id: 'run', key: 'r', action: 'dashRun', footer: true },
91
+ { id: 'npm', key: 'n', action: 'dashNpm', footer: true },
92
+ { id: 'agents', key: 'a', action: 'dashAgents', footer: false },
93
+ ];
94
+ const DASH_ACTIONS = {};
95
+ const DASH_BLOCKS = {};
96
+ const DASH_KEYS = {};
97
+ for (const tile of DASH_TILES) {
98
+ if (tile.footer) DASH_ACTIONS[tile.action] = [tile.key];
99
+ DASH_BLOCKS[tile.action] = tile.id;
100
+ DASH_KEYS[tile.key] = tile.action;
101
+ }
102
+ const DASH_IDS = Object.keys(DASH_ACTIONS);
103
+ const DASH_ALLOWED = [
104
+ ...Object.keys(DASH_BLOCKS),
105
+ 'back',
106
+ 'pull',
107
+ 'push',
108
+ 'quit',
109
+ 'theme',
110
+ ];
79
111
  const REPOS_ALLOWED = [
80
112
  'repoLeft',
81
113
  'repoRight',
82
114
  'repoUp',
83
115
  'repoDown',
84
116
  'open',
117
+ 'back',
85
118
  'quit',
86
119
  'theme',
87
120
  ];
@@ -95,10 +128,16 @@ const ACTION_HOT = {
95
128
  newCommit: 'ins',
96
129
  feedback: 'q',
97
130
  check: 'space',
131
+ taskUp: 'ctrl+up',
132
+ taskDown: 'ctrl+down',
133
+ taskNudge: 'up/dn',
134
+ plan: 'p',
135
+ import: 'i',
98
136
  theme: 'l',
137
+ lines: '+l',
99
138
  npmNew: 'ins',
100
139
  npmDrop: 'del',
101
- npmStop: 'esc',
140
+ npmStop: 's',
102
141
  packageNew: 'ins',
103
142
  packageDrop: 'del',
104
143
  };
@@ -107,7 +146,13 @@ const ACTION_LABEL = {
107
146
  ...DASH_BLOCKS,
108
147
  revert: 'drop',
109
148
  check: 'space',
149
+ taskUp: 'ctrl+up',
150
+ taskDown: 'ctrl+down',
151
+ taskNudge: 'ctrl+up/dn',
152
+ plan: 'plan',
153
+ import: 'import',
110
154
  theme: 'light',
155
+ lines: 'ctrl+l',
111
156
  layout: 'mode',
112
157
  tasks: 'tasks',
113
158
  view: 'view',
@@ -125,9 +170,16 @@ const ACTION_LABEL = {
125
170
  packageDrop: 'delete',
126
171
  packageWanted: 'wanted',
127
172
  packageLatest: 'latest',
128
- npmStop: 'esc',
173
+ npmStop: 'stop',
129
174
  npmVerbose: 'verbose',
130
175
  npmRerun: 're-run',
176
+ agentModel: 'model',
177
+ agentEffort: 'effort',
178
+ agentFast: 'fast',
179
+ agentContext: 'context',
180
+ agentReview: 'plan',
181
+ agentStop: 'stop',
182
+ agentRerun: 're-run',
131
183
  quit: 'q',
132
184
  prev: '←',
133
185
  next: '→',
@@ -193,8 +245,12 @@ const BRANCHES_DISABLED = [
193
245
  const COMMITS_DISABLED = BRANCHES_DISABLED.filter((id) => id !== 'commit');
194
246
  const NPM_DISABLED = [...BRANCHES_DISABLED, 'pull', 'push'];
195
247
 
196
- const isTasksTarget = (item) =>
197
- !!(item && (item.kind === 'tasks' || item.origin === 'task'));
248
+ const isTasksTarget = (item) => {
249
+ if (!item) return false;
250
+ const tasks = item.kind === 'tasks';
251
+ const task = item.origin === 'task';
252
+ return tasks || task;
253
+ };
198
254
 
199
255
  const DIFF_MOVE_IDS = ['add', 'unstage', 'revert'];
200
256
 
@@ -214,8 +270,10 @@ const entryHasDiff = (entry) => {
214
270
  return total > 0;
215
271
  };
216
272
 
217
- const lineHasDiff = (line) =>
218
- !!line && (line.type === 'add' || line.type === 'del');
273
+ const lineHasDiff = (line) => {
274
+ if (!line) return false;
275
+ return line.type === 'add' || line.type === 'del';
276
+ };
219
277
 
220
278
  const withoutMoves = (off, hasDiff) => {
221
279
  if (hasDiff) return off;
@@ -286,7 +344,9 @@ const disabledActions = (pane, item) => {
286
344
  if (pane === 'commits') {
287
345
  return [...COMMITS_DISABLED, ...commitWriteDisabled(item)];
288
346
  }
289
- if (pane === 'npm' || pane === 'packages') return NPM_DISABLED;
347
+ if (pane === 'npm' || pane === 'packages' || pane === 'agents') {
348
+ return NPM_DISABLED;
349
+ }
290
350
  if (pane === 'tasks' || onTodos) return TASKS_DISABLED;
291
351
  return [...DIFF_DISABLED, ...diffGitDisabled(item)];
292
352
  };
@@ -323,7 +383,7 @@ const keysForActions = (actions) => {
323
383
 
324
384
  const PANE_ONLY = ['branch', 'file', 'diff'];
325
385
  const REVIEW_DROP = { d: 'revert' };
326
- const DIFF_KEYS = { ...REVIEW_DROP, q: 'feedback' };
386
+ const DIFF_KEYS = { ...REVIEW_DROP, q: 'feedback', 'ctrl-l': 'lines' };
327
387
  const BRANCH_KEYS = keysForActions(BRANCH_ACTIONS);
328
388
  const COMMIT_KEYS = { ...keysForActions(COMMIT_ACTIONS), s: 'push' };
329
389
  const NPM_KEYS = {
@@ -332,8 +392,7 @@ const NPM_KEYS = {
332
392
  delete: 'npmDrop',
333
393
  l: 'npmLogs',
334
394
  };
335
- const DASH_KEYS = keysForActions(DASH_ACTIONS);
336
- const SCREEN_KEYS = DASH_KEYS;
395
+ const SCREEN_KEYS = keysForActions(DASH_ACTIONS);
337
396
  const PANE_KEYS = {
338
397
  dashboard: { ...DASH_KEYS, p: 'pull', s: 'push' },
339
398
  files: REVIEW_DROP,
@@ -343,7 +402,8 @@ const PANE_KEYS = {
343
402
  commits: COMMIT_KEYS,
344
403
  npm: NPM_KEYS,
345
404
  packages: keysForActions(PACKAGE_ACTIONS),
346
- tasks: { delete: 'drop' },
405
+ agents: keysForActions(AGENT_LIST_ACTIONS),
406
+ tasks: { delete: 'drop', p: 'plan', i: 'import' },
347
407
  repos: {
348
408
  left: 'repoLeft',
349
409
  right: 'repoRight',
@@ -376,14 +436,17 @@ const actionFromKey = (key, pane) => {
376
436
  return KEY_TO_ACTION[key] ?? null;
377
437
  };
378
438
 
379
- const actionKeys = (id) =>
380
- ACTIONS[id] ??
381
- BRANCH_ACTIONS[id] ??
382
- COMMIT_ACTIONS[id] ??
383
- NPM_ACTIONS[id] ??
384
- PACKAGE_ACTIONS[id] ??
385
- DASH_ACTIONS[id] ??
386
- [];
439
+ const ACTION_KEYS = {
440
+ ...DASH_ACTIONS,
441
+ ...AGENT_ACTIONS,
442
+ ...PACKAGE_ACTIONS,
443
+ ...NPM_ACTIONS,
444
+ ...COMMIT_ACTIONS,
445
+ ...BRANCH_ACTIONS,
446
+ ...ACTIONS,
447
+ };
448
+
449
+ const actionKeys = (id) => ACTION_KEYS[id] ?? [];
387
450
 
388
451
  const actionLetter = (id) => ACTION_HOT[id] ?? actionKeys(id)[0];
389
452
 
@@ -401,15 +464,13 @@ const YES_NO = [
401
464
 
402
465
  const CONFIRM = {
403
466
  confirmQuit: {
404
- choices: [
405
- { letter: 'f', label: 'finish as ready', lead: ' ' },
406
- { letter: 'c', label: 'continue next time', lead: ' ' },
407
- ],
467
+ choices: YES_NO,
468
+ prefix: (view) => view.quitWarning || ' exit will terminate subprocesses',
408
469
  keys: {
409
- f: 'ready',
410
- c: 'editing',
411
- 'ctrl-c': 'ready',
470
+ y: 'confirm',
471
+ n: 'cancel',
412
472
  escape: 'cancel',
473
+ 'ctrl-c': 'cancel',
413
474
  },
414
475
  },
415
476
  confirmUpdate: {
@@ -445,6 +506,7 @@ const CONFIRM = {
445
506
  confirmCommit: {
446
507
  choices: [
447
508
  { letter: 'esc', label: 'esc cancel', lead: ' ' },
509
+ { letter: 'u', label: 'update', lead: ' ' },
448
510
  { letter: 'c', label: 'commit', lead: ' ' },
449
511
  { letter: 'a', label: 'amend', lead: ' ' },
450
512
  { letter: 'f', label: 'fixup', lead: ' ' },
@@ -453,6 +515,8 @@ const CONFIRM = {
453
515
  keys: {
454
516
  esc: 'cancel',
455
517
  escape: 'cancel',
518
+ enter: 'update',
519
+ u: 'update',
456
520
  c: 'commit',
457
521
  a: 'amend',
458
522
  f: 'fixup',
@@ -479,6 +543,7 @@ const promptFromChoices = (choices) =>
479
543
 
480
544
  module.exports = {
481
545
  LIST_PANES,
546
+ DASH_TILES,
482
547
  DASH_IDS,
483
548
  DASH_BLOCKS,
484
549
  DASH_ALLOWED,
@@ -486,8 +551,6 @@ module.exports = {
486
551
  REPOS_ALLOWED,
487
552
  ACTIONS,
488
553
  ACTION_IDS,
489
- BRANCH_ACTIONS,
490
- COMMIT_ACTIONS,
491
554
  DIFF_DISABLED,
492
555
  UNIT_DISABLED,
493
556
  FILES_DISABLED,
@@ -1,9 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const ansi = require('./ansi.js');
4
- const { graphemes, visibleWidth } = ansi;
5
- const wrap = require('./wrap.js');
6
- const { wrapMove } = wrap;
3
+ const { clamp } = require('../common/utilities.js');
4
+ const { graphemes, visibleWidth } = require('../term/ansi.js');
5
+ const { wrapMove } = require('../term/wrap.js');
7
6
 
8
7
  const UNDO_CAP = 100;
9
8
  const WORD_RE = /[\p{L}\p{N}_]/u;
@@ -127,7 +126,7 @@ class Editor {
127
126
  place(offset) {
128
127
  this.wantCol = null;
129
128
  this.anchor = null;
130
- this.cursor = Math.max(0, Math.min(offset, this.text.length));
129
+ this.cursor = clamp(offset, 0, this.text.length);
131
130
  }
132
131
 
133
132
  backspace() {
@@ -145,8 +144,8 @@ class Editor {
145
144
  if (this.removeSpan()) return;
146
145
  const { parts, i, pos } = graphemeIndex(this.text, this.cursor);
147
146
  if (i >= parts.length) return;
148
- const next =
149
- this.text.slice(0, pos) + this.text.slice(pos + parts[i].length);
147
+ const size = parts[i].length;
148
+ const next = this.text.slice(0, pos) + this.text.slice(pos + size);
150
149
  this.edit(next, this.cursor);
151
150
  }
152
151
 
@@ -206,7 +205,7 @@ class Editor {
206
205
  }
207
206
  const rows = this.text.split('\n');
208
207
  const { line, col } = this.linePos();
209
- const next = Math.min(Math.max(line + delta, 0), rows.length - 1);
208
+ const next = clamp(line + delta, 0, rows.length - 1);
210
209
  if (next === line) return;
211
210
  let cursor = 0;
212
211
  for (let i = 0; i < next; i++) cursor += rows[i].length + 1;
@@ -1,16 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const actions = require('./session/actions.js');
4
-
5
- const { ACTIONS, ACTION_IDS, BRANCH_ACTIONS, COMMIT_ACTIONS } = actions;
6
- const { DASH_IDS, DASH_HIDDEN } = actions;
7
- const { DIFF_DISABLED, FILES_GIT_DISABLED, FILES_HIDDEN } = actions;
8
- const { TASKS_DISABLED, BRANCHES_DISABLED } = actions;
9
- const { COMMITS_DISABLED, UNIT_DISABLED, NPM_DISABLED } = actions;
10
- const { diffGitDisabled, branchGitDisabled, commitGitDisabled } = actions;
11
- const { entryHasDiff, lineHasDiff, withoutMoves } = actions;
12
- const { actionLetter, buttonWord } = actions;
13
- const { CONFIRM, confirmPrefix, promptFromChoices } = actions;
3
+ const actions = require('./actions.js');
4
+
5
+ const { ACTIONS } = actions;
14
6
 
15
7
  const CSI_KEYS = {
16
8
  A: 'up',
@@ -19,6 +11,7 @@ const CSI_KEYS = {
19
11
  D: 'left',
20
12
  H: 'home',
21
13
  F: 'end',
14
+ Z: 'shift-tab',
22
15
  };
23
16
 
24
17
  const SS3_KEYS = {
@@ -68,14 +61,16 @@ const MOUSE_WHEEL = {
68
61
  65: 'wheelDown',
69
62
  };
70
63
 
64
+ const ARROW_KEYS = ['left', 'right', 'up', 'down'];
65
+ const EDGE_KEYS = ['home', 'end'];
66
+
71
67
  const NEED_MORE = { needMore: true };
72
68
 
73
69
  const keyAt = (key, end) => ({ event: { type: 'key', key }, end });
74
70
 
75
71
  const modifiedKey = (base, modText) => {
76
- const arrow =
77
- base === 'left' || base === 'right' || base === 'up' || base === 'down';
78
- const end = base === 'home' || base === 'end';
72
+ const arrow = ARROW_KEYS.includes(base);
73
+ const end = EDGE_KEYS.includes(base);
79
74
  if (!arrow && !end) return base;
80
75
  const mod = parseInt(modText, 10);
81
76
  const bits = Number.isFinite(mod) ? mod - 1 : 0;
@@ -183,6 +178,11 @@ const decodeChunk = (text, carry = '') => {
183
178
  return { events, carry: '' };
184
179
  };
185
180
 
181
+ const isTextKey = (key) => {
182
+ if (!key || key.length !== 1) return false;
183
+ return key.charCodeAt(0) >= 32;
184
+ };
185
+
186
186
  const hitAction = (hits, x) => {
187
187
  for (const hit of hits) {
188
188
  if (x >= hit.x0 && x < hit.x1) return hit.id;
@@ -190,32 +190,4 @@ const hitAction = (hits, x) => {
190
190
  return null;
191
191
  };
192
192
 
193
- module.exports = {
194
- ACTIONS,
195
- ACTION_IDS,
196
- DASH_IDS,
197
- DASH_HIDDEN,
198
- BRANCH_ACTIONS,
199
- COMMIT_ACTIONS,
200
- DIFF_DISABLED,
201
- FILES_HIDDEN,
202
- FILES_GIT_DISABLED,
203
- TASKS_DISABLED,
204
- BRANCHES_DISABLED,
205
- COMMITS_DISABLED,
206
- NPM_DISABLED,
207
- UNIT_DISABLED,
208
- diffGitDisabled,
209
- entryHasDiff,
210
- lineHasDiff,
211
- withoutMoves,
212
- branchGitDisabled,
213
- commitGitDisabled,
214
- decodeChunk,
215
- actionLetter,
216
- buttonWord,
217
- hitAction,
218
- CONFIRM,
219
- confirmPrefix,
220
- promptFromChoices,
221
- };
193
+ module.exports = { ACTIONS, decodeChunk, hitAction, isTextKey };
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ const { Editor } = require('./editor.js');
4
+ const { isTextKey } = require('./keys.js');
5
+ const clipboard = require('../term/clipboard.js');
6
+
7
+ const copyLine = (editor, ui) => {
8
+ const text = editor.selectedText();
9
+ if (!text) return;
10
+ const ok = clipboard.copyText(text, ui.stdout);
11
+ ui.status = ok ? 'copied' : 'copy failed';
12
+ };
13
+
14
+ const cutLine = (editor, ui) => {
15
+ if (!editor.hasSelect()) return;
16
+ copyLine(editor, ui);
17
+ editor.removeSpan();
18
+ };
19
+
20
+ const pasteLine = (editor) => {
21
+ const raw = clipboard.pasteText() ?? '';
22
+ const text = `${raw}`.replaceAll('\r', '').replaceAll('\n', '');
23
+ if (text) editor.insert(text);
24
+ };
25
+
26
+ const EDIT_OP = {
27
+ left: (editor) => editor.move(-1),
28
+ right: (editor) => editor.move(1),
29
+ 'shift-left': (editor) => editor.move(-1, true),
30
+ 'shift-right': (editor) => editor.move(1, true),
31
+ 'ctrl-left': (editor) => editor.moveWord(-1),
32
+ 'ctrl-right': (editor) => editor.moveWord(1),
33
+ 'ctrl-shift-left': (editor) => editor.moveWord(-1, true),
34
+ 'ctrl-shift-right': (editor) => editor.moveWord(1, true),
35
+ backspace: (editor) => editor.backspace(),
36
+ delete: (editor) => editor.delete(),
37
+ };
38
+
39
+ const LINE_OP = {
40
+ ...EDIT_OP,
41
+ 'ctrl-x': cutLine,
42
+ 'ctrl-v': (editor) => pasteLine(editor),
43
+ };
44
+
45
+ const END_OP = {
46
+ home: (editor) => editor.home(),
47
+ end: (editor) => editor.end(),
48
+ 'shift-home': (editor) => editor.home(true),
49
+ 'shift-end': (editor) => editor.end(true),
50
+ };
51
+
52
+ const LINE_ENDS = { ...LINE_OP, ...END_OP };
53
+
54
+ const lineKey = (editor, key, ui, ends) => {
55
+ if (key === 'ctrl-c') {
56
+ copyLine(editor, ui);
57
+ return 'caret';
58
+ }
59
+ const op = (ends ? LINE_ENDS : LINE_OP)[key];
60
+ if (!op && !isTextKey(key)) return null;
61
+ const before = editor.text;
62
+ if (op) op(editor, ui);
63
+ else editor.insert(key);
64
+ return editor.text === before ? 'caret' : 'text';
65
+ };
66
+
67
+ const lineField = (name) => {
68
+ const editor = new Editor('');
69
+ return {
70
+ editor,
71
+ get [name]() {
72
+ return editor.text;
73
+ },
74
+ };
75
+ };
76
+
77
+ module.exports = { EDIT_OP, copyLine, cutLine, lineKey, lineField };
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ const { clamp } = require('../common/utilities.js');
4
+ const { Editor } = require('./editor.js');
5
+
6
+ const MENU_ROWS = 8;
7
+
8
+ const menuRows = (bodyH, fraction = 1) => {
9
+ const height = Math.min(bodyH > 0 ? bodyH : MENU_ROWS, MENU_ROWS);
10
+ return Math.max(1, Math.floor(height * fraction));
11
+ };
12
+
13
+ const pickDelta = (key, page, half) => {
14
+ const steps = {
15
+ up: -1,
16
+ down: 1,
17
+ pageUp: -page,
18
+ pageDown: page,
19
+ 'ctrl-b': -page,
20
+ 'ctrl-f': page,
21
+ 'ctrl-u': -half,
22
+ 'ctrl-d': half,
23
+ home: -Infinity,
24
+ end: Infinity,
25
+ };
26
+ return Object.hasOwn(steps, key) ? steps[key] : 0;
27
+ };
28
+
29
+ class MenuPick {
30
+ constructor(field, cursor) {
31
+ this.field = field;
32
+ this.cursor = cursor;
33
+ this.scroll = 0;
34
+ this.editor = new Editor('');
35
+ }
36
+
37
+ get query() {
38
+ return this.editor.text;
39
+ }
40
+
41
+ filter(items) {
42
+ const query = this.query.trim().toLowerCase();
43
+ if (!query) return items;
44
+ return items.filter((name) => name.toLowerCase().includes(query));
45
+ }
46
+
47
+ touch() {
48
+ this.cursor = 0;
49
+ this.scroll = 0;
50
+ }
51
+
52
+ move(delta, items, isOff) {
53
+ const last = items.length - 1;
54
+ const step = delta < 0 ? -1 : 1;
55
+ const goal = clamp(this.cursor + delta, 0, last);
56
+ let cursor = this.cursor;
57
+ for (let next = cursor; next !== goal;) {
58
+ next += step;
59
+ if (!isOff(items[next])) cursor = next;
60
+ }
61
+ if (isOff(items[cursor])) {
62
+ let scan = goal;
63
+ while (scan >= 0 && scan <= last && isOff(items[scan])) scan += step;
64
+ if (scan >= 0 && scan <= last) cursor = scan;
65
+ }
66
+ this.cursor = cursor;
67
+ }
68
+ }
69
+
70
+ module.exports = { menuRows, pickDelta, MenuPick };