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
@@ -4,22 +4,29 @@ const wrap = require('../wrap.js');
4
4
  const { cursorInWrap, wrapDoc } = wrap;
5
5
  const render = require('../render/render.js');
6
6
  const { noteInnerWidth, codeInnerWidth, todoBodyWidth } = render;
7
- const { commitMessageWidth } = require('../render/commits.js');
8
- const { listedCommits } = require('./commit.js');
9
- const { Editor } = require('../editor.js');
7
+ const commits = require('../render/commits.js');
8
+ const { commitMessageWidth } = commits;
9
+ const commit = require('./commit.js');
10
+ const { listedCommits } = commit;
11
+ const editor = require('../editor.js');
12
+ const { Editor } = editor;
10
13
  const files = require('../files.js');
11
- const { itemPath, isTodoItem, listPath, isReadOnlyOrigin } = files;
12
- const { blockAddText } = require('../diff/diff.js');
14
+ const { itemPath, isTaskItem, listPath, isReadOnlyOrigin } = files;
15
+ const diff = require('../diff/diff.js');
16
+ const { blockAddText } = diff;
13
17
  const items = require('./items.js');
14
18
  const { itemFeedKey, canEditCode } = items;
15
19
  const review = require('../review.js');
16
20
  const { setFeedback, setCode, checkLabel, rememberTemplate } = review;
17
- const { createTemplatePick } = require('./templates.js');
18
- const { createTodoCompose } = require('./todos.js');
21
+ const templates = require('./templates.js');
22
+ const { createTemplatePick } = templates;
23
+ const todos = require('./tasks.js');
24
+ const { createTasksCompose } = todos;
19
25
  const unit = require('./unit.js');
26
+ const clipboard = require('../clipboard.js');
20
27
 
21
28
  const COMPOSE_EDITS = ['backspace', 'delete', 'ctrl-z', 'ctrl-y'];
22
- const COMPOSE_LEAVE = ['next', 'prev', 'todo'];
29
+ const COMPOSE_LEAVE = ['next', 'prev', 'tasks'];
23
30
 
