reslop 0.2.3 → 0.2.5

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 (89) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +8 -4
  3. package/bin/reslop.js +4 -2
  4. package/lib/api.js +2 -1
  5. package/lib/capabilities.js +0 -4
  6. package/lib/cli.js +19 -10
  7. package/lib/clipboard.js +45 -4
  8. package/lib/deps-diff.js +61 -118
  9. package/lib/deps.js +166 -235
  10. package/lib/detect.js +4 -2
  11. package/lib/diff/align.js +2 -1
  12. package/lib/diff/diff.js +6 -3
  13. package/lib/diff/display.js +2 -1
  14. package/lib/diff/inline.js +68 -2
  15. package/lib/diff/patch.js +2 -1
  16. package/lib/diff/unit.js +15 -30
  17. package/lib/editor.js +91 -13
  18. package/lib/files.js +54 -27
  19. package/lib/git-branches.js +107 -154
  20. package/lib/git-deps.js +78 -155
  21. package/lib/git-worktree.js +508 -0
  22. package/lib/git.js +23 -580
  23. package/lib/github.js +9 -8
  24. package/lib/gitlab.js +5 -4
  25. package/lib/highlight/bash.js +2 -1
  26. package/lib/highlight/core.js +11 -0
  27. package/lib/highlight/css.js +2 -1
  28. package/lib/highlight/csv.js +2 -1
  29. package/lib/highlight/dot.js +3 -12
  30. package/lib/highlight/html.js +2 -1
  31. package/lib/highlight/json.js +2 -1
  32. package/lib/highlight/log.js +5 -11
  33. package/lib/highlight/txt.js +3 -12
  34. package/lib/highlight.js +2 -1
  35. package/lib/keys.js +22 -12
  36. package/lib/manifest.js +4 -6
  37. package/lib/npm-commands.js +82 -24
  38. package/lib/remote.js +21 -20
  39. package/lib/render/branches.js +9 -27
  40. package/lib/render/chrome.js +109 -35
  41. package/lib/render/commits.js +216 -216
  42. package/lib/render/diff.js +160 -47
  43. package/lib/render/files.js +9 -26
  44. package/lib/render/frame.js +97 -118
  45. package/lib/render/notes.js +74 -26
  46. package/lib/render/npm.js +104 -30
  47. package/lib/render/present.js +2 -1
  48. package/lib/render/primitives.js +128 -52
  49. package/lib/render/render.js +2 -1
  50. package/lib/render/unit.js +17 -11
  51. package/lib/report-model.js +6 -4
  52. package/lib/report-parse.js +8 -14
  53. package/lib/report-render.js +38 -21
  54. package/lib/report-run.js +12 -11
  55. package/lib/report-test.js +45 -51
  56. package/lib/report-tools.js +44 -25
  57. package/lib/review-merge.js +102 -131
  58. package/lib/review.js +69 -78
  59. package/lib/select.js +52 -0
  60. package/lib/session/actions.js +72 -87
  61. package/lib/session/branches.js +18 -52
  62. package/lib/session/change-actions.js +175 -147
  63. package/lib/session/commit.js +65 -61
  64. package/lib/session/compose.js +152 -83
  65. package/lib/session/dispatch.js +53 -46
  66. package/lib/session/edit-click.js +57 -0
  67. package/lib/session/files-pane.js +30 -51
  68. package/lib/session/items.js +73 -90
  69. package/lib/session/lifecycle.js +37 -98
  70. package/lib/session/load.js +52 -57
  71. package/lib/session/navigation.js +105 -95
  72. package/lib/session/npm.js +205 -71
  73. package/lib/session/ops.js +200 -45
  74. package/lib/session/pointer.js +33 -8
  75. package/lib/session/progress.js +9 -6
  76. package/lib/session/review.js +35 -53
  77. package/lib/session/tasks.js +292 -0
  78. package/lib/session/terminal.js +17 -19
  79. package/lib/session/unit.js +3 -4
  80. package/lib/session/update.js +76 -96
  81. package/lib/session/view.js +45 -16
  82. package/lib/session/watch.js +47 -27
  83. package/lib/session.js +66 -97
  84. package/lib/source.js +6 -3
  85. package/lib/update.js +5 -13
  86. package/lib/utilities.js +25 -3
  87. package/lib/wrap.js +18 -1
  88. package/package.json +1 -1
  89. package/lib/session/todos.js +0 -293
package/lib/wrap.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { graphemes, graphemeWidth, visibleWidth } = require('./ansi.js');
3
+ const ansi = require('./ansi.js');
4
+ const { graphemes, graphemeWidth, visibleWidth } = ansi;
4
5
 
5
6
  const wrapPlain = (text, width) => {
6
7
  if (width <= 0) return [''];
@@ -28,6 +29,7 @@ const wrapPlain = (text, width) => {
28
29
  if (col + w > width && col > 0) {
29
30
  if (lastBreak >= start) {
30
31
  flush(lastBreak + 1);
32
+ i = start;
31
33
  continue;
32
34
  }
33
35
  flush(i);
@@ -79,6 +81,20 @@ const cursorInWrap = (text, cursor, width) => {
79
81
  return { row, col: visibleWidth(line.text.slice(0, take)) };
80
82
  };
81
83
 
84
+ const indexAtRow = (start, text, col) => {
85
+ const raw = `${text ?? ''}`;
86
+ if (col <= 0) return start;
87
+ let used = 0;
88
+ let idx = 0;
89
+ for (const ch of graphemes(raw)) {
90
+ const w = graphemeWidth(ch, used);
91
+ if (used + w > col) break;
92
+ used += w;
93
+ idx += ch.length;
94
+ }
95
+ return start + idx;
96
+ };
97
+
82
98
  const indexAtWrapCol = (row, col, limit) => {
83
99
  let used = 0;
84
100
  let idx = 0;
@@ -114,5 +130,6 @@ module.exports = {
114
130
  wrapDoc,
115
131
  wrapMultiline,
116
132
  cursorInWrap,
133
+ indexAtRow,
117
134
  wrapMove,
118
135
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reslop",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Interactive code review and repair planning for AI-assisted changes",
5
5
  "keywords": [
6
6
  "code-review",
@@ -1,293 +0,0 @@
1
- 'use strict';
2
-
3
- const files = require('../files.js');
4
- const { isTodosEntry, TODO_FILE } = files;
5
- const review = require('../review.js');
6
- const { addTodo, setTodoText, setTodoDone, removeTodo, checkLabel } = review;
7
-
8
- const TODO_TYPE_SKIP = [
9
- 'enter',
10
- 'escape',
11
- 'tab',
12
- 'backspace',
13
- 'delete',
14
- 'left',
15
- 'right',
16
- 'up',
17
- 'down',
18
- 'home',
19
- 'end',
20
- 'pageUp',
21
- 'pageDown',
22
- 'j',
23
- 'k',
24
- 'n',
25
- 'p',
26
- 'q',
27
- ];
28
-
29
- const isTodoTypeKey = (key) => {
30
- if (!key) return false;
31
- if (TODO_TYPE_SKIP.includes(key)) return false;
32
- if (key.startsWith('ctrl-')) return false;
33
- if (key.startsWith('alt-')) return false;
34
- return key.charCodeAt(0) >= 32;
35
- };
36
-
37
- const listTodos = (api) => {
38
- const notes = api.review.store;
39
- if (!notes) return [];
40
- return notes.todos;
41
- };
42
-
43
- const isDraftCompose = (api) => {
44
- const { state } = api;
45
- return state.composeKind === 'todo' && state.composeTodoId === null;
46
- };
47
-
48
- const todoTexts = (api) => {
49
- const nav = api.nav;
50
- if (!nav.todoOpen) return [];
51
- const todos = listTodos(api);
52
- const texts = new Array(todos.length + 1);
53
- const { state } = api;
54
- const editingTodo = state.composeKind === 'todo';
55
- const editingId = editingTodo ? state.composeTodoId : null;
56
- for (let i = 0; i < todos.length; i++) {
57
- const todo = todos[i];
58
- const live = editingId === todo.id && state.editor;
59
- const text = live ? state.editor.text : todo.text;
60
- texts[i] = checkLabel(text, todo.done);
61
- }
62
- const draft = isDraftCompose(api) && state.editor ? state.editor.text : '';
63
- texts[todos.length] = checkLabel(draft, false);
64
- return texts;
65
- };
66
-
67
- const todoRowCount = (api) => {
68
- if (!api.nav.todoOpen) return 0;
69
- return listTodos(api).length + 1;
70
- };
71
-
72
- const clampedTodoFocus = (api) => {
73
- const last = Math.max(0, todoRowCount(api) - 1);
74
- const focus = api.nav.todoFocus;
75
- if (focus < 0) return 0;
76
- if (focus > last) return last;
77
- return focus;
78
- };
79
-
80
- const moveTodoFocus = (api, delta) => {
81
- const nav = api.nav;
82
- if (!nav.todoOpen) return;
83
- const n = todoRowCount(api);
84
- if (!n) return;
85
- const next = clampedTodoFocus(api) + delta;
86
- if (next < 0) nav.todoFocus = 0;
87
- else if (next >= n) nav.todoFocus = n - 1;
88
- else nav.todoFocus = next;
89
- };
90
-
91
- const openTodoPage = (api) => {
92
- const nav = api.nav;
93
- nav.todoOpen = true;
94
- nav.pane = 'diff';
95
- nav.scroll = 0;
96
- nav.clearSelection();
97
- api.ui.syncReviewPath();
98
- };
99
-
100
- const closeTodoPage = (api) => {
101
- api.nav.todoOpen = false;
102
- api.ui.syncReviewPath();
103
- api.ui.syncFileCursor();
104
- api.ui.lifecycle.catchUpWatch();
105
- };
106
-
107
- const focusTodoPage = (api, todoId) => {
108
- openTodoPage(api);
109
- const todos = listTodos(api);
110
- let focus = 0;
111
- if (todoId === null) {
112
- focus = todos.length;
113
- } else if (todoId !== undefined) {
114
- const at = todos.findIndex((todo) => todo.id === todoId);
115
- if (at >= 0) focus = at;
116
- else if (todos.length) focus = todos.length - 1;
117
- }
118
- api.nav.todoFocus = focus;
119
- api.nav.scroll = 0;
120
- return true;
121
- };
122
-
123
- const startDraftCompose = (api) => {
124
- focusTodoPage(api, null);
125
- api.open('todo', '', null);
126
- };
127
-
128
- const editFocusedTodo = (api) => {
129
- if (!api.nav.todoOpen) return;
130
- const todos = listTodos(api);
131
- const todo = todos[clampedTodoFocus(api)];
132
- if (!todo) return void startDraftCompose(api);
133
- api.open('todo', todo.text, todo.id);
134
- };
135
-
136
- const editNextTodo = (api) => {
137
- if (!api.nav.todoOpen) return;
138
- const last = todoRowCount(api) - 1;
139
- const next = clampedTodoFocus(api) + 1;
140
- if (next <= last) api.nav.todoFocus = next;
141
- editFocusedTodo(api);
142
- };
143
-
144
- const moveTodoCompose = (api, delta, width) => {
145
- if (!api.nav.todoOpen) return;
146
- const editor = api.state.editor;
147
- if (editor && width) {
148
- const before = editor.cursor;
149
- editor.moveLine(delta, width);
150
- if (editor.cursor !== before) return;
151
- }
152
- const last = todoRowCount(api) - 1;
153
- const at = clampedTodoFocus(api);
154
- let next = at + delta;
155
- if (next < 0) next = 0;
156
- if (next > last) next = last;
157
- if (next === at) return;
158
- api.commit();
159
- api.ui.flushReview();
160
- api.close();
161
- const max = Math.max(0, todoRowCount(api) - 1);
162
- api.nav.todoFocus = Math.min(next, max);
163
- editFocusedTodo(api);
164
- };
165
-
166
- const typeIntoTodo = (api, key) => {
167
- const nav = api.nav;
168
- if (!nav.todoOpen) return false;
169
- if (nav.pane !== 'diff') return false;
170
- if (api.ui.mode !== 'review') return false;
171
- if (!isTodoTypeKey(key)) return false;
172
- editFocusedTodo(api);
173
- api.state.editor.insert(key);
174
- api.resetBlink();
175
- return true;
176
- };
177
-
178
- const removeFocusedTodo = (api) => {
179
- const nav = api.nav;
180
- if (nav.pane !== 'diff' || !nav.todoOpen) return;
181
- const todos = listTodos(api);
182
- const todo = todos[clampedTodoFocus(api)];
183
- if (!todo) return;
184
- const notes = api.review.store;
185
- removeTodo(notes, todo.id);
186
- const left = listTodos(api);
187
- if (nav.todoFocus >= left.length) {
188
- nav.todoFocus = Math.max(0, left.length - 1);
189
- }
190
- api.ui.flushReview(true);
191
- };
192
-
193
- const toggleFocusedTodo = (api) => {
194
- const nav = api.nav;
195
- if (!nav.todoOpen || nav.pane !== 'diff') return false;
196
- if (api.ui.mode !== 'review') return false;
197
- const todo = listTodos(api)[clampedTodoFocus(api)];
198
- if (!todo) return false;
199
- setTodoDone(api.review.store, todo.id, !todo.done);
200
- api.ui.flushReview(true);
201
- return true;
202
- };
203
-
204
- const onTodoHit = (api, cursor) => {
205
- if (!api.nav.todoOpen) return;
206
- const todos = listTodos(api);
207
- const draft = cursor === todos.length;
208
- const target = todos[cursor];
209
- if (!draft && !target) return;
210
- const { state } = api;
211
- if (state.composeKind === 'todo') {
212
- if (draft && state.composeTodoId === null) return;
213
- if (target && target.id === state.composeTodoId) return;
214
- api.commit();
215
- api.close();
216
- }
217
- api.nav.todoFocus = cursor;
218
- editFocusedTodo(api);
219
- };
220
-
221
- const onTodoCheck = (api, cursor) => {
222
- if (!api.nav.todoOpen) return;
223
- const todo = listTodos(api)[cursor];
224
- if (!todo) return void onTodoHit(api, cursor);
225
- const { state } = api;
226
- if (state.composeKind === 'todo' && state.composeTodoId !== todo.id) {
227
- api.commit();
228
- api.close();
229
- }
230
- setTodoDone(api.review.store, todo.id, !todo.done);
231
- api.nav.todoFocus = cursor;
232
- api.ui.flushReview(true);
233
- };
234
-
235
- const createTodo = (api) => {
236
- startDraftCompose(api);
237
- };
238
-
239
- const onTodo = (api) => {
240
- const { nav, ui } = api;
241
- if (nav.pane === 'files') {
242
- const entry = ui.fileCursorEntry();
243
- if (entry && !isTodosEntry(entry)) {
244
- nav.index = entry.openIndex ?? entry.firstIndex;
245
- }
246
- }
247
- createTodo(api);
248
- };
249
-
250
- const todoEditView = (api) => {
251
- const { state } = api;
252
- if (state.composeKind !== 'todo') return null;
253
- if (!state.editor) return null;
254
- return { cursor: state.editor.cursor };
255
- };
256
-
257
- const commitTodo = (api, text) => {
258
- const notes = api.review.store;
259
- const id = api.state.composeTodoId;
260
- const todos = notes.todos;
261
- const prev = id === null ? null : todos.find((entry) => entry.id === id);
262
- if (prev) return void setTodoText(notes, id, text);
263
- if (!text.trim()) return;
264
- const todo = addTodo(notes, TODO_FILE, text);
265
- api.state.composeTodoId = todo.id;
266
- };
267
-
268
- const createTodoCompose = (api) => ({
269
- todoTexts: () => todoTexts(api),
270
- clampedTodoFocus: () => clampedTodoFocus(api),
271
- todoRowCount: () => todoRowCount(api),
272
- moveTodoFocus: (delta) => moveTodoFocus(api, delta),
273
- openTodoPage: () => openTodoPage(api),
274
- closeTodoPage: () => closeTodoPage(api),
275
- startDraftCompose: () => startDraftCompose(api),
276
- editFocusedTodo: () => editFocusedTodo(api),
277
- editNextTodo: () => editNextTodo(api),
278
- moveTodoCompose: (delta, width) => moveTodoCompose(api, delta, width),
279
- typeIntoTodo: (key) => typeIntoTodo(api, key),
280
- removeFocusedTodo: () => removeFocusedTodo(api),
281
- onTodoHit: (cursor) => onTodoHit(api, cursor),
282
- onTodoCheck: (cursor) => onTodoCheck(api, cursor),
283
- toggleFocusedTodo: () => toggleFocusedTodo(api),
284
- onTodo: () => onTodo(api),
285
- todoEditView: () => todoEditView(api),
286
- commitTodo: (text) => commitTodo(api, text),
287
- });
288
-
289
- module.exports = {
290
- TODO_TYPE_SKIP,
291
- isTodoTypeKey,
292
- createTodoCompose,
293
- };