reslop 0.2.4 → 0.2.6
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 +25 -1
- package/LICENSE +1 -1
- package/README.md +12 -13
- package/bin/reslop.js +3 -8
- package/lib/ansi.js +32 -19
- package/lib/capabilities.js +8 -12
- package/lib/cli.js +40 -74
- package/lib/clipboard.js +48 -18
- package/lib/dashboard/git.js +128 -0
- package/lib/dashboard/model.js +332 -0
- package/lib/dashboard/npm.js +193 -0
- package/lib/dashboard/tree.js +278 -0
- package/lib/deps-diff.js +196 -404
- package/lib/deps.js +240 -399
- package/lib/detect.js +8 -18
- package/lib/diff/align.js +41 -113
- package/lib/diff/diff.js +12 -43
- package/lib/diff/display.js +25 -36
- package/lib/diff/edit.js +6 -20
- package/lib/diff/inline.js +138 -141
- package/lib/diff/items.js +33 -47
- package/lib/diff/parse.js +28 -56
- package/lib/diff/patch.js +12 -33
- package/lib/diff/unit.js +90 -206
- package/lib/editor.js +98 -55
- package/lib/files.js +86 -103
- package/lib/git-branches.js +254 -416
- package/lib/git-deps.js +182 -344
- package/lib/git-reword-todo.js +5 -13
- package/lib/git-worktree.js +141 -212
- package/lib/git.js +21 -49
- package/lib/github.js +67 -181
- package/lib/gitlab.js +40 -90
- package/lib/highlight/bash.js +55 -177
- package/lib/highlight/core.js +157 -132
- package/lib/highlight/css.js +25 -58
- package/lib/highlight/csv.js +19 -37
- package/lib/highlight/dart.js +72 -213
- package/lib/highlight/dot.js +16 -26
- package/lib/highlight/html.js +42 -63
- package/lib/highlight/js.js +177 -506
- package/lib/highlight/json.js +17 -37
- package/lib/highlight/log.js +22 -35
- package/lib/highlight/txt.js +9 -22
- package/lib/highlight.js +17 -46
- package/lib/keys.js +62 -85
- package/lib/manifest.js +67 -130
- package/lib/npm-commands.js +119 -189
- package/lib/remote.js +141 -197
- package/lib/render/branches.js +66 -73
- package/lib/render/chrome.js +264 -323
- package/lib/render/commits.js +187 -500
- package/lib/render/dash-activity.js +212 -0
- package/lib/render/dash-blocks.js +245 -0
- package/lib/render/dash-table.js +290 -0
- package/lib/render/dashboard.js +62 -0
- package/lib/render/diff.js +123 -228
- package/lib/render/files.js +45 -99
- package/lib/render/frame.js +129 -183
- package/lib/render/notes.js +144 -160
- package/lib/render/npm.js +234 -112
- package/lib/render/present.js +4 -11
- package/lib/render/primitives.js +131 -170
- package/lib/render/render.js +5 -14
- package/lib/render/report.js +43 -0
- package/lib/render/tiles.js +260 -0
- package/lib/render/unit.js +15 -28
- package/lib/report-model.js +110 -33
- package/lib/report-parse.js +56 -38
- package/lib/report-render.js +102 -59
- package/lib/report-run.js +87 -78
- package/lib/report-test.js +256 -141
- package/lib/report-tools.js +96 -161
- package/lib/review-merge.js +114 -152
- package/lib/review.js +115 -189
- package/lib/runs.js +225 -0
- package/lib/select.js +89 -75
- package/lib/session/accessors.js +14 -21
- package/lib/session/actions.js +82 -63
- package/lib/session/branches.js +193 -247
- package/lib/session/change-actions.js +171 -214
- package/lib/session/commit.js +280 -342
- package/lib/session/compose.js +559 -674
- package/lib/session/dashboard.js +385 -0
- package/lib/session/dispatch.js +164 -157
- package/lib/session/edit-click.js +57 -0
- package/lib/session/files-pane.js +85 -53
- package/lib/session/items.js +73 -104
- package/lib/session/lifecycle.js +403 -541
- package/lib/session/load.js +48 -68
- package/lib/session/navigation.js +96 -87
- package/lib/session/npm.js +409 -345
- package/lib/session/ops.js +196 -63
- package/lib/session/pointer.js +23 -15
- package/lib/session/progress.js +33 -21
- package/lib/session/review.js +33 -54
- package/lib/session/tasks.js +333 -0
- package/lib/session/templates.js +51 -59
- package/lib/session/terminal.js +85 -136
- package/lib/session/unit.js +3 -4
- package/lib/session/update.js +76 -97
- package/lib/session/view.js +52 -83
- package/lib/session/watch.js +208 -204
- package/lib/session.js +112 -237
- package/lib/update.js +45 -116
- package/lib/utilities.js +67 -89
- package/lib/wrap.js +33 -44
- package/package.json +1 -1
- package/lib/session/todos.js +0 -293
package/lib/session/compose.js
CHANGED
|
@@ -11,7 +11,7 @@ const { listedCommits } = commit;
|
|
|
11
11
|
const editor = require('../editor.js');
|
|
12
12
|
const { Editor } = editor;
|
|
13
13
|
const files = require('../files.js');
|
|
14
|
-
const { itemPath,
|
|
14
|
+
const { itemPath, isTaskItem, listPath, isReadOnlyOrigin } = files;
|
|
15
15
|
const diff = require('../diff/diff.js');
|
|
16
16
|
const { blockAddText } = diff;
|
|
17
17
|
const items = require('./items.js');
|
|
@@ -19,163 +19,28 @@ const { itemFeedKey, canEditCode } = items;
|
|
|
19
19
|
const review = require('../review.js');
|
|
20
20
|
const { setFeedback, setCode, checkLabel, rememberTemplate } = review;
|
|
21
21
|
const templates = require('./templates.js');
|
|
22
|
-
const {
|
|
23
|
-
const
|
|
24
|
-
const {
|
|
22
|
+
const { TemplatePick } = templates;
|
|
23
|
+
const tasks = require('./tasks.js');
|
|
24
|
+
const { TaskPage } = tasks;
|
|
25
25
|
const unit = require('./unit.js');
|
|
26
|
+
const clipboard = require('../clipboard.js');
|
|
26
27
|
|
|
27
28
|
const COMPOSE_EDITS = ['backspace', 'delete', 'ctrl-z', 'ctrl-y'];
|
|
28
|
-
const
|
|
29
|
+
const COMPOSE_CLIP = ['ctrl-x', 'ctrl-v'];
|
|
30
|
+
const COMPOSE_LEAVE = ['next', 'prev', 'tasks'];
|
|
29
31
|
|
|
30
32
|
const KIND = {
|
|
31
|
-
feedback: {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
code: {
|
|
40
|
-
enter: 'newline',
|
|
41
|
-
tab: 'insert-tab',
|
|
42
|
-
escape: 'save',
|
|
43
|
-
move: 'code',
|
|
44
|
-
command: null,
|
|
45
|
-
view: 'code',
|
|
46
|
-
},
|
|
47
|
-
file: {
|
|
48
|
-
enter: 'newline',
|
|
49
|
-
tab: 'insert-tab',
|
|
50
|
-
escape: 'save',
|
|
51
|
-
move: 'code',
|
|
52
|
-
command: null,
|
|
53
|
-
view: 'code',
|
|
54
|
-
},
|
|
55
|
-
todo: {
|
|
56
|
-
enter: 'edit-next',
|
|
57
|
-
tab: 'template',
|
|
58
|
-
escape: 'save',
|
|
59
|
-
move: 'todo',
|
|
60
|
-
command: null,
|
|
61
|
-
view: 'todo',
|
|
62
|
-
},
|
|
63
|
-
commit: {
|
|
64
|
-
enter: 'save',
|
|
65
|
-
tab: 'template',
|
|
66
|
-
escape: 'cancel',
|
|
67
|
-
move: 'note',
|
|
68
|
-
command: 'commit',
|
|
69
|
-
view: 'note',
|
|
70
|
-
},
|
|
71
|
-
branch: {
|
|
72
|
-
enter: 'save',
|
|
73
|
-
tab: 'template',
|
|
74
|
-
escape: 'cancel',
|
|
75
|
-
move: 'note',
|
|
76
|
-
command: 'branch',
|
|
77
|
-
view: 'note',
|
|
78
|
-
},
|
|
79
|
-
npm: {
|
|
80
|
-
enter: 'save',
|
|
81
|
-
tab: 'insert-tab',
|
|
82
|
-
escape: 'cancel',
|
|
83
|
-
move: 'note',
|
|
84
|
-
command: 'npm',
|
|
85
|
-
view: 'note',
|
|
86
|
-
},
|
|
33
|
+
feedback: { enter: 'save', tab: 'template', escape: 'save', move: 'note' },
|
|
34
|
+
code: { enter: 'newline', tab: 'insert-tab', escape: 'save', move: 'code' },
|
|
35
|
+
file: { enter: 'newline', tab: 'insert-tab', escape: 'save', move: 'code' },
|
|
36
|
+
tasks: { enter: 'edit-next', tab: 'template', escape: 'save', move: 'tasks' },
|
|
37
|
+
commit: { enter: 'save', tab: 'template', escape: 'cancel', move: 'note' },
|
|
38
|
+
branch: { enter: 'save', tab: 'template', escape: 'cancel', move: 'note' },
|
|
39
|
+
npm: { enter: 'save', tab: 'insert-tab', escape: 'cancel', move: 'note' },
|
|
87
40
|
};
|
|
88
41
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
composeKind: null,
|
|
92
|
-
commitKind: null,
|
|
93
|
-
commitBody: '',
|
|
94
|
-
composeTodoId: null,
|
|
95
|
-
composeBaseline: '',
|
|
96
|
-
templateIndex: -1,
|
|
97
|
-
templateFocus: false,
|
|
98
|
-
blinkOn: true,
|
|
99
|
-
fileDirty: false,
|
|
100
|
-
fileEditRows: null,
|
|
101
|
-
fileOpened: '',
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
const reset = (state) => {
|
|
105
|
-
Object.assign(state, initialState());
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const resetBlink = (api) => {
|
|
109
|
-
api.state.blinkOn = true;
|
|
110
|
-
api.restartBlink();
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const tickBlink = (api) => {
|
|
114
|
-
if (!api.state.composeKind) return;
|
|
115
|
-
api.state.blinkOn = !api.state.blinkOn;
|
|
116
|
-
api.ui.draw();
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const viewSize = (api) => api.ui.lastSize ?? api.ui.getSize();
|
|
120
|
-
|
|
121
|
-
const syncCodeScroll = (api) => {
|
|
122
|
-
const kind = api.state.composeKind;
|
|
123
|
-
if (kind !== 'code' && kind !== 'file') return;
|
|
124
|
-
const ed = api.state.editor;
|
|
125
|
-
if (!ed) return;
|
|
126
|
-
const size = viewSize(api);
|
|
127
|
-
const inner = codeInnerWidth(size.width, api.ui.layout);
|
|
128
|
-
ed.reveal(inner);
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const followFileEdit = (api) => {
|
|
132
|
-
if (api.state.composeKind !== 'file') return;
|
|
133
|
-
const text = api.state.editor ? api.state.editor.text : null;
|
|
134
|
-
const rows = unit.linesFor(
|
|
135
|
-
api.ui,
|
|
136
|
-
api.nav.reviewPath,
|
|
137
|
-
text,
|
|
138
|
-
api.state.fileEditRows,
|
|
139
|
-
);
|
|
140
|
-
unit.followFileCursor(api.nav, api.state.editor, api.ui.lastFrame, rows);
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const afterClose = (api) => {
|
|
144
|
-
api.ui.clampIndex();
|
|
145
|
-
api.ui.syncReviewPath();
|
|
146
|
-
api.ui.syncFileCursor();
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const openCompose = (api, kind, text, todoId) => {
|
|
150
|
-
const { state, ui, nav } = api;
|
|
151
|
-
ui.mode = 'compose';
|
|
152
|
-
state.composeKind = kind;
|
|
153
|
-
state.composeTodoId = todoId ?? null;
|
|
154
|
-
state.composeBaseline = text;
|
|
155
|
-
state.fileOpened = kind === 'file' ? text : '';
|
|
156
|
-
state.editor = new Editor(text);
|
|
157
|
-
state.templateIndex = -1;
|
|
158
|
-
state.templateFocus = false;
|
|
159
|
-
ui.status = '';
|
|
160
|
-
nav.clearSelection();
|
|
161
|
-
resetBlink(api);
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const closeCompose = (api) => {
|
|
165
|
-
const { state, ui } = api;
|
|
166
|
-
ui.mode = 'review';
|
|
167
|
-
state.editor = null;
|
|
168
|
-
state.composeKind = null;
|
|
169
|
-
state.commitKind = null;
|
|
170
|
-
state.commitBody = '';
|
|
171
|
-
state.composeTodoId = null;
|
|
172
|
-
state.composeBaseline = '';
|
|
173
|
-
state.fileOpened = '';
|
|
174
|
-
state.templateIndex = -1;
|
|
175
|
-
state.templateFocus = false;
|
|
176
|
-
state.fileEditRows = null;
|
|
177
|
-
afterClose(api);
|
|
178
|
-
};
|
|
42
|
+
const COMMAND_KINDS = ['commit', 'branch', 'npm'];
|
|
43
|
+
const NOTE_VIEWS = ['feedback', 'commit', 'branch', 'npm'];
|
|
179
44
|
|
|
180
45
|
const itemNote = (item, text) => {
|
|
181
46
|
const hunk = item.hunk;
|
|
@@ -190,586 +55,606 @@ const itemNote = (item, text) => {
|
|
|
190
55
|
};
|
|
191
56
|
};
|
|
192
57
|
|
|
193
|
-
const
|
|
194
|
-
if (!
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
if (
|
|
198
|
-
|
|
199
|
-
return !!(repo && typeof repo.edit === 'function');
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const commitFeedback = (api, text) => {
|
|
203
|
-
const item = api.ui.current();
|
|
204
|
-
if (!item || isTodoItem(item)) return;
|
|
205
|
-
const notes = api.review.store;
|
|
206
|
-
setFeedback(notes, itemFeedKey(item), itemNote(item, text));
|
|
58
|
+
const onEmptyLastLine = (editor) => {
|
|
59
|
+
if (!editor) return false;
|
|
60
|
+
const text = editor.text;
|
|
61
|
+
const lastBreak = text.lastIndexOf('\n');
|
|
62
|
+
if (editor.cursor < lastBreak + 1) return false;
|
|
63
|
+
return text.slice(lastBreak + 1).trim() === '';
|
|
207
64
|
};
|
|
208
65
|
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
if (!notes) return;
|
|
212
|
-
const key = itemFeedKey(item);
|
|
213
|
-
if (!notes.code.has(key)) return;
|
|
214
|
-
setCode(notes, key, { ...itemNote(item, text), clear: true });
|
|
215
|
-
};
|
|
66
|
+
const filledLines = (text) =>
|
|
67
|
+
text.split('\n').filter((line) => line.trim() !== '').length;
|
|
216
68
|
|
|
217
|
-
const
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
return void setCode(notes, key, { ...note, clear: true });
|
|
69
|
+
const blankTail = (text) => {
|
|
70
|
+
const lines = text.split('\n');
|
|
71
|
+
let count = 0;
|
|
72
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
73
|
+
if (lines[i].trim() !== '') break;
|
|
74
|
+
count += 1;
|
|
224
75
|
}
|
|
225
|
-
|
|
76
|
+
return count;
|
|
226
77
|
};
|
|
227
78
|
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
79
|
+
const COMPOSE_SPECIAL = {
|
|
80
|
+
backspace: (c) => c.editor.backspace(),
|
|
81
|
+
delete: (c) => c.editor.delete(),
|
|
82
|
+
left: (c) => c.editor.move(-1),
|
|
83
|
+
right: (c) => c.editor.move(1),
|
|
84
|
+
'shift-left': (c) => c.editor.move(-1, true),
|
|
85
|
+
'shift-right': (c) => c.editor.move(1, true),
|
|
86
|
+
'ctrl-left': (c) => c.editor.moveWord(-1),
|
|
87
|
+
'ctrl-right': (c) => c.editor.moveWord(1),
|
|
88
|
+
'ctrl-shift-left': (c) => c.editor.moveWord(-1, true),
|
|
89
|
+
'ctrl-shift-right': (c) => c.editor.moveWord(1, true),
|
|
90
|
+
up: (c) => c.moveLine(-1),
|
|
91
|
+
down: (c) => c.moveLine(1),
|
|
92
|
+
'shift-up': (c) => c.moveLine(-1, true),
|
|
93
|
+
'shift-down': (c) => c.moveLine(1, true),
|
|
94
|
+
home: (c) => c.home(),
|
|
95
|
+
end: (c) => c.end(),
|
|
96
|
+
'shift-home': (c) => c.home(true),
|
|
97
|
+
'shift-end': (c) => c.end(true),
|
|
98
|
+
pageUp: (c) => c.movePage(-1),
|
|
99
|
+
pageDown: (c) => c.movePage(1),
|
|
100
|
+
'ctrl-a': (c) => c.editor.home(),
|
|
101
|
+
'ctrl-e': (c) => c.editor.end(),
|
|
102
|
+
'ctrl-c': (c) => c.copy(),
|
|
103
|
+
'ctrl-x': (c) => c.cut(),
|
|
104
|
+
'ctrl-v': (c) => c.paste(),
|
|
105
|
+
'ctrl-z': (c) => c.editor.undoEdit(),
|
|
106
|
+
'ctrl-y': (c) => c.editor.redoEdit(),
|
|
107
|
+
tab: (c) => c.tab(),
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
class Composer {
|
|
111
|
+
constructor(ui) {
|
|
112
|
+
this.ui = ui;
|
|
113
|
+
this.templates = new TemplatePick(this);
|
|
114
|
+
this.tasks = new TaskPage(this);
|
|
115
|
+
this.reset();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
reset() {
|
|
119
|
+
this.fileDirty = false;
|
|
120
|
+
this.clear();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
clear() {
|
|
124
|
+
this.editor = null;
|
|
125
|
+
this.composeKind = null;
|
|
126
|
+
this.commitKind = null;
|
|
127
|
+
this.commitBody = '';
|
|
128
|
+
this.composeTaskId = null;
|
|
129
|
+
this.composeBaseline = '';
|
|
130
|
+
this.templateIndex = -1;
|
|
131
|
+
this.templateFocus = false;
|
|
132
|
+
this.fileEditRows = null;
|
|
133
|
+
this.fileOpened = '';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
get spec() {
|
|
137
|
+
return KIND[this.composeKind] ?? null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
get notes() {
|
|
141
|
+
return this.ui.review.store;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
viewSize() {
|
|
145
|
+
return this.ui.lastSize ?? this.ui.getSize();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
canChange() {
|
|
149
|
+
const caps = this.ui.capabilities;
|
|
150
|
+
return Boolean(caps && caps.changes === true);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
syncCodeScroll() {
|
|
154
|
+
const kind = this.composeKind;
|
|
155
|
+
if (kind !== 'code' && kind !== 'file') return;
|
|
156
|
+
if (!this.editor) return;
|
|
157
|
+
const width = this.viewSize().width;
|
|
158
|
+
this.editor.reveal(codeInnerWidth(width, this.ui.layout));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
followFileEdit() {
|
|
162
|
+
if (this.composeKind !== 'file') return;
|
|
163
|
+
const ui = this.ui;
|
|
164
|
+
const text = this.editor ? this.editor.text : null;
|
|
165
|
+
const rel = ui.nav.reviewPath;
|
|
166
|
+
const rows = unit.linesFor(ui, rel, text, this.fileEditRows);
|
|
167
|
+
unit.followFileCursor(ui.nav, this.editor, ui.lastFrame, rows);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
openCompose(kind, text, taskId = null) {
|
|
171
|
+
this.ui.mode = 'compose';
|
|
172
|
+
this.composeKind = kind;
|
|
173
|
+
this.composeTaskId = taskId;
|
|
174
|
+
this.composeBaseline = text;
|
|
175
|
+
this.fileOpened = kind === 'file' ? text : '';
|
|
176
|
+
this.editor = new Editor(text);
|
|
177
|
+
this.templateIndex = -1;
|
|
178
|
+
this.templateFocus = false;
|
|
179
|
+
this.ui.status = '';
|
|
180
|
+
this.ui.nav.clearSelection();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
closeCompose() {
|
|
184
|
+
const ui = this.ui;
|
|
185
|
+
ui.mode = 'review';
|
|
186
|
+
this.clear();
|
|
187
|
+
ui.clampIndex();
|
|
188
|
+
ui.syncReviewPath();
|
|
189
|
+
ui.syncFileCursor();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
liveFileText() {
|
|
193
|
+
if (this.composeKind !== 'file' || !this.editor) return null;
|
|
194
|
+
return this.editor.text;
|
|
233
195
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return
|
|
196
|
+
|
|
197
|
+
writesCodeFile(item) {
|
|
198
|
+
if (!canEditCode(item) || !this.canChange()) return false;
|
|
199
|
+
if (isReadOnlyOrigin(item.origin)) return false;
|
|
200
|
+
return Boolean(this.ui.repo.edit);
|
|
239
201
|
}
|
|
240
|
-
dropCodeNote(api, item, text);
|
|
241
|
-
api.state.fileDirty = true;
|
|
242
|
-
return true;
|
|
243
|
-
};
|
|
244
202
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
return true;
|
|
251
|
-
};
|
|
203
|
+
commitFeedback(text) {
|
|
204
|
+
const item = this.ui.current();
|
|
205
|
+
if (!item || isTaskItem(item)) return;
|
|
206
|
+
setFeedback(this.notes, itemFeedKey(item), itemNote(item, text));
|
|
207
|
+
}
|
|
252
208
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
api.ui.status = 'read only';
|
|
260
|
-
return false;
|
|
209
|
+
dropCodeNote(item, text) {
|
|
210
|
+
const notes = this.notes;
|
|
211
|
+
if (!notes) return;
|
|
212
|
+
const key = itemFeedKey(item);
|
|
213
|
+
if (!notes.code.has(key)) return;
|
|
214
|
+
setCode(notes, key, { ...itemNote(item, text), clear: true });
|
|
261
215
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return
|
|
216
|
+
|
|
217
|
+
commitCode(text) {
|
|
218
|
+
const ui = this.ui;
|
|
219
|
+
const item = ui.current();
|
|
220
|
+
if (!canEditCode(item)) return true;
|
|
221
|
+
const original = blockAddText(item.hunk, item.blockId);
|
|
222
|
+
if (!this.writesCodeFile(item)) {
|
|
223
|
+
const note = itemNote(item, text);
|
|
224
|
+
const clear = text === original;
|
|
225
|
+
setCode(this.notes, itemFeedKey(item), clear ? { ...note, clear } : note);
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
if (text !== original) {
|
|
229
|
+
try {
|
|
230
|
+
ui.repo.edit(ui.top, item, text);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
ui.status = error.message;
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
this.fileDirty = true;
|
|
236
|
+
}
|
|
237
|
+
this.dropCodeNote(item, text);
|
|
238
|
+
return true;
|
|
267
239
|
}
|
|
268
|
-
if (typeof api.ui.ignoreWatch === 'function') api.ui.ignoreWatch();
|
|
269
|
-
api.state.composeBaseline = text;
|
|
270
|
-
api.state.fileDirty = true;
|
|
271
|
-
return true;
|
|
272
|
-
};
|
|
273
240
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
241
|
+
saveUnitFile(text) {
|
|
242
|
+
const ui = this.ui;
|
|
243
|
+
const rel = ui.nav.reviewPath;
|
|
244
|
+
if (!rel || !ui.repo.writeFile) return true;
|
|
245
|
+
if (!this.canChange()) {
|
|
246
|
+
ui.status = 'read only';
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
281
249
|
try {
|
|
282
|
-
repo.
|
|
250
|
+
ui.repo.writeFile(ui.top, rel, text);
|
|
283
251
|
} catch (error) {
|
|
284
|
-
|
|
252
|
+
ui.status = error.message;
|
|
285
253
|
return false;
|
|
286
254
|
}
|
|
287
|
-
|
|
255
|
+
ui.ignoreWatch();
|
|
256
|
+
this.composeBaseline = text;
|
|
257
|
+
this.fileDirty = true;
|
|
288
258
|
return true;
|
|
289
259
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (
|
|
295
|
-
if (
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
260
|
+
|
|
261
|
+
stageEditedFile(rel) {
|
|
262
|
+
const ui = this.ui;
|
|
263
|
+
const repo = ui.repo;
|
|
264
|
+
if (!rel || !this.canChange()) return false;
|
|
265
|
+
if (repo.stagePath) {
|
|
266
|
+
try {
|
|
267
|
+
repo.stagePath(ui.top, rel);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
ui.status = error.message;
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
ui.ignoreWatch();
|
|
273
|
+
return true;
|
|
301
274
|
}
|
|
302
|
-
if (
|
|
303
|
-
|
|
275
|
+
if (!repo.add) return false;
|
|
276
|
+
let staged = 0;
|
|
277
|
+
for (const item of ui.items) {
|
|
278
|
+
if (listPath(item) !== rel) continue;
|
|
279
|
+
if (isReadOnlyOrigin(item.origin) || item.origin === 'staged') continue;
|
|
280
|
+
try {
|
|
281
|
+
repo.add(ui.top, item);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
ui.status = error.message;
|
|
284
|
+
return staged > 0;
|
|
285
|
+
}
|
|
286
|
+
ui.collection.keepOrigin(item, 'staged');
|
|
287
|
+
staged += 1;
|
|
304
288
|
}
|
|
305
|
-
staged
|
|
289
|
+
if (staged) ui.ignoreWatch();
|
|
290
|
+
return staged > 0;
|
|
306
291
|
}
|
|
307
|
-
|
|
308
|
-
|
|
292
|
+
|
|
293
|
+
finishSave(reload, doneStatus) {
|
|
294
|
+
const ui = this.ui;
|
|
295
|
+
ui.flushReview();
|
|
296
|
+
this.closeCompose();
|
|
297
|
+
if (!reload) {
|
|
298
|
+
if (!ui.status) ui.status = doneStatus;
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
ui.reloadAfterChange(() => {
|
|
302
|
+
if (!ui.status) ui.status = doneStatus;
|
|
303
|
+
});
|
|
309
304
|
}
|
|
310
|
-
return staged > 0;
|
|
311
|
-
};
|
|
312
305
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const reload = api.state.fileDirty === true;
|
|
324
|
-
if (changed) stageEditedFile(api, rel);
|
|
325
|
-
api.ui.flushReview();
|
|
326
|
-
closeCompose(api);
|
|
327
|
-
const doneStatus = changed ? 'staged' : 'saved';
|
|
328
|
-
if (!reload || typeof api.ui.reloadAfterChange !== 'function') {
|
|
329
|
-
if (!api.ui.status) api.ui.status = doneStatus;
|
|
306
|
+
saveFileCompose() {
|
|
307
|
+
const rel = this.ui.nav.reviewPath;
|
|
308
|
+
const opened = this.fileOpened;
|
|
309
|
+
const text = this.editor ? this.editor.text : '';
|
|
310
|
+
if (this.editor) this.ui.nav.unitLine = this.editor.linePos().line;
|
|
311
|
+
if (!this.saveUnitFile(text)) return { kind: 'error' };
|
|
312
|
+
const changed = text !== opened;
|
|
313
|
+
const reload = this.fileDirty === true;
|
|
314
|
+
if (changed) this.stageEditedFile(rel);
|
|
315
|
+
this.finishSave(reload, changed ? 'staged' : 'saved');
|
|
330
316
|
return { kind: 'saved' };
|
|
331
317
|
}
|
|
332
|
-
api.ui.reloadAfterChange(() => {
|
|
333
|
-
if (!api.ui.status) api.ui.status = doneStatus;
|
|
334
|
-
});
|
|
335
|
-
return { kind: 'saved' };
|
|
336
|
-
};
|
|
337
318
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
319
|
+
commitCompose() {
|
|
320
|
+
const kind = this.composeKind;
|
|
321
|
+
if (!kind || !this.editor) return true;
|
|
322
|
+
const text = this.editor.text;
|
|
323
|
+
if (kind === 'feedback') {
|
|
324
|
+
if (this.notes) this.commitFeedback(text);
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
if (kind === 'tasks') {
|
|
328
|
+
this.tasks.commitTask(text);
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
if (kind === 'code') return this.commitCode(text);
|
|
332
|
+
if (kind === 'file') return this.saveUnitFile(text);
|
|
348
333
|
return true;
|
|
349
334
|
}
|
|
350
|
-
if (state.composeKind === 'code') return commitCode(api, text);
|
|
351
|
-
if (state.composeKind === 'file') return saveUnitFile(api, text);
|
|
352
|
-
return true;
|
|
353
|
-
};
|
|
354
335
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
api.ui.reloadAfterChange(() => {
|
|
384
|
-
if (!api.ui.status) api.ui.status = doneStatus;
|
|
385
|
-
});
|
|
386
|
-
} else {
|
|
387
|
-
api.ui.status = doneStatus;
|
|
336
|
+
saveCompose(opts = {}) {
|
|
337
|
+
const ui = this.ui;
|
|
338
|
+
const kind = this.composeKind;
|
|
339
|
+
if (COMMAND_KINDS.includes(kind)) return { kind };
|
|
340
|
+
if (kind === 'file') return this.saveFileCompose();
|
|
341
|
+
const item = ui.current();
|
|
342
|
+
const shouldStage =
|
|
343
|
+
kind === 'code' && this.writesCodeFile(item) && item.origin !== 'staged';
|
|
344
|
+
const rel = itemPath(item);
|
|
345
|
+
const baseline = this.composeBaseline;
|
|
346
|
+
if (this.commitCompose() === false) return { kind: 'error' };
|
|
347
|
+
const reload = this.fileDirty === true;
|
|
348
|
+
if (kind === 'feedback' && this.notes) {
|
|
349
|
+
const next = this.editor ? this.editor.text : '';
|
|
350
|
+
rememberTemplate(this.notes, baseline, next);
|
|
351
|
+
}
|
|
352
|
+
const staging = shouldStage && reload;
|
|
353
|
+
if (staging) this.stageEditedFile(rel);
|
|
354
|
+
const doneStatus = staging ? 'staged' : 'saved';
|
|
355
|
+
if (reload) {
|
|
356
|
+
this.finishSave(true, doneStatus);
|
|
357
|
+
} else {
|
|
358
|
+
ui.flushReview();
|
|
359
|
+
this.closeCompose();
|
|
360
|
+
ui.status = doneStatus;
|
|
361
|
+
}
|
|
362
|
+
if (opts.editNext === true) this.tasks.editNextTask();
|
|
363
|
+
return { kind: 'saved' };
|
|
388
364
|
}
|
|
389
|
-
if (opts.editNext === true) api.todos.editNextTodo();
|
|
390
|
-
return { kind: 'saved' };
|
|
391
|
-
};
|
|
392
365
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
366
|
+
autosave() {
|
|
367
|
+
const kind = this.composeKind;
|
|
368
|
+
const text = this.editor ? this.editor.text : '';
|
|
369
|
+
if (kind === 'file' && this.editor) {
|
|
370
|
+
if (text === this.composeBaseline) return;
|
|
371
|
+
return void this.saveUnitFile(text);
|
|
372
|
+
}
|
|
373
|
+
const editingCode = kind === 'code';
|
|
374
|
+
const reviewKind = kind !== null && !COMMAND_KINDS.includes(kind);
|
|
375
|
+
const fileEdit = editingCode && this.writesCodeFile(this.ui.current());
|
|
376
|
+
if (reviewKind && !fileEdit && (editingCode || text.trim())) {
|
|
377
|
+
this.commitCompose();
|
|
378
|
+
}
|
|
379
|
+
if (this.notes && this.notes.dirty) this.ui.flushReview();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
composeView() {
|
|
383
|
+
const { composeKind: kind, editor } = this;
|
|
384
|
+
if (!editor || !NOTE_VIEWS.includes(kind)) return null;
|
|
385
|
+
const { text, cursor, scrollCol, anchor } = editor;
|
|
386
|
+
return {
|
|
387
|
+
kind,
|
|
388
|
+
text,
|
|
389
|
+
cursor,
|
|
390
|
+
scrollCol,
|
|
391
|
+
anchor,
|
|
392
|
+
commitKind: this.commitKind,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
liveCodeText() {
|
|
397
|
+
const kind = this.composeKind;
|
|
398
|
+
if (kind === 'file' && this.editor) return this.editor.text;
|
|
399
|
+
const item = this.ui.current();
|
|
400
|
+
if (!canEditCode(item)) return null;
|
|
401
|
+
if (kind === 'code' && this.editor) return this.editor.text;
|
|
402
|
+
if (this.writesCodeFile(item) || !this.notes) return null;
|
|
403
|
+
const note = this.notes.code.get(itemFeedKey(item));
|
|
404
|
+
return note ? note.text : null;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
codeOverlayView() {
|
|
408
|
+
const text = this.liveCodeText();
|
|
409
|
+
if (text === null) return null;
|
|
410
|
+
const editing = this.composeKind === 'code' || this.composeKind === 'file';
|
|
411
|
+
const editor = editing ? this.editor : null;
|
|
412
|
+
return {
|
|
413
|
+
text,
|
|
414
|
+
keepEmpty: editing,
|
|
415
|
+
cursor: editor?.cursor ?? null,
|
|
416
|
+
scrollCol: editor?.scrollCol ?? 0,
|
|
417
|
+
anchor: editor?.anchor ?? null,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
idleNoteText() {
|
|
422
|
+
const notes = this.notes;
|
|
423
|
+
if (!notes || this.composeKind) return '';
|
|
424
|
+
const item = this.ui.current();
|
|
425
|
+
if (!item || isTaskItem(item)) return '';
|
|
426
|
+
const note = notes.feedback.get(itemFeedKey(item));
|
|
427
|
+
if (!note || !note.text.trim()) return '';
|
|
428
|
+
return checkLabel(note.text, note.done);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
tab() {
|
|
432
|
+
if (this.spec && this.spec.tab === 'insert-tab') {
|
|
433
|
+
this.editor.insert('\t');
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
return this.templates.applyTemplate();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
moveTemplateLine(delta, shown, width) {
|
|
440
|
+
const pick = this.templates;
|
|
441
|
+
if (this.templateFocus) {
|
|
442
|
+
const next = Math.max(
|
|
443
|
+
0,
|
|
444
|
+
Math.min(this.templateIndex + delta, shown.length - 1),
|
|
445
|
+
);
|
|
446
|
+
if (next === this.templateIndex) this.templateFocus = false;
|
|
447
|
+
else pick.focusTemplate(next, false);
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
const pos = cursorInWrap(this.editor.text, this.editor.cursor, width);
|
|
451
|
+
const last = wrapDoc(this.editor.text, width).length - 1;
|
|
452
|
+
if (delta < 0 && pos.row === 0) {
|
|
453
|
+
pick.focusTemplate(shown.length - 1, false);
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
if (delta > 0 && pos.row === last) {
|
|
457
|
+
pick.focusTemplate(0, false);
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
460
462
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
return
|
|
463
|
+
noteLineWidth() {
|
|
464
|
+
const width = this.viewSize().width;
|
|
465
|
+
const fullCommit =
|
|
466
|
+
this.composeKind === 'commit' && this.ui.commits.commitView === 'full';
|
|
467
|
+
if (!fullCommit) return noteInnerWidth(width);
|
|
468
|
+
const inPlace = this.commitKind === 'amend' || this.commitKind === 'reword';
|
|
469
|
+
const list = listedCommits(this.ui.commits.commits);
|
|
470
|
+
const entry = (inPlace && list[this.ui.nav.commitCursor]) || null;
|
|
471
|
+
return commitMessageWidth(entry, width);
|
|
466
472
|
}
|
|
467
|
-
return api.templates.applyTemplate();
|
|
468
|
-
};
|
|
469
473
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if (next >= shown.length) next = shown.length - 1;
|
|
476
|
-
if (next === state.templateIndex) {
|
|
477
|
-
state.templateFocus = false;
|
|
478
|
-
return true;
|
|
474
|
+
moveLine(delta, extend) {
|
|
475
|
+
const move = this.spec ? this.spec.move : 'note';
|
|
476
|
+
if (move === 'tasks') {
|
|
477
|
+
const inner = todoBodyWidth(this.viewSize().width);
|
|
478
|
+
return void this.tasks.moveTaskCompose(delta, inner, extend);
|
|
479
479
|
}
|
|
480
|
-
|
|
481
|
-
|
|
480
|
+
if (move === 'code') {
|
|
481
|
+
this.editor.moveLine(delta, undefined, extend);
|
|
482
|
+
return void this.syncCodeScroll();
|
|
483
|
+
}
|
|
484
|
+
const width = this.noteLineWidth();
|
|
485
|
+
const shown = this.templates.shownTemplates();
|
|
486
|
+
const feedback = this.composeKind === 'feedback';
|
|
487
|
+
if (shown.length && feedback && !extend) {
|
|
488
|
+
if (this.moveTemplateLine(delta, shown, width)) return;
|
|
489
|
+
}
|
|
490
|
+
this.editor.moveLine(delta, width, extend);
|
|
482
491
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
492
|
+
|
|
493
|
+
movePage(dir) {
|
|
494
|
+
const frame = this.ui.lastFrame;
|
|
495
|
+
const fallback = Math.max(1, (this.viewSize().height ?? 24) - 4);
|
|
496
|
+
const step = Math.max(1, frame?.bodyH ?? fallback);
|
|
497
|
+
this.moveLine(step * dir);
|
|
488
498
|
}
|
|
489
|
-
|
|
490
|
-
|
|
499
|
+
|
|
500
|
+
jumpTemplates(toEnd) {
|
|
501
|
+
if (!this.templateFocus) return false;
|
|
502
|
+
const shown = this.templates.shownTemplates();
|
|
503
|
+
if (!shown.length) return false;
|
|
504
|
+
this.templates.focusTemplate(toEnd ? shown.length - 1 : 0, false);
|
|
491
505
|
return true;
|
|
492
506
|
}
|
|
493
|
-
return false;
|
|
494
|
-
};
|
|
495
507
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const shown = api.templates.shownTemplates();
|
|
500
|
-
const feedback = api.state.composeKind === 'feedback';
|
|
501
|
-
if (shown.length && feedback) {
|
|
502
|
-
if (moveTemplateLine(api, delta, shown, width)) return;
|
|
508
|
+
home(extend) {
|
|
509
|
+
if (!extend && this.jumpTemplates(false)) return;
|
|
510
|
+
this.editor.home(extend);
|
|
503
511
|
}
|
|
504
|
-
api.state.editor.moveLine(delta, width);
|
|
505
|
-
};
|
|
506
|
-
|
|
507
|
-
const fullCommitEdit = (api) =>
|
|
508
|
-
api.state.composeKind === 'commit' && api.ui.commits.commitView === 'full';
|
|
509
|
-
|
|
510
|
-
const editedCommitEntry = (api) => {
|
|
511
|
-
const kind = api.state.commitKind;
|
|
512
|
-
if (kind !== 'amend' && kind !== 'reword') return null;
|
|
513
|
-
const list = listedCommits(api.ui.commits.commits);
|
|
514
|
-
return list[api.nav.commitCursor] ?? null;
|
|
515
|
-
};
|
|
516
512
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
api.state.editor.moveLine(delta, width);
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
const moveComposeLine = (api, delta) => {
|
|
524
|
-
const spec = KIND[api.state.composeKind];
|
|
525
|
-
const move = spec ? spec.move : 'note';
|
|
526
|
-
if (move === 'todo') {
|
|
527
|
-
const size = viewSize(api);
|
|
528
|
-
const inner = todoBodyWidth(size.width);
|
|
529
|
-
return void api.todos.moveTodoCompose(delta, inner);
|
|
530
|
-
}
|
|
531
|
-
if (move === 'code') {
|
|
532
|
-
api.state.editor.moveLine(delta);
|
|
533
|
-
return void syncCodeScroll(api);
|
|
513
|
+
end(extend) {
|
|
514
|
+
if (!extend && this.jumpTemplates(true)) return;
|
|
515
|
+
this.editor.end(extend);
|
|
534
516
|
}
|
|
535
|
-
if (fullCommitEdit(api)) return void moveCommitLine(api, delta);
|
|
536
|
-
moveNoteLine(api, delta);
|
|
537
|
-
};
|
|
538
|
-
|
|
539
|
-
const composePageStep = (api) => {
|
|
540
|
-
const frame = api.ui.lastFrame ?? null;
|
|
541
|
-
const size = viewSize(api);
|
|
542
|
-
const fallback = Math.max(1, (size.height ?? 24) - 4);
|
|
543
|
-
return Math.max(1, frame?.bodyH ?? fallback);
|
|
544
|
-
};
|
|
545
|
-
|
|
546
|
-
const jumpComposeTemplates = (api, toEnd) => {
|
|
547
|
-
if (!api.state.templateFocus) return false;
|
|
548
|
-
const shown = api.templates.shownTemplates();
|
|
549
|
-
if (!shown.length) return false;
|
|
550
|
-
const index = toEnd ? shown.length - 1 : 0;
|
|
551
|
-
api.templates.focusTemplate(index, false);
|
|
552
|
-
return true;
|
|
553
|
-
};
|
|
554
|
-
|
|
555
|
-
const composeHome = (api) => {
|
|
556
|
-
if (jumpComposeTemplates(api, false)) return;
|
|
557
|
-
api.state.editor.home();
|
|
558
|
-
};
|
|
559
517
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
const onFeedback = (api) => {
|
|
571
|
-
if (api.nav.pane === 'files') {
|
|
572
|
-
api.ui.status = 'not a diff block';
|
|
573
|
-
return;
|
|
518
|
+
onFeedback() {
|
|
519
|
+
const ui = this.ui;
|
|
520
|
+
const item = ui.current();
|
|
521
|
+
if (ui.nav.pane === 'files' || !item || isTaskItem(item)) {
|
|
522
|
+
ui.status = 'not a diff block';
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const prev = this.notes.feedback.get(itemFeedKey(item));
|
|
526
|
+
this.openCompose('feedback', prev ? prev.text : '');
|
|
574
527
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
528
|
+
|
|
529
|
+
onUnitEdit() {
|
|
530
|
+
const ui = this.ui;
|
|
531
|
+
if (!ui.nav.reviewPath) {
|
|
532
|
+
ui.status = 'not a diff block';
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (!this.canChange()) {
|
|
536
|
+
ui.status = 'read only';
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const at = unit.editCursor(ui);
|
|
540
|
+
this.openCompose('file', at.text);
|
|
541
|
+
this.fileEditRows = at.rows ?? null;
|
|
542
|
+
this.editor.cursor = Math.min(at.cursor, at.text.length);
|
|
543
|
+
this.syncCodeScroll();
|
|
544
|
+
this.followFileEdit();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
onCode() {
|
|
548
|
+
const ui = this.ui;
|
|
549
|
+
if (ui.nav.pane === 'unit') return void this.onUnitEdit();
|
|
550
|
+
const item = ui.current();
|
|
551
|
+
if (ui.nav.pane === 'files' || !canEditCode(item)) {
|
|
552
|
+
ui.status = 'not a diff block';
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const original = blockAddText(item.hunk, item.blockId);
|
|
556
|
+
const writes = this.writesCodeFile(item);
|
|
557
|
+
const prev = writes ? null : this.notes.code.get(itemFeedKey(item));
|
|
558
|
+
this.openCompose('code', prev ? prev.text : original);
|
|
559
|
+
this.syncCodeScroll();
|
|
579
560
|
}
|
|
580
|
-
const notes = api.review.store;
|
|
581
|
-
const prev = notes.feedback.get(itemFeedKey(item));
|
|
582
|
-
const text = prev ? prev.text : '';
|
|
583
|
-
openCompose(api, 'feedback', text);
|
|
584
|
-
};
|
|
585
561
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
592
|
-
const caps = api.ui.capabilities;
|
|
593
|
-
if (!caps || caps.changes !== true) {
|
|
594
|
-
api.ui.status = 'read only';
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
const at = unit.editCursor(api.ui);
|
|
598
|
-
openCompose(api, 'file', at.text);
|
|
599
|
-
api.state.fileEditRows = at.rows ?? null;
|
|
600
|
-
api.state.editor.cursor = Math.min(at.cursor, at.text.length);
|
|
601
|
-
syncCodeScroll(api);
|
|
602
|
-
followFileEdit(api);
|
|
603
|
-
};
|
|
562
|
+
insertNewline() {
|
|
563
|
+
this.editor.insert('\n');
|
|
564
|
+
this.followFileEdit();
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
604
567
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
syncCodeScroll(api);
|
|
624
|
-
};
|
|
568
|
+
enter() {
|
|
569
|
+
const kind = this.composeKind;
|
|
570
|
+
if (this.templateFocus && kind === 'feedback') {
|
|
571
|
+
return this.templates.selectTemplate(this.templateIndex);
|
|
572
|
+
}
|
|
573
|
+
const spec = this.spec;
|
|
574
|
+
if (spec && spec.enter === 'newline') return this.insertNewline();
|
|
575
|
+
if (kind === 'commit' && this.ui.commits.commitView === 'full') {
|
|
576
|
+
const text = this.editor ? this.editor.text : '';
|
|
577
|
+
const shaped = filledLines(text) >= 2 || blankTail(text) >= 2;
|
|
578
|
+
if (onEmptyLastLine(this.editor) && shaped) return this.saveCompose();
|
|
579
|
+
return this.insertNewline();
|
|
580
|
+
}
|
|
581
|
+
if (spec && spec.enter === 'edit-next') {
|
|
582
|
+
return this.saveCompose({ editNext: true });
|
|
583
|
+
}
|
|
584
|
+
return this.saveCompose();
|
|
585
|
+
}
|
|
625
586
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
}
|
|
587
|
+
leave(key) {
|
|
588
|
+
if (this.spec && this.spec.escape === 'cancel' && key === 'escape') {
|
|
589
|
+
this.closeCompose();
|
|
590
|
+
this.ui.status = '';
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
return this.saveCompose();
|
|
594
|
+
}
|
|
632
595
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
};
|
|
596
|
+
copy() {
|
|
597
|
+
const text = this.editor.selectedText();
|
|
598
|
+
if (!text) return;
|
|
599
|
+
const ok = clipboard.copyText(text, this.ui.stdout);
|
|
600
|
+
this.ui.status = ok ? 'copied' : 'copy failed';
|
|
601
|
+
}
|
|
640
602
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
603
|
+
cut() {
|
|
604
|
+
if (!this.editor.hasSelect()) return;
|
|
605
|
+
this.copy();
|
|
606
|
+
this.editor.removeSpan();
|
|
645
607
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
if (
|
|
650
|
-
return insertNewline(api);
|
|
608
|
+
|
|
609
|
+
paste() {
|
|
610
|
+
const text = clipboard.pasteText();
|
|
611
|
+
if (text) this.editor.insert(text);
|
|
651
612
|
}
|
|
652
|
-
|
|
653
|
-
|
|
613
|
+
|
|
614
|
+
afterEdit() {
|
|
615
|
+
this.followFileEdit();
|
|
616
|
+
this.syncCodeScroll();
|
|
654
617
|
}
|
|
655
|
-
return saveCompose(api);
|
|
656
|
-
};
|
|
657
618
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
619
|
+
handleKey(key) {
|
|
620
|
+
if (key === 'enter') {
|
|
621
|
+
const result = this.enter();
|
|
622
|
+
this.syncCodeScroll();
|
|
623
|
+
return result;
|
|
624
|
+
}
|
|
625
|
+
if (key === 'escape' || key === 'ctrl-s') return this.leave(key);
|
|
626
|
+
const special = COMPOSE_SPECIAL[key];
|
|
627
|
+
if (special) {
|
|
628
|
+
const result = special(this);
|
|
629
|
+
const edits = COMPOSE_EDITS.includes(key) || COMPOSE_CLIP.includes(key);
|
|
630
|
+
if (edits) this.templates.clearTemplatePick();
|
|
631
|
+
this.afterEdit();
|
|
632
|
+
return result ?? null;
|
|
633
|
+
}
|
|
634
|
+
if (!key.length || key.startsWith('ctrl-') || key.startsWith('alt-')) {
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
if (key.charCodeAt(0) >= 32) {
|
|
638
|
+
this.templates.clearTemplatePick();
|
|
639
|
+
this.editor.insert(key);
|
|
640
|
+
this.afterEdit();
|
|
641
|
+
}
|
|
663
642
|
return null;
|
|
664
643
|
}
|
|
665
|
-
return saveCompose(api);
|
|
666
|
-
};
|
|
667
644
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
'ctrl-e': (api) => api.state.editor.end(),
|
|
683
|
-
'ctrl-z': (api) => api.state.editor.undoEdit(),
|
|
684
|
-
'ctrl-y': (api) => api.state.editor.redoEdit(),
|
|
685
|
-
tab: (api) => composeTab(api),
|
|
686
|
-
};
|
|
687
|
-
|
|
688
|
-
const handleKey = (api, key) => {
|
|
689
|
-
if (key === 'enter') {
|
|
690
|
-
const result = enterCompose(api);
|
|
691
|
-
syncCodeScroll(api);
|
|
692
|
-
return result;
|
|
693
|
-
}
|
|
694
|
-
if (key === 'escape' || key === 'ctrl-s') return leaveCompose(api, key);
|
|
695
|
-
const special = COMPOSE_SPECIAL[key];
|
|
696
|
-
if (special) {
|
|
697
|
-
const result = special(api);
|
|
698
|
-
if (COMPOSE_EDITS.includes(key)) api.templates.clearTemplatePick();
|
|
699
|
-
resetBlink(api);
|
|
700
|
-
followFileEdit(api);
|
|
701
|
-
syncCodeScroll(api);
|
|
702
|
-
return result ?? null;
|
|
703
|
-
}
|
|
704
|
-
if (!key.length) return null;
|
|
705
|
-
if (key.startsWith('ctrl-') || key.startsWith('alt-')) return null;
|
|
706
|
-
if (key.charCodeAt(0) >= 32) {
|
|
707
|
-
api.templates.clearTemplatePick();
|
|
708
|
-
api.state.editor.insert(key);
|
|
709
|
-
resetBlink(api);
|
|
710
|
-
followFileEdit(api);
|
|
711
|
-
syncCodeScroll(api);
|
|
712
|
-
}
|
|
713
|
-
return null;
|
|
714
|
-
};
|
|
715
|
-
|
|
716
|
-
const applyDiskText = (api, text) => {
|
|
717
|
-
if (api.state.composeKind !== 'file' || !api.state.editor) return;
|
|
718
|
-
if (text === api.state.editor.text) return;
|
|
719
|
-
const cursor = api.state.editor.cursor;
|
|
720
|
-
api.state.editor.text = text;
|
|
721
|
-
api.state.editor.cursor = Math.min(cursor, text.length);
|
|
722
|
-
api.state.composeBaseline = text;
|
|
723
|
-
api.state.fileEditRows = unit.linesFor(api.ui, api.nav.reviewPath, null);
|
|
724
|
-
syncCodeScroll(api);
|
|
725
|
-
followFileEdit(api);
|
|
726
|
-
};
|
|
727
|
-
|
|
728
|
-
const composerMethods = (api) => ({
|
|
729
|
-
reset: () => reset(api.state),
|
|
730
|
-
openCompose: (kind, text, todoId) => openCompose(api, kind, text, todoId),
|
|
731
|
-
closeCompose: () => closeCompose(api),
|
|
732
|
-
commitCompose: () => commitCompose(api),
|
|
733
|
-
saveCompose: (opts) => saveCompose(api, opts),
|
|
734
|
-
autosave: () => autosave(api),
|
|
735
|
-
composeView: () => composeView(api),
|
|
736
|
-
codeOverlayView: () => codeOverlayView(api),
|
|
737
|
-
idleNoteText: () => idleNoteText(api),
|
|
738
|
-
resetBlink: () => resetBlink(api),
|
|
739
|
-
tickBlink: () => tickBlink(api),
|
|
740
|
-
onFeedback: () => onFeedback(api),
|
|
741
|
-
onCode: () => onCode(api),
|
|
742
|
-
handleKey: (key) => handleKey(api, key),
|
|
743
|
-
liveFileText: () => {
|
|
744
|
-
if (api.state.composeKind !== 'file' || !api.state.editor) return null;
|
|
745
|
-
return api.state.editor.text;
|
|
746
|
-
},
|
|
747
|
-
fileEditRows: () => api.state.fileEditRows,
|
|
748
|
-
applyDiskText: (text) => applyDiskText(api, text),
|
|
749
|
-
});
|
|
750
|
-
|
|
751
|
-
const createComposer = ({ nav, review, ui, restartBlink }) => {
|
|
752
|
-
const state = initialState();
|
|
753
|
-
const api = { nav, review, ui, restartBlink, state };
|
|
754
|
-
api.open = (kind, text, todoId) => openCompose(api, kind, text, todoId);
|
|
755
|
-
api.close = () => closeCompose(api);
|
|
756
|
-
api.commit = () => commitCompose(api);
|
|
757
|
-
api.save = (opts) => saveCompose(api, opts);
|
|
758
|
-
api.resetBlink = () => resetBlink(api);
|
|
759
|
-
api.templates = createTemplatePick(api);
|
|
760
|
-
api.todos = createTodoCompose(api);
|
|
761
|
-
const composer = {
|
|
762
|
-
state,
|
|
763
|
-
...composerMethods(api),
|
|
764
|
-
...api.todos,
|
|
765
|
-
...api.templates,
|
|
766
|
-
};
|
|
767
|
-
return composer;
|
|
768
|
-
};
|
|
645
|
+
applyDiskText(text) {
|
|
646
|
+
const editor = this.editor;
|
|
647
|
+
if (this.composeKind !== 'file' || !editor) return;
|
|
648
|
+
if (text === editor.text) return;
|
|
649
|
+
const cursor = editor.cursor;
|
|
650
|
+
editor.text = text;
|
|
651
|
+
editor.cursor = Math.min(cursor, text.length);
|
|
652
|
+
editor.anchor = null;
|
|
653
|
+
this.composeBaseline = text;
|
|
654
|
+
this.fileEditRows = unit.linesFor(this.ui, this.ui.nav.reviewPath, null);
|
|
655
|
+
this.syncCodeScroll();
|
|
656
|
+
this.followFileEdit();
|
|
657
|
+
}
|
|
658
|
+
}
|
|
769
659
|
|
|
770
|
-
module.exports = {
|
|
771
|
-
COMPOSE_EDITS,
|
|
772
|
-
COMPOSE_LEAVE,
|
|
773
|
-
KIND,
|
|
774
|
-
createComposer,
|
|
775
|
-
};
|
|
660
|
+
module.exports = { COMPOSE_LEAVE, Composer };
|