24
31
  const KIND = {
25
32
  feedback: {
@@ -46,13 +53,13 @@ const KIND = {
46
53
  command: null,
47
54
  view: 'code',
48
55
  },
49
- todo: {
56
+ tasks: {
50
57
  enter: 'edit-next',
51
58
  tab: 'template',
52
59
  escape: 'save',
53
- move: 'todo',
60
+ move: 'tasks',
54
61
  command: null,
55
- view: 'todo',
62
+ view: 'tasks',
56
63
  },
57
64
  commit: {
58
65
  enter: 'save',
@@ -85,11 +92,10 @@ const initialState = () => ({
85
92
  composeKind: null,
86
93
  commitKind: null,
87
94
  commitBody: '',
88
- composeTodoId: null,
95
+ composeTaskId: null,
89
96
  composeBaseline: '',
90
97
  templateIndex: -1,
91
98
  templateFocus: false,
92
- blinkOn: true,
93
99
  fileDirty: false,
94
100
  fileEditRows: null,
95
101
  fileOpened: '',
@@ -99,19 +105,18 @@ const reset = (state) => {
99
105
  Object.assign(state, initialState());
100
106
  };
101
107
 
102
- const resetBlink = (api) => {
103
- api.state.blinkOn = true;
104
- api.restartBlink();
105
- };
108
+ const viewSize = (api) => api.ui.lastSize ?? api.ui.getSize();
106
109
 
107
- const tickBlink = (api) => {
108
- if (!api.state.composeKind) return;
109
- api.state.blinkOn = !api.state.blinkOn;
110
- api.ui.draw();
110
+ const syncCodeScroll = (api) => {
111
+ const kind = api.state.composeKind;
112
+ if (kind !== 'code' && kind !== 'file') return;
113
+ const ed = api.state.editor;
114
+ if (!ed) return;
115
+ const size = viewSize(api);
116
+ const inner = codeInnerWidth(size.width, api.ui.layout);
117
+ ed.reveal(inner);
111
118
  };
112
119
 
113
- const viewSize = (api) => api.ui.lastSize ?? api.ui.getSize();
114
-
115
120
  const followFileEdit = (api) => {
116
121
  if (api.state.composeKind !== 'file') return;
117
122
  const text = api.state.editor ? api.state.editor.text : null;
@@ -134,7 +139,7 @@ const openCompose = (api, kind, text, todoId) => {
134
139
  const { state, ui, nav } = api;
135
140
  ui.mode = 'compose';
136
141
  state.composeKind = kind;
137
- state.composeTodoId = todoId ?? null;
142
+ state.composeTaskId = todoId ?? null;
138
143
  state.composeBaseline = text;
139
144
  state.fileOpened = kind === 'file' ? text : '';
140
145
  state.editor = new Editor(text);
@@ -142,7 +147,6 @@ const openCompose = (api, kind, text, todoId) => {
142
147
  state.templateFocus = false;
143
148
  ui.status = '';
144
149
  nav.clearSelection();
145
- resetBlink(api);
146
150
  };
147
151
 
148
152
  const closeCompose = (api) => {
@@ -152,7 +156,7 @@ const closeCompose = (api) => {
152
156
  state.composeKind = null;
153
157
  state.commitKind = null;
154
158
  state.commitBody = '';
155
- state.composeTodoId = null;
159
+ state.composeTaskId = null;
156
160
  state.composeBaseline = '';
157
161
  state.fileOpened = '';
158
162
  state.templateIndex = -1;
@@ -185,7 +189,7 @@ const writesCodeFile = (api, item) => {
185
189
 
186
190
  const commitFeedback = (api, text) => {
187
191
  const item = api.ui.current();
188
- if (!item || isTodoItem(item)) return;
192
+ if (!item || isTaskItem(item)) return;
189
193
  const notes = api.review.store;
190
194
  setFeedback(notes, itemFeedKey(item), itemNote(item, text));
191
195
  };
@@ -249,7 +253,7 @@ const saveUnitFile = (api, text) => {
249
253
  api.ui.status = error.message;
250
254
  return false;
251
255
  }
252
- if (typeof api.ui.ignoreWatch === 'function') api.ui.ignoreWatch();
256
+ api.ui.ignoreWatch();
253
257
  api.state.composeBaseline = text;
254
258
  api.state.fileDirty = true;
255
259
  return true;
@@ -268,12 +272,12 @@ const stageEditedFile = (api, rel) => {
268
272
  api.ui.status = error.message;
269
273
  return false;
270
274
  }
271
- if (typeof api.ui.ignoreWatch === 'function') api.ui.ignoreWatch();
275
+ api.ui.ignoreWatch();
272
276
  return true;
273
277
  }
274
278
  if (typeof repo.add !== 'function') return false;
275
279
  let staged = 0;
276
- for (const item of api.ui.items ?? []) {
280
+ for (const item of api.ui.items) {
277
281
  if (listPath(item) !== rel) continue;
278
282
  if (isReadOnlyOrigin(item.origin)) continue;
279
283
  if (item.origin === 'staged') continue;
@@ -283,14 +287,10 @@ const stageEditedFile = (api, rel) => {
283
287
  api.ui.status = error.message;
284
288
  return staged > 0;
285
289
  }
286
- if (api.ui.collection && api.ui.collection.keepOrigin) {
287
- api.ui.collection.keepOrigin(item, 'staged');
288
- }
290
+ api.ui.collection.keepOrigin(item, 'staged');
289
291
  staged += 1;
290
292
  }
291
- if (staged && typeof api.ui.ignoreWatch === 'function') {
292
- api.ui.ignoreWatch();
293
- }
293
+ if (staged) api.ui.ignoreWatch();
294
294
  return staged > 0;
295
295
  };
296
296
 
@@ -309,7 +309,7 @@ const saveFileCompose = (api) => {
309
309
  api.ui.flushReview();
310
310
  closeCompose(api);
311
311
  const doneStatus = changed ? 'staged' : 'saved';
312
- if (!reload || typeof api.ui.reloadAfterChange !== 'function') {
312
+ if (!reload) {
313
313
  if (!api.ui.status) api.ui.status = doneStatus;
314
314
  return { kind: 'saved' };
315
315
  }
@@ -327,8 +327,8 @@ const commitCompose = (api) => {
327
327
  if (api.review.store) commitFeedback(api, text);
328
328
  return true;
329
329
  }
330
- if (state.composeKind === 'todo') {
331
- api.todos.commitTodo(text);
330
+ if (state.composeKind === 'tasks') {
331
+ api.tasks.commitTask(text);
332
332
  return true;
333
333
  }
334
334
  if (state.composeKind === 'code') return commitCode(api, text);
@@ -342,7 +342,7 @@ const saveCompose = (api, extra) => {
342
342
  if (spec && spec.command) return { kind: spec.command };
343
343
  const kind = api.state.composeKind;
344
344
  if (kind === 'file') return saveFileCompose(api);
345
- const item = api.ui.current ? api.ui.current() : null;
345
+ const item = api.ui.current();
346
346
  const shouldStage =
347
347
  kind === 'code' &&
348
348
  writesCodeFile(api, item) &&
@@ -363,14 +363,14 @@ const saveCompose = (api, extra) => {
363
363
  api.ui.flushReview();
364
364
  closeCompose(api);
365
365
  const doneStatus = shouldStage && reload ? 'staged' : 'saved';
366
- if (reload && typeof api.ui.reloadAfterChange === 'function') {
366
+ if (reload) {
367
367
  api.ui.reloadAfterChange(() => {
368
368
  if (!api.ui.status) api.ui.status = doneStatus;
369
369
  });
370
370
  } else {
371
371
  api.ui.status = doneStatus;
372
372
  }
373
- if (opts.editNext === true) api.todos.editNextTodo();
373
+ if (opts.editNext === true) api.tasks.editNextTask();
374
374
  return { kind: 'saved' };
375
375
  };
376
376
 
@@ -402,7 +402,16 @@ const composeView = (api) => {
402
402
  const kind = state.composeKind;
403
403
  const text = state.editor.text;
404
404
  const cursor = state.editor.cursor;
405
- return { kind, text, cursor, commitKind: state.commitKind };
405
+ const scrollCol = state.editor.scrollCol;
406
+ const anchor = state.editor.anchor;
407
+ return {
408
+ kind,
409
+ text,
410
+ cursor,
411
+ scrollCol,
412
+ anchor,
413
+ commitKind: state.commitKind,
414
+ };
406
415
  };
407
416
 
408
417
  const liveCodeText = (api) => {
@@ -426,7 +435,10 @@ const codeOverlayView = (api) => {
426
435
  api.state.composeKind === 'code' || api.state.composeKind === 'file';
427
436
  const editor = api.state.editor;
428
437
  const cursor = editing && editor ? editor.cursor : null;
429
- return { text, keepEmpty: editing, cursor };
438
+ const picked = editing && editor ? editor.scrollCol : 0;
439
+ const scrollCol = picked ?? 0;
440
+ const anchor = editing && editor ? editor.anchor : null;
441
+ return { text, keepEmpty: editing, cursor, scrollCol, anchor };
430
442
  };
431
443
 
432
444
  const idleNoteText = (api) => {
@@ -434,7 +446,7 @@ const idleNoteText = (api) => {
434
446
  if (!notes) return '';
435
447
  if (api.state.composeKind) return '';
436
448
  const item = api.ui.current();
437
- if (!item || isTodoItem(item)) return '';
449
+ if (!item || isTaskItem(item)) return '';
438
450
  const note = notes.feedback.get(itemFeedKey(item));
439
451
  if (!note || !note.text.trim()) return '';
440
452
  return checkLabel(note.text, note.done);
@@ -475,15 +487,15 @@ const moveTemplateLine = (api, delta, shown, width) => {
475
487
  return false;
476
488
  };
477
489
 
478
- const moveNoteLine = (api, delta) => {
490
+ const moveNoteLine = (api, delta, extend) => {
479
491
  const size = viewSize(api);
480
492
  const width = noteInnerWidth(size.width);
481
493
  const shown = api.templates.shownTemplates();
482
494
  const feedback = api.state.composeKind === 'feedback';
483
- if (shown.length && feedback) {
495
+ if (shown.length && feedback && !extend) {
484
496
  if (moveTemplateLine(api, delta, shown, width)) return;
485
497
  }
486
- api.state.editor.moveLine(delta, width);
498
+ api.state.editor.moveLine(delta, width, extend);
487
499
  };
488
500
 
489
501
  const fullCommitEdit = (api) =>
@@ -496,27 +508,26 @@ const editedCommitEntry = (api) => {
496
508
  return list[api.nav.commitCursor] ?? null;
497
509
  };
498
510
 
499
- const moveCommitLine = (api, delta) => {
511
+ const moveCommitLine = (api, delta, extend) => {
500
512
  const size = viewSize(api);
501
513
  const width = commitMessageWidth(editedCommitEntry(api), size.width);
502
- api.state.editor.moveLine(delta, width);
514
+ api.state.editor.moveLine(delta, width, extend);
503
515
  };
504
516
 
505
- const moveComposeLine = (api, delta) => {
517
+ const moveComposeLine = (api, delta, extend) => {
506
518
  const spec = KIND[api.state.composeKind];
507
519
  const move = spec ? spec.move : 'note';
508
- if (move === 'todo') {
520
+ if (move === 'tasks') {
509
521
  const size = viewSize(api);
510
522
  const inner = todoBodyWidth(size.width);
511
- return void api.todos.moveTodoCompose(delta, inner);
523
+ return void api.tasks.moveTaskCompose(delta, inner, extend);
512
524
  }
513
525
  if (move === 'code') {
514
- const size = viewSize(api);
515
- const inner = codeInnerWidth(size.width, api.ui.layout);
516
- return void api.state.editor.moveLine(delta, inner);
526
+ api.state.editor.moveLine(delta, undefined, extend);
527
+ return void syncCodeScroll(api);
517
528
  }
518
- if (fullCommitEdit(api)) return void moveCommitLine(api, delta);
519
- moveNoteLine(api, delta);
529
+ if (fullCommitEdit(api)) return void moveCommitLine(api, delta, extend);
530
+ moveNoteLine(api, delta, extend);
520
531
  };
521
532
 
522
533
  const composePageStep = (api) => {
@@ -535,14 +546,14 @@ const jumpComposeTemplates = (api, toEnd) => {
535
546
  return true;
536
547
  };
537
548
 
538
- const composeHome = (api) => {
539
- if (jumpComposeTemplates(api, false)) return;
540
- api.state.editor.home();
549
+ const composeHome = (api, extend) => {
550
+ if (!extend && jumpComposeTemplates(api, false)) return;
551
+ api.state.editor.home(extend);
541
552
  };
542
553
 
543
- const composeEnd = (api) => {
544
- if (jumpComposeTemplates(api, true)) return;
545
- api.state.editor.end();
554
+ const composeEnd = (api, extend) => {
555
+ if (!extend && jumpComposeTemplates(api, true)) return;
556
+ api.state.editor.end(extend);
546
557
  };
547
558
 
548
559
  const moveComposePage = (api, dir) => {
@@ -556,7 +567,7 @@ const onFeedback = (api) => {
556
567
  return;
557
568
  }
558
569
  const item = api.ui.current();
559
- if (!item || isTodoItem(item)) {
570
+ if (!item || isTaskItem(item)) {
560
571
  api.ui.status = 'not a diff block';
561
572
  return;
562
573
  }
@@ -581,6 +592,7 @@ const onUnitEdit = (api) => {
581
592
  openCompose(api, 'file', at.text);
582
593
  api.state.fileEditRows = at.rows ?? null;
583
594
  api.state.editor.cursor = Math.min(at.cursor, at.text.length);
595
+ syncCodeScroll(api);
584
596
  followFileEdit(api);
585
597
  };
586
598
 
@@ -602,11 +614,11 @@ const onCode = (api) => {
602
614
  : notes.code.get(itemFeedKey(item));
603
615
  const text = prev ? prev.text : original;
604
616
  openCompose(api, 'code', text);
617
+ syncCodeScroll(api);
605
618
  };
606
619
 
607
620
  const insertNewline = (api) => {
608
621
  api.state.editor.insert('\n');
609
- resetBlink(api);
610
622
  followFileEdit(api);
611
623
  return null;
612
624
  };
@@ -619,6 +631,32 @@ const onEmptyLastLine = (editor) => {
619
631
  return text.slice(lastBreak + 1).trim() === '';
620
632
  };
621
633
 
634
+ const filledLines = (text) => {
635
+ let count = 0;
636
+ for (const line of `${text ?? ''}`.split('\n')) {
637
+ if (line.trim() !== '') count += 1;
638
+ }
639
+ return count;
640
+ };
641
+
642
+ const blankTail = (text) => {
643
+ const lines = `${text ?? ''}`.split('\n');
644
+ let count = 0;
645
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
646
+ if (lines[i].trim() !== '') break;
647
+ count += 1;
648
+ }
649
+ return count;
650
+ };
651
+
652
+ const enterFullCommit = (api) => {
653
+ const editor = api.state.editor;
654
+ const text = editor ? editor.text : '';
655
+ const shaped = filledLines(text) >= 2 || blankTail(text) >= 2;
656
+ if (onEmptyLastLine(editor) && shaped) return saveCompose(api);
657
+ return insertNewline(api);
658
+ };
659
+
622
660
  const enterCompose = (api) => {
623
661
  const { state } = api;
624
662
  if (state.templateFocus && state.composeKind === 'feedback') {
@@ -626,10 +664,7 @@ const enterCompose = (api) => {
626
664
  }
627
665
  const spec = KIND[state.composeKind];
628
666
  if (spec && spec.enter === 'newline') return insertNewline(api);
629
- if (fullCommitEdit(api)) {
630
- if (onEmptyLastLine(state.editor)) return saveCompose(api);
631
- return insertNewline(api);
632
- }
667
+ if (fullCommitEdit(api)) return enterFullCommit(api);
633
668
  if (spec && spec.enter === 'edit-next') {
634
669
  return saveCompose(api, { editNext: true });
635
670
  }
@@ -646,35 +681,72 @@ const leaveCompose = (api, key) => {
646
681
  return saveCompose(api);
647
682
  };
648
683
 
684
+ const copyCompose = (api) => {
685
+ const text = api.state.editor.selectedText();
686
+ if (!text) return;
687
+ const ok = clipboard.copyText(text, api.ui.stdout);
688
+ api.ui.status = ok ? 'copied' : 'copy failed';
689
+ };
690
+
691
+ const cutCompose = (api) => {
692
+ if (!api.state.editor.hasSelect()) return;
693
+ copyCompose(api);
694
+ api.state.editor.removeSpan();
695
+ };
696
+
697
+ const pasteCompose = (api) => {
698
+ const text = clipboard.pasteText();
699
+ if (!text) return;
700
+ api.state.editor.insert(text);
701
+ };
702
+
703
+ const COMPOSE_CLIP = ['ctrl-x', 'ctrl-v'];
704
+
649
705
  const COMPOSE_SPECIAL = {
650
706
  backspace: (api) => api.state.editor.backspace(),
651
707
  delete: (api) => api.state.editor.delete(),
652
708
  left: (api) => api.state.editor.move(-1),
653
709
  right: (api) => api.state.editor.move(1),
710
+ 'shift-left': (api) => api.state.editor.move(-1, true),
711
+ 'shift-right': (api) => api.state.editor.move(1, true),
654
712
  'ctrl-left': (api) => api.state.editor.moveWord(-1),
655
713
  'ctrl-right': (api) => api.state.editor.moveWord(1),
714
+ 'ctrl-shift-left': (api) => api.state.editor.moveWord(-1, true),
715
+ 'ctrl-shift-right': (api) => api.state.editor.moveWord(1, true),
656
716
  up: (api) => moveComposeLine(api, -1),
657
717
  down: (api) => moveComposeLine(api, 1),
718
+ 'shift-up': (api) => moveComposeLine(api, -1, true),
719
+ 'shift-down': (api) => moveComposeLine(api, 1, true),
658
720
  home: (api) => composeHome(api),
659
721
  end: (api) => composeEnd(api),
722
+ 'shift-home': (api) => composeHome(api, true),
723
+ 'shift-end': (api) => composeEnd(api, true),
660
724
  pageUp: (api) => moveComposePage(api, -1),
661
725
  pageDown: (api) => moveComposePage(api, 1),
662
726
  'ctrl-a': (api) => api.state.editor.home(),
663
727
  'ctrl-e': (api) => api.state.editor.end(),
728
+ 'ctrl-c': (api) => copyCompose(api),
729
+ 'ctrl-x': (api) => cutCompose(api),
730
+ 'ctrl-v': (api) => pasteCompose(api),
664
731
  'ctrl-z': (api) => api.state.editor.undoEdit(),
665
732
  'ctrl-y': (api) => api.state.editor.redoEdit(),
666
733
  tab: (api) => composeTab(api),
667
734
  };
668
735
 
669
736
  const handleKey = (api, key) => {
670
- if (key === 'enter') return enterCompose(api);
737
+ if (key === 'enter') {
738
+ const result = enterCompose(api);
739
+ syncCodeScroll(api);
740
+ return result;
741
+ }
671
742
  if (key === 'escape' || key === 'ctrl-s') return leaveCompose(api, key);
672
743
  const special = COMPOSE_SPECIAL[key];
673
744
  if (special) {
674
745
  const result = special(api);
675
- if (COMPOSE_EDITS.includes(key)) api.templates.clearTemplatePick();
676
- resetBlink(api);
746
+ const edits = COMPOSE_EDITS.includes(key) || COMPOSE_CLIP.includes(key);
747
+ if (edits) api.templates.clearTemplatePick();
677
748
  followFileEdit(api);
749
+ syncCodeScroll(api);
678
750
  return result ?? null;
679
751
  }
680
752
  if (!key.length) return null;
@@ -682,8 +754,8 @@ const handleKey = (api, key) => {
682
754
  if (key.charCodeAt(0) >= 32) {
683
755
  api.templates.clearTemplatePick();
684
756
  api.state.editor.insert(key);
685
- resetBlink(api);
686
757
  followFileEdit(api);
758
+ syncCodeScroll(api);
687
759
  }
688
760
  return null;
689
761
  };
@@ -694,8 +766,10 @@ const applyDiskText = (api, text) => {
694
766
  const cursor = api.state.editor.cursor;
695
767
  api.state.editor.text = text;
696
768
  api.state.editor.cursor = Math.min(cursor, text.length);
769
+ api.state.editor.anchor = null;
697
770
  api.state.composeBaseline = text;
698
771
  api.state.fileEditRows = unit.linesFor(api.ui, api.nav.reviewPath, null);
772
+ syncCodeScroll(api);
699
773
  followFileEdit(api);
700
774
  };
701
775
 
@@ -709,8 +783,6 @@ const composerMethods = (api) => ({
709
783
  composeView: () => composeView(api),
710
784
  codeOverlayView: () => codeOverlayView(api),
711
785
  idleNoteText: () => idleNoteText(api),
712
- resetBlink: () => resetBlink(api),
713
- tickBlink: () => tickBlink(api),
714
786
  onFeedback: () => onFeedback(api),
715
787
  onCode: () => onCode(api),
716
788
  handleKey: (key) => handleKey(api, key),
@@ -722,28 +794,25 @@ const composerMethods = (api) => ({
722
794
  applyDiskText: (text) => applyDiskText(api, text),
723
795
  });
724
796
 
725
- const createComposer = ({ nav, review, ui, restartBlink }) => {
797
+ const createComposer = ({ nav, review, ui }) => {
726
798
  const state = initialState();
727
- const api = { nav, review, ui, restartBlink, state };
799
+ const api = { nav, review, ui, state };
728
800
  api.open = (kind, text, todoId) => openCompose(api, kind, text, todoId);
729
801
  api.close = () => closeCompose(api);
730
802
  api.commit = () => commitCompose(api);
731
803
  api.save = (opts) => saveCompose(api, opts);
732
- api.resetBlink = () => resetBlink(api);
733
804
  api.templates = createTemplatePick(api);
734
- api.todos = createTodoCompose(api);
805
+ api.tasks = createTasksCompose(api);
735
806
  const composer = {
736
807
  state,
737
808
  ...composerMethods(api),
738
- ...api.todos,
809
+ ...api.tasks,
739
810
  ...api.templates,
740
811
  };
741
812
  return composer;
742
813
  };
743
814
 
744
815
  module.exports = {
745
- COMPOSE_EDITS,
746
816
  COMPOSE_LEAVE,
747
- KIND,
748
817
  createComposer,
749
818
  };