reslop 0.2.4 → 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 (67) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/README.md +1 -2
  3. package/lib/capabilities.js +0 -4
  4. package/lib/clipboard.js +39 -1
  5. package/lib/deps-diff.js +0 -3
  6. package/lib/deps.js +144 -176
  7. package/lib/diff/inline.js +66 -1
  8. package/lib/diff/unit.js +11 -28
  9. package/lib/editor.js +69 -9
  10. package/lib/files.js +17 -16
  11. package/lib/git-deps.js +58 -95
  12. package/lib/github.js +7 -7
  13. package/lib/gitlab.js +3 -3
  14. package/lib/highlight/core.js +11 -0
  15. package/lib/highlight/dot.js +2 -12
  16. package/lib/highlight/log.js +4 -11
  17. package/lib/highlight/txt.js +2 -12
  18. package/lib/keys.js +22 -12
  19. package/lib/manifest.js +0 -4
  20. package/lib/npm-commands.js +1 -22
  21. package/lib/remote.js +1 -7
  22. package/lib/render/branches.js +4 -13
  23. package/lib/render/chrome.js +85 -31
  24. package/lib/render/commits.js +95 -207
  25. package/lib/render/diff.js +31 -1
  26. package/lib/render/files.js +7 -16
  27. package/lib/render/frame.js +95 -110
  28. package/lib/render/notes.js +71 -16
  29. package/lib/render/npm.js +104 -18
  30. package/lib/render/primitives.js +108 -45
  31. package/lib/render/unit.js +4 -1
  32. package/lib/report-parse.js +0 -1
  33. package/lib/report-run.js +0 -1
  34. package/lib/report-test.js +40 -47
  35. package/lib/report-tools.js +0 -7
  36. package/lib/review-merge.js +101 -131
  37. package/lib/review.js +64 -77
  38. package/lib/select.js +52 -0
  39. package/lib/session/actions.js +23 -22
  40. package/lib/session/branches.js +5 -3
  41. package/lib/session/change-actions.js +173 -146
  42. package/lib/session/commit.js +32 -13
  43. package/lib/session/compose.js +120 -77
  44. package/lib/session/dispatch.js +36 -21
  45. package/lib/session/edit-click.js +57 -0
  46. package/lib/session/files-pane.js +24 -24
  47. package/lib/session/items.js +69 -89
  48. package/lib/session/lifecycle.js +31 -96
  49. package/lib/session/load.js +51 -58
  50. package/lib/session/navigation.js +105 -96
  51. package/lib/session/npm.js +158 -77
  52. package/lib/session/ops.js +192 -51
  53. package/lib/session/pointer.js +11 -5
  54. package/lib/session/progress.js +9 -6
  55. package/lib/session/review.js +35 -54
  56. package/lib/session/tasks.js +292 -0
  57. package/lib/session/terminal.js +13 -17
  58. package/lib/session/unit.js +3 -4
  59. package/lib/session/update.js +76 -97
  60. package/lib/session/view.js +17 -14
  61. package/lib/session/watch.js +47 -27
  62. package/lib/session.js +34 -79
  63. package/lib/update.js +1 -5
  64. package/lib/utilities.js +21 -1
  65. package/lib/wrap.js +15 -0
  66. package/package.json +1 -1
  67. package/lib/session/todos.js +0 -293
package/lib/update.js CHANGED
@@ -8,7 +8,7 @@ const metautil = require('metautil');
8
8
  const { jsonParse, isHashObject } = metautil;
9
9
  const utilities = require('./utilities.js');
10
10
  const { parseVersion, cmpVersion, npmBin } = utilities;
11
- const { npmOpts, runProc, oneLine } = utilities;
11
+ const { npmOpts, runProc, oneLine, asText } = utilities;
12
12
 
13
13
  const PKG = require('../package.json');
14
14
 
@@ -28,8 +28,6 @@ const emptyCache = () => ({
28
28
  skipped: '',
29
29
  });
30
30
 
31
- const asText = (value) => (typeof value === 'string' ? value : '');
32
-
33
31
  const toInt = (text) => parseInt(text, 10);
34
32
 
35
33
  const isNpmInstall = (pkgDir) => {
@@ -247,8 +245,6 @@ const checkUpdate = async (options = {}) => {
247
245
 
248
246
  module.exports = {
249
247
  CHECK_INTERVAL_MS,
250
- PKG_NAME,
251
- PKG_VERSION,
252
248
  parseVersion,
253
249
  cmpVersion,
254
250
  isNpmInstall,
package/lib/utilities.js CHANGED
@@ -30,6 +30,23 @@ const cmpVersion = (left, right) =>
30
30
  const oneLine = (text, fallback = '') =>
31
31
  split(`${text ?? ''}`.trim(), '\n')[0] || fallback;
32
32
 
33
+ const asText = (value) => (typeof value === 'string' ? value : '');
34
+
35
+ const unquote = (value) => {
36
+ const text = `${value ?? ''}`;
37
+ if (text.length < 2) return text;
38
+ const open = text[0];
39
+ const quoted = open === text.at(-1) && (open === '"' || open === '\x27');
40
+ return quoted ? text.slice(1, -1) : text;
41
+ };
42
+
43
+ const pad2 = (n) => `${n}`.padStart(2, '0');
44
+
45
+ const dateStamp = (date) => {
46
+ const month = pad2(date.getMonth() + 1);
47
+ return `${date.getFullYear()}-${month}-${pad2(date.getDate())}`;
48
+ };
49
+
33
50
  const npmBin = (platform = process.platform) =>
34
51
  platform === 'win32' ? 'npm.cmd' : 'npm';
35
52
 
@@ -214,6 +231,10 @@ module.exports = {
214
231
  parseVersion,
215
232
  cmpVersion,
216
233
  oneLine,
234
+ asText,
235
+ unquote,
236
+ pad2,
237
+ dateStamp,
217
238
  npmBin,
218
239
  spawnBase,
219
240
  npmOpts,
@@ -222,7 +243,6 @@ module.exports = {
222
243
  watchResize,
223
244
  abortError,
224
245
  isAbort,
225
- procResult,
226
246
  runProcSync,
227
247
  runProc,
228
248
  };
package/lib/wrap.js CHANGED
@@ -81,6 +81,20 @@ const cursorInWrap = (text, cursor, width) => {
81
81
  return { row, col: visibleWidth(line.text.slice(0, take)) };
82
82
  };
83
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
+
84
98
  const indexAtWrapCol = (row, col, limit) => {
85
99
  let used = 0;
86
100
  let idx = 0;
@@ -116,5 +130,6 @@ module.exports = {
116
130
  wrapDoc,
117
131
  wrapMultiline,
118
132
  cursorInWrap,
133
+ indexAtRow,
119
134
  wrapMove,
120
135
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reslop",
3
- "version": "0.2.4",
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
- };