reslop 0.2.7 → 0.3.1

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 +30 -1
  2. package/README.md +16 -14
  3. package/bin/reslop.js +8 -5
  4. package/lib/agents/agents.js +573 -0
  5. package/lib/agents/allow.js +71 -0
  6. package/lib/agents/allow.json +17 -0
  7. package/lib/agents/models.js +467 -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} +151 -95
  34. package/lib/git/git.js +71 -0
  35. package/lib/git/worktree.js +504 -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 +114 -48
  45. package/lib/{editor.js → input/editor.js} +16 -8
  46. package/lib/{keys.js → input/keys.js} +24 -45
  47. package/lib/input/line-edit.js +78 -0
  48. package/lib/input/menu-pick.js +70 -0
  49. package/lib/render/agents.js +642 -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 +385 -0
  59. package/lib/render/frame.js +123 -25
  60. package/lib/render/header.js +176 -0
  61. package/lib/render/log.js +320 -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} +48 -19
  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 +14 -0
  81. package/lib/review/format.js +420 -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 +976 -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 -40
  98. package/lib/session/change-actions.js +34 -100
  99. package/lib/session/commit.js +91 -52
  100. package/lib/session/compose.js +189 -147
  101. package/lib/session/dashboard.js +23 -22
  102. package/lib/session/dispatch.js +86 -21
  103. package/lib/session/files-pane.js +47 -26
  104. package/lib/session/find.js +16 -29
  105. package/lib/session/import.js +93 -0
  106. package/lib/session/items.js +8 -17
  107. package/lib/session/lifecycle.js +78 -72
  108. package/lib/session/load.js +2 -4
  109. package/lib/session/navigation.js +6 -3
  110. package/lib/session/npm/history.js +250 -0
  111. package/lib/session/npm/npm.js +836 -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} +158 -63
  120. package/lib/session/tasks.js +321 -66
  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} +4 -3
  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,65 @@ 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
+ agentContinue: ['c'],
73
+ agentStop: ['s'],
74
+ agentRerun: ['r'],
67
75
  };
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',
76
+
77
+ const AGENT_LIST_ACTIONS = {
78
+ agentModel: AGENT_ACTIONS.agentModel,
79
+ agentEffort: AGENT_ACTIONS.agentEffort,
80
+ agentFast: AGENT_ACTIONS.agentFast,
81
+ agentContext: AGENT_ACTIONS.agentContext,
82
+ agentReview: AGENT_ACTIONS.agentReview,
83
+ agentContinue: AGENT_ACTIONS.agentContinue,
77
84
  };
78
- const DASH_ALLOWED = [...DASH_IDS, 'pull', 'push', 'quit', 'theme'];
85
+
86
+ const DASH_TILES = [
87
+ { id: 'files', key: 'f', action: 'dashFiles', footer: true },
88
+ { id: 'diffs', key: 'd', action: 'dashDiffs', footer: true },
89
+ { id: 'tasks', key: 't', action: 'dashTasks', footer: true },
90
+ { id: 'branches', key: 'b', action: 'dashBranches', footer: true },
91
+ { id: 'commits', key: 'c', action: 'dashCommits', footer: true },
92
+ { id: 'run', key: 'r', action: 'dashRun', footer: true },
93
+ { id: 'npm', key: 'n', action: 'dashNpm', footer: true },
94
+ { id: 'agents', key: 'a', action: 'dashAgents', footer: false },
95
+ ];
96
+ const DASH_ACTIONS = {};
97
+ const DASH_BLOCKS = {};
98
+ const DASH_KEYS = {};
99
+ for (const tile of DASH_TILES) {
100
+ if (tile.footer) DASH_ACTIONS[tile.action] = [tile.key];
101
+ DASH_BLOCKS[tile.action] = tile.id;
102
+ DASH_KEYS[tile.key] = tile.action;
103
+ }
104
+ const DASH_IDS = Object.keys(DASH_ACTIONS);
105
+ const DASH_ALLOWED = [
106
+ ...Object.keys(DASH_BLOCKS),
107
+ 'back',
108
+ 'pull',
109
+ 'push',
110
+ 'quit',
111
+ 'theme',
112
+ ];
79
113
  const REPOS_ALLOWED = [
80
114
  'repoLeft',
81
115
  'repoRight',
82
116
  'repoUp',
83
117
  'repoDown',
84
118
  'open',
119
+ 'back',
85
120
  'quit',
86
121
  'theme',
87
122
  ];
@@ -95,10 +130,16 @@ const ACTION_HOT = {
95
130
  newCommit: 'ins',
96
131
  feedback: 'q',
97
132
  check: 'space',
133
+ taskUp: 'ctrl+up',
134
+ taskDown: 'ctrl+down',
135
+ taskNudge: 'up/dn',
136
+ plan: 'p',
137
+ import: 'i',
98
138
  theme: 'l',
139
+ lines: '+l',
99
140
  npmNew: 'ins',
100
141
  npmDrop: 'del',
101
- npmStop: 'esc',
142
+ npmStop: 's',
102
143
  packageNew: 'ins',
103
144
  packageDrop: 'del',
104
145
  };
@@ -107,7 +148,13 @@ const ACTION_LABEL = {
107
148
  ...DASH_BLOCKS,
108
149
  revert: 'drop',
109
150
  check: 'space',
151
+ taskUp: 'ctrl+up',
152
+ taskDown: 'ctrl+down',
153
+ taskNudge: 'ctrl+up/dn',
154
+ plan: 'plan',
155
+ import: 'import',
110
156
  theme: 'light',
157
+ lines: 'ctrl+l',
111
158
  layout: 'mode',
112
159
  tasks: 'tasks',
113
160
  view: 'view',
@@ -125,9 +172,17 @@ const ACTION_LABEL = {
125
172
  packageDrop: 'delete',
126
173
  packageWanted: 'wanted',
127
174
  packageLatest: 'latest',
128
- npmStop: 'esc',
175
+ npmStop: 'stop',
129
176
  npmVerbose: 'verbose',
130
177
  npmRerun: 're-run',
178
+ agentModel: 'model',
179
+ agentEffort: 'effort',
180
+ agentFast: 'fast',
181
+ agentContext: 'context',
182
+ agentReview: 'plan',
183
+ agentContinue: 'continue',
184
+ agentStop: 'stop',
185
+ agentRerun: 're-run',
131
186
  quit: 'q',
132
187
  prev: '←',
133
188
  next: '→',
@@ -193,8 +248,12 @@ const BRANCHES_DISABLED = [
193
248
  const COMMITS_DISABLED = BRANCHES_DISABLED.filter((id) => id !== 'commit');
194
249
  const NPM_DISABLED = [...BRANCHES_DISABLED, 'pull', 'push'];
195
250
 
196
- const isTasksTarget = (item) =>
197
- !!(item && (item.kind === 'tasks' || item.origin === 'task'));
251
+ const isTasksTarget = (item) => {
252
+ if (!item) return false;
253
+ const tasks = item.kind === 'tasks';
254
+ const task = item.origin === 'task';
255
+ return tasks || task;
256
+ };
198
257
 
199
258
  const DIFF_MOVE_IDS = ['add', 'unstage', 'revert'];
200
259
 
@@ -214,8 +273,10 @@ const entryHasDiff = (entry) => {
214
273
  return total > 0;
215
274
  };
216
275
 
217
- const lineHasDiff = (line) =>
218
- !!line && (line.type === 'add' || line.type === 'del');
276
+ const lineHasDiff = (line) => {
277
+ if (!line) return false;
278
+ return line.type === 'add' || line.type === 'del';
279
+ };
219
280
 
220
281
  const withoutMoves = (off, hasDiff) => {
221
282
  if (hasDiff) return off;
@@ -286,7 +347,9 @@ const disabledActions = (pane, item) => {
286
347
  if (pane === 'commits') {
287
348
  return [...COMMITS_DISABLED, ...commitWriteDisabled(item)];
288
349
  }
289
- if (pane === 'npm' || pane === 'packages') return NPM_DISABLED;
350
+ if (pane === 'npm' || pane === 'packages' || pane === 'agents') {
351
+ return NPM_DISABLED;
352
+ }
290
353
  if (pane === 'tasks' || onTodos) return TASKS_DISABLED;
291
354
  return [...DIFF_DISABLED, ...diffGitDisabled(item)];
292
355
  };
@@ -323,7 +386,7 @@ const keysForActions = (actions) => {
323
386
 
324
387
  const PANE_ONLY = ['branch', 'file', 'diff'];
325
388
  const REVIEW_DROP = { d: 'revert' };
326
- const DIFF_KEYS = { ...REVIEW_DROP, q: 'feedback' };
389
+ const DIFF_KEYS = { ...REVIEW_DROP, q: 'feedback', 'ctrl-l': 'lines' };
327
390
  const BRANCH_KEYS = keysForActions(BRANCH_ACTIONS);
328
391
  const COMMIT_KEYS = { ...keysForActions(COMMIT_ACTIONS), s: 'push' };
329
392
  const NPM_KEYS = {
@@ -332,8 +395,7 @@ const NPM_KEYS = {
332
395
  delete: 'npmDrop',
333
396
  l: 'npmLogs',
334
397
  };
335
- const DASH_KEYS = keysForActions(DASH_ACTIONS);
336
- const SCREEN_KEYS = DASH_KEYS;
398
+ const SCREEN_KEYS = keysForActions(DASH_ACTIONS);
337
399
  const PANE_KEYS = {
338
400
  dashboard: { ...DASH_KEYS, p: 'pull', s: 'push' },
339
401
  files: REVIEW_DROP,
@@ -343,7 +405,8 @@ const PANE_KEYS = {
343
405
  commits: COMMIT_KEYS,
344
406
  npm: NPM_KEYS,
345
407
  packages: keysForActions(PACKAGE_ACTIONS),
346
- tasks: { delete: 'drop' },
408
+ agents: keysForActions(AGENT_LIST_ACTIONS),
409
+ tasks: { delete: 'drop', p: 'plan', i: 'import' },
347
410
  repos: {
348
411
  left: 'repoLeft',
349
412
  right: 'repoRight',
@@ -376,14 +439,17 @@ const actionFromKey = (key, pane) => {
376
439
  return KEY_TO_ACTION[key] ?? null;
377
440
  };
378
441
 
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
- [];
442
+ const ACTION_KEYS = {
443
+ ...DASH_ACTIONS,
444
+ ...AGENT_ACTIONS,
445
+ ...PACKAGE_ACTIONS,
446
+ ...NPM_ACTIONS,
447
+ ...COMMIT_ACTIONS,
448
+ ...BRANCH_ACTIONS,
449
+ ...ACTIONS,
450
+ };
451
+
452
+ const actionKeys = (id) => ACTION_KEYS[id] ?? [];
387
453
 
388
454
  const actionLetter = (id) => ACTION_HOT[id] ?? actionKeys(id)[0];
389
455
 
@@ -401,15 +467,13 @@ const YES_NO = [
401
467
 
402
468
  const CONFIRM = {
403
469
  confirmQuit: {
404
- choices: [
405
- { letter: 'f', label: 'finish as ready', lead: ' ' },
406
- { letter: 'c', label: 'continue next time', lead: ' ' },
407
- ],
470
+ choices: YES_NO,
471
+ prefix: (view) => view.quitWarning || ' exit will terminate subprocesses',
408
472
  keys: {
409
- f: 'ready',
410
- c: 'editing',
411
- 'ctrl-c': 'ready',
473
+ y: 'confirm',
474
+ n: 'cancel',
412
475
  escape: 'cancel',
476
+ 'ctrl-c': 'cancel',
413
477
  },
414
478
  },
415
479
  confirmUpdate: {
@@ -445,6 +509,7 @@ const CONFIRM = {
445
509
  confirmCommit: {
446
510
  choices: [
447
511
  { letter: 'esc', label: 'esc cancel', lead: ' ' },
512
+ { letter: 'u', label: 'update', lead: ' ' },
448
513
  { letter: 'c', label: 'commit', lead: ' ' },
449
514
  { letter: 'a', label: 'amend', lead: ' ' },
450
515
  { letter: 'f', label: 'fixup', lead: ' ' },
@@ -453,6 +518,8 @@ const CONFIRM = {
453
518
  keys: {
454
519
  esc: 'cancel',
455
520
  escape: 'cancel',
521
+ enter: 'update',
522
+ u: 'update',
456
523
  c: 'commit',
457
524
  a: 'amend',
458
525
  f: 'fixup',
@@ -479,6 +546,7 @@ const promptFromChoices = (choices) =>
479
546
 
480
547
  module.exports = {
481
548
  LIST_PANES,
549
+ DASH_TILES,
482
550
  DASH_IDS,
483
551
  DASH_BLOCKS,
484
552
  DASH_ALLOWED,
@@ -486,8 +554,6 @@ module.exports = {
486
554
  REPOS_ALLOWED,
487
555
  ACTIONS,
488
556
  ACTION_IDS,
489
- BRANCH_ACTIONS,
490
- COMMIT_ACTIONS,
491
557
  DIFF_DISABLED,
492
558
  UNIT_DISABLED,
493
559
  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() {
@@ -140,13 +139,22 @@ class Editor {
140
139
  this.edit(next, this.cursor - size);
141
140
  }
142
141
 
142
+ backspaceWord() {
143
+ this.wantCol = null;
144
+ if (this.removeSpan()) return;
145
+ const { parts, i } = graphemeIndex(this.text, this.cursor);
146
+ const start = offsetAt(parts, wordIndex(parts, i, -1));
147
+ if (start === this.cursor) return;
148
+ this.edit(this.text.slice(0, start) + this.text.slice(this.cursor), start);
149
+ }
150
+
143
151
  delete() {
144
152
  this.wantCol = null;
145
153
  if (this.removeSpan()) return;
146
154
  const { parts, i, pos } = graphemeIndex(this.text, this.cursor);
147
155
  if (i >= parts.length) return;
148
- const next =
149
- this.text.slice(0, pos) + this.text.slice(pos + parts[i].length);
156
+ const size = parts[i].length;
157
+ const next = this.text.slice(0, pos) + this.text.slice(pos + size);
150
158
  this.edit(next, this.cursor);
151
159
  }
152
160
 
@@ -206,7 +214,7 @@ class Editor {
206
214
  }
207
215
  const rows = this.text.split('\n');
208
216
  const { line, col } = this.linePos();
209
- const next = Math.min(Math.max(line + delta, 0), rows.length - 1);
217
+ const next = clamp(line + delta, 0, rows.length - 1);
210
218
  if (next === line) return;
211
219
  let cursor = 0;
212
220
  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 = {
@@ -52,6 +45,10 @@ const CSI_TILDE = {
52
45
  24: 'f12',
53
46
  };
54
47
 
48
+ const CSI_U = {
49
+ 127: 'backspace',
50
+ };
51
+
55
52
  const CSI_MAX = 16;
56
53
 
57
54
  const LITERAL_KEY = {
@@ -68,19 +65,22 @@ const MOUSE_WHEEL = {
68
65
  65: 'wheelDown',
69
66
  };
70
67
 
68
+ const ARROW_KEYS = ['left', 'right', 'up', 'down'];
69
+ const EDGE_KEYS = ['home', 'end'];
70
+
71
71
  const NEED_MORE = { needMore: true };
72
72
 
73
73
  const keyAt = (key, end) => ({ event: { type: 'key', key }, end });
74
74
 
75
75
  const modifiedKey = (base, modText) => {
76
- const arrow =
77
- base === 'left' || base === 'right' || base === 'up' || base === 'down';
78
- const end = base === 'home' || base === 'end';
79
- if (!arrow && !end) return base;
76
+ const arrow = ARROW_KEYS.includes(base);
77
+ const end = EDGE_KEYS.includes(base);
80
78
  const mod = parseInt(modText, 10);
81
79
  const bits = Number.isFinite(mod) ? mod - 1 : 0;
82
80
  const ctrl = (bits & 4) !== 0;
83
81
  const shift = (bits & 1) !== 0;
82
+ if (base === 'backspace' && ctrl) return 'ctrl-backspace';
83
+ if (!arrow && !end) return base;
84
84
  if (ctrl && shift && arrow) return `ctrl-shift-${base}`;
85
85
  if (ctrl && arrow) return `ctrl-${base}`;
86
86
  if (shift) return `shift-${base}`;
@@ -89,7 +89,9 @@ const modifiedKey = (base, modText) => {
89
89
 
90
90
  const keyFromCsi = (paramsText, final) => {
91
91
  const params = paramsText ? paramsText.split(';') : [];
92
- const base = final === '~' ? CSI_TILDE[params[0]] : CSI_KEYS[final];
92
+ let base = CSI_KEYS[final];
93
+ if (final === '~') base = CSI_TILDE[params[0]];
94
+ if (final === 'u') base = CSI_U[params[0]];
93
95
  if (!base) return null;
94
96
  if (params.length < 2) return base;
95
97
  return modifiedKey(base, params[1]);
@@ -183,6 +185,11 @@ const decodeChunk = (text, carry = '') => {
183
185
  return { events, carry: '' };
184
186
  };
185
187
 
188
+ const isTextKey = (key) => {
189
+ if (!key || key.length !== 1) return false;
190
+ return key.charCodeAt(0) >= 32;
191
+ };
192
+
186
193
  const hitAction = (hits, x) => {
187
194
  for (const hit of hits) {
188
195
  if (x >= hit.x0 && x < hit.x1) return hit.id;
@@ -190,32 +197,4 @@ const hitAction = (hits, x) => {
190
197
  return null;
191
198
  };
192
199
 
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
- };
200
+ module.exports = { ACTIONS, decodeChunk, hitAction, isTextKey };
@@ -0,0 +1,78 @@
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
+ 'ctrl-backspace': (editor) => editor.backspaceWord(),
37
+ delete: (editor) => editor.delete(),
38
+ };
39
+
40
+ const LINE_OP = {
41
+ ...EDIT_OP,
42
+ 'ctrl-x': cutLine,
43
+ 'ctrl-v': (editor) => pasteLine(editor),
44
+ };
45
+
46
+ const END_OP = {
47
+ home: (editor) => editor.home(),
48
+ end: (editor) => editor.end(),
49
+ 'shift-home': (editor) => editor.home(true),
50
+ 'shift-end': (editor) => editor.end(true),
51
+ };
52
+
53
+ const LINE_ENDS = { ...LINE_OP, ...END_OP };
54
+
55
+ const lineKey = (editor, key, ui, ends) => {
56
+ if (key === 'ctrl-c') {
57
+ copyLine(editor, ui);
58
+ return 'caret';
59
+ }
60
+ const op = (ends ? LINE_ENDS : LINE_OP)[key];
61
+ if (!op && !isTextKey(key)) return null;
62
+ const before = editor.text;
63
+ if (op) op(editor, ui);
64
+ else editor.insert(key);
65
+ return editor.text === before ? 'caret' : 'text';
66
+ };
67
+
68
+ const lineField = (name, text = '') => {
69
+ const editor = new Editor(text);
70
+ return {
71
+ editor,
72
+ get [name]() {
73
+ return editor.text;
74
+ },
75
+ };
76
+ };
77
+
78
+ 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 };