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.
- package/CHANGELOG.md +27 -1
- package/README.md +8 -4
- package/bin/reslop.js +4 -2
- package/lib/api.js +2 -1
- package/lib/capabilities.js +0 -4
- package/lib/cli.js +19 -10
- package/lib/clipboard.js +45 -4
- package/lib/deps-diff.js +61 -118
- package/lib/deps.js +166 -235
- package/lib/detect.js +4 -2
- package/lib/diff/align.js +2 -1
- package/lib/diff/diff.js +6 -3
- package/lib/diff/display.js +2 -1
- package/lib/diff/inline.js +68 -2
- package/lib/diff/patch.js +2 -1
- package/lib/diff/unit.js +15 -30
- package/lib/editor.js +91 -13
- package/lib/files.js +54 -27
- package/lib/git-branches.js +107 -154
- package/lib/git-deps.js +78 -155
- package/lib/git-worktree.js +508 -0
- package/lib/git.js +23 -580
- package/lib/github.js +9 -8
- package/lib/gitlab.js +5 -4
- package/lib/highlight/bash.js +2 -1
- package/lib/highlight/core.js +11 -0
- package/lib/highlight/css.js +2 -1
- package/lib/highlight/csv.js +2 -1
- package/lib/highlight/dot.js +3 -12
- package/lib/highlight/html.js +2 -1
- package/lib/highlight/json.js +2 -1
- package/lib/highlight/log.js +5 -11
- package/lib/highlight/txt.js +3 -12
- package/lib/highlight.js +2 -1
- package/lib/keys.js +22 -12
- package/lib/manifest.js +4 -6
- package/lib/npm-commands.js +82 -24
- package/lib/remote.js +21 -20
- package/lib/render/branches.js +9 -27
- package/lib/render/chrome.js +109 -35
- package/lib/render/commits.js +216 -216
- package/lib/render/diff.js +160 -47
- package/lib/render/files.js +9 -26
- package/lib/render/frame.js +97 -118
- package/lib/render/notes.js +74 -26
- package/lib/render/npm.js +104 -30
- package/lib/render/present.js +2 -1
- package/lib/render/primitives.js +128 -52
- package/lib/render/render.js +2 -1
- package/lib/render/unit.js +17 -11
- package/lib/report-model.js +6 -4
- package/lib/report-parse.js +8 -14
- package/lib/report-render.js +38 -21
- package/lib/report-run.js +12 -11
- package/lib/report-test.js +45 -51
- package/lib/report-tools.js +44 -25
- package/lib/review-merge.js +102 -131
- package/lib/review.js +69 -78
- package/lib/select.js +52 -0
- package/lib/session/actions.js +72 -87
- package/lib/session/branches.js +18 -52
- package/lib/session/change-actions.js +175 -147
- package/lib/session/commit.js +65 -61
- package/lib/session/compose.js +152 -83
- package/lib/session/dispatch.js +53 -46
- package/lib/session/edit-click.js +57 -0
- package/lib/session/files-pane.js +30 -51
- package/lib/session/items.js +73 -90
- package/lib/session/lifecycle.js +37 -98
- package/lib/session/load.js +52 -57
- package/lib/session/navigation.js +105 -95
- package/lib/session/npm.js +205 -71
- package/lib/session/ops.js +200 -45
- package/lib/session/pointer.js +33 -8
- package/lib/session/progress.js +9 -6
- package/lib/session/review.js +35 -53
- package/lib/session/tasks.js +292 -0
- package/lib/session/terminal.js +17 -19
- package/lib/session/unit.js +3 -4
- package/lib/session/update.js +76 -96
- package/lib/session/view.js +45 -16
- package/lib/session/watch.js +47 -27
- package/lib/session.js +66 -97
- package/lib/source.js +6 -3
- package/lib/update.js +5 -13
- package/lib/utilities.js +25 -3
- package/lib/wrap.js +18 -1
- package/package.json +1 -1
- package/lib/session/todos.js +0 -293
package/lib/session/compose.js
CHANGED
|
@@ -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
|
|
8
|
-
const {
|
|
9
|
-
const
|
|
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,
|
|
12
|
-
const
|
|
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
|
|
18
|
-
const {
|
|
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', '
|
|
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
|
-
|
|
56
|
+
tasks: {
|
|
50
57
|
enter: 'edit-next',
|
|
51
58
|
tab: 'template',
|
|
52
59
|
escape: 'save',
|
|
53
|
-
move: '
|
|
60
|
+
move: 'tasks',
|
|
54
61
|
command: null,
|
|
55
|
-
view: '
|
|
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
|
-
|
|
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
|
|
103
|
-
api.state.blinkOn = true;
|
|
104
|
-
api.restartBlink();
|
|
105
|
-
};
|
|
108
|
+
const viewSize = (api) => api.ui.lastSize ?? api.ui.getSize();
|
|
106
109
|
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
api.
|
|
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.
|
|
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.
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
287
|
-
api.ui.collection.keepOrigin(item, 'staged');
|
|
288
|
-
}
|
|
290
|
+
api.ui.collection.keepOrigin(item, 'staged');
|
|
289
291
|
staged += 1;
|
|
290
292
|
}
|
|
291
|
-
if (staged
|
|
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
|
|
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 === '
|
|
331
|
-
api.
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 ||
|
|
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 === '
|
|
520
|
+
if (move === 'tasks') {
|
|
509
521
|
const size = viewSize(api);
|
|
510
522
|
const inner = todoBodyWidth(size.width);
|
|
511
|
-
return void api.
|
|
523
|
+
return void api.tasks.moveTaskCompose(delta, inner, extend);
|
|
512
524
|
}
|
|
513
525
|
if (move === 'code') {
|
|
514
|
-
|
|
515
|
-
|
|
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 ||
|
|
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')
|
|
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
|
-
|
|
676
|
-
|
|
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
|
|
797
|
+
const createComposer = ({ nav, review, ui }) => {
|
|
726
798
|
const state = initialState();
|
|
727
|
-
const api = { nav, review, ui,
|
|
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.
|
|
805
|
+
api.tasks = createTasksCompose(api);
|
|
735
806
|
const composer = {
|
|
736
807
|
state,
|
|
737
808
|
...composerMethods(api),
|
|
738
|
-
...api.
|
|
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
|
};
|