reslop 0.1.8 → 0.2.0
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/README.md +27 -38
- package/bin/reslop.js +2 -2
- package/lib/ansi.js +6 -2
- package/lib/api.js +2 -2
- package/lib/capabilities.js +10 -14
- package/lib/cli.js +28 -47
- package/lib/clipboard.js +1 -1
- package/lib/deps-diff.js +712 -0
- package/lib/deps.js +556 -32
- package/lib/detect.js +1 -7
- package/lib/diff/align.js +7 -10
- package/lib/diff/diff.js +53 -0
- package/lib/diff/display.js +5 -4
- package/lib/diff/inline.js +26 -21
- package/lib/diff/items.js +6 -4
- package/lib/diff/patch.js +1 -3
- package/lib/diff/unit.js +392 -0
- package/lib/files.js +34 -8
- package/lib/{git/branches.js → git-branches.js} +103 -10
- package/lib/{git/dependencies.js → git-deps.js} +111 -38
- package/lib/git.js +592 -65
- package/lib/github.js +28 -197
- package/lib/gitlab.js +24 -195
- package/lib/highlight/bash.js +1 -3
- package/lib/highlight/css.js +1 -3
- package/lib/highlight/js.js +11 -14
- package/lib/highlight.js +6 -9
- package/lib/keys.js +22 -9
- package/lib/manifest.js +330 -0
- package/lib/remote.js +374 -0
- package/lib/render/branches.js +34 -66
- package/lib/render/chrome.js +57 -22
- package/lib/render/commits.js +85 -0
- package/lib/render/diff.js +16 -16
- package/lib/render/files.js +29 -37
- package/lib/render/frame.js +29 -13
- package/lib/render/notes.js +4 -3
- package/lib/render/primitives.js +46 -1
- package/lib/{render.js → render/render.js} +6 -7
- package/lib/render/unit.js +41 -0
- package/lib/review.js +750 -14
- package/lib/select.js +5 -10
- package/lib/session/accessors.js +9 -1
- package/lib/session/actions.js +93 -36
- package/lib/session/branches.js +130 -144
- package/lib/session/change-actions.js +206 -124
- package/lib/session/commit.js +217 -62
- package/lib/session/compose.js +158 -13
- package/lib/session/dispatch.js +109 -55
- package/lib/session/files-pane.js +145 -142
- package/lib/session/items.js +131 -34
- package/lib/session/lifecycle.js +82 -42
- package/lib/session/load.js +19 -33
- package/lib/session/navigation.js +15 -3
- package/lib/session/ops.js +28 -41
- package/lib/session/pointer.js +58 -54
- package/lib/session/review.js +23 -37
- package/lib/session/terminal.js +6 -12
- package/lib/session/todos.js +8 -12
- package/lib/session/unit.js +208 -0
- package/lib/session/update.js +33 -46
- package/lib/session/view.js +82 -11
- package/lib/session/watch.js +11 -19
- package/lib/session.js +29 -226
- package/lib/source.js +3 -3
- package/lib/update.js +40 -75
- package/lib/{git/proc.js → utilities.js} +75 -11
- package/lib/wrap.js +1 -3
- package/package.json +2 -2
- package/lib/deps/changes.js +0 -331
- package/lib/deps/items.js +0 -131
- package/lib/deps/manifest.js +0 -145
- package/lib/deps/merge.js +0 -120
- package/lib/deps/proposals.js +0 -164
- package/lib/deps/reports.js +0 -180
- package/lib/deps/rows.js +0 -177
- package/lib/deps/scan.js +0 -138
- package/lib/deps/usage.js +0 -68
- package/lib/deps/versions.js +0 -119
- package/lib/diff.js +0 -33
- package/lib/git/changes.js +0 -164
- package/lib/git/read.js +0 -253
- package/lib/git/run.js +0 -100
- package/lib/npm/commands.js +0 -100
- package/lib/remote/changes.js +0 -37
- package/lib/remote/headers.js +0 -36
- package/lib/remote/http.js +0 -122
- package/lib/remote/notes.js +0 -94
- package/lib/review/format.js +0 -434
- package/lib/review/storage.js +0 -150
- package/lib/review/store.js +0 -178
- package/lib/review/templates.js +0 -49
- package/lib/session/origins.js +0 -7
- package/lib/sys.js +0 -84
package/lib/session/commit.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { stateView } = require('./accessors.js');
|
|
4
|
+
|
|
5
|
+
const { isReadOnlyOrigin } = require('../files.js');
|
|
4
6
|
const { pickRepoWrite } = require('./ops.js');
|
|
5
7
|
|
|
6
8
|
const COMMIT_STATUS = {
|
|
@@ -9,114 +11,267 @@ const COMMIT_STATUS = {
|
|
|
9
11
|
fixup: 'fixup',
|
|
10
12
|
};
|
|
11
13
|
|
|
12
|
-
const canCommit = (
|
|
13
|
-
const caps =
|
|
14
|
+
const canCommit = (ui) => {
|
|
15
|
+
const caps = ui.capabilities;
|
|
14
16
|
if (!caps.branches) {
|
|
15
|
-
|
|
17
|
+
ui.status = 'read only';
|
|
16
18
|
return false;
|
|
17
19
|
}
|
|
18
|
-
for (const item of
|
|
20
|
+
for (const item of ui.items) {
|
|
19
21
|
if (isReadOnlyOrigin(item.origin)) {
|
|
20
|
-
|
|
22
|
+
ui.status = 'read only';
|
|
21
23
|
return false;
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
26
|
return true;
|
|
25
27
|
};
|
|
26
28
|
|
|
27
|
-
const hasStagedChanges = (
|
|
28
|
-
const repo =
|
|
29
|
-
if (repo.hasStaged) return repo.hasStaged(
|
|
30
|
-
for (const item of
|
|
29
|
+
const hasStagedChanges = (ui) => {
|
|
30
|
+
const repo = ui.repo;
|
|
31
|
+
if (repo.hasStaged) return repo.hasStaged(ui.top);
|
|
32
|
+
for (const item of ui.items) {
|
|
31
33
|
if (item.origin === 'staged') return true;
|
|
32
34
|
}
|
|
33
35
|
return false;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
const selectedCommit = (api) => {
|
|
39
|
+
const cursor = api.ui.nav.commitCursor;
|
|
40
|
+
return api.state.commits[cursor] ?? null;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const clampCommitCursor = (api) => {
|
|
44
|
+
const last = api.state.commits.length - 1;
|
|
45
|
+
const nav = api.ui.nav;
|
|
46
|
+
if (nav.commitCursor > last) nav.commitCursor = Math.max(0, last);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const refreshCommitList = (api) => {
|
|
50
|
+
const repo = api.ui.repo;
|
|
51
|
+
if (!repo.listCommits) return;
|
|
52
|
+
try {
|
|
53
|
+
api.state.commits = repo.listCommits(api.ui.top);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
api.ui.status = error.message;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
clampCommitCursor(api);
|
|
40
59
|
};
|
|
41
60
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
options.setStatus('');
|
|
61
|
+
const refreshAfterWrite = (api, extra = {}) => {
|
|
62
|
+
refreshCommitList(api);
|
|
63
|
+
api.ui.refreshFromRepo({ keepEmpty: true, ...extra });
|
|
46
64
|
};
|
|
47
65
|
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
66
|
+
const showCommits = (api) => {
|
|
67
|
+
if (api.ui.mode === 'compose') return;
|
|
68
|
+
if (!canCommit(api.ui)) return;
|
|
69
|
+
const repo = api.ui.repo;
|
|
70
|
+
if (!repo.listCommits) {
|
|
71
|
+
api.ui.status = 'read only';
|
|
53
72
|
return;
|
|
54
73
|
}
|
|
55
|
-
|
|
74
|
+
try {
|
|
75
|
+
api.state.commits = repo.listCommits(api.ui.top);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
api.ui.status = error.message;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
api.ui.nav.commitCursor = 0;
|
|
81
|
+
api.ui.nav.pane = 'commits';
|
|
82
|
+
api.ui.status = '';
|
|
83
|
+
api.ui.nav.clearSelection();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const onCommitMove = (api, delta) => {
|
|
87
|
+
const commits = api.state.commits;
|
|
88
|
+
if (!commits.length) {
|
|
89
|
+
api.ui.status = 'nothing to review';
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const next = Math.min(
|
|
93
|
+
commits.length - 1,
|
|
94
|
+
Math.max(0, api.ui.nav.commitCursor + delta),
|
|
95
|
+
);
|
|
96
|
+
if (next === api.ui.nav.commitCursor) return;
|
|
97
|
+
api.ui.nav.commitCursor = next;
|
|
98
|
+
api.ui.status = '';
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const startGitCommit = (api, kind) => {
|
|
102
|
+
const ui = api.ui;
|
|
103
|
+
if (kind !== 'amend' && !hasStagedChanges(ui)) {
|
|
104
|
+
ui.mode = 'review';
|
|
105
|
+
ui.composer.state.commitKind = null;
|
|
106
|
+
ui.status = 'nothing to commit';
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
ui.composer.state.commitKind = kind;
|
|
56
110
|
let text = '';
|
|
57
|
-
if (kind === 'fixup')
|
|
58
|
-
|
|
59
|
-
text =
|
|
111
|
+
if (kind === 'fixup') {
|
|
112
|
+
const entry = selectedCommit(api);
|
|
113
|
+
text = entry ? entry.shortSha || entry.sha : 'HEAD';
|
|
60
114
|
}
|
|
61
|
-
|
|
115
|
+
if (kind === 'amend' && ui.repo.lastMessage) {
|
|
116
|
+
text = ui.repo.lastMessage(ui.top) ?? '';
|
|
117
|
+
}
|
|
118
|
+
ui.composer.openCompose('commit', text);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const onCommit = (api) => {
|
|
122
|
+
if (api.ui.nav.pane !== 'commits') return void showCommits(api);
|
|
123
|
+
if (!canCommit(api.ui)) return;
|
|
124
|
+
startGitCommit(api, 'commit');
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const onAmend = (api) => {
|
|
128
|
+
if (api.ui.nav.pane !== 'commits') return;
|
|
129
|
+
if (!canCommit(api.ui)) return;
|
|
130
|
+
startGitCommit(api, 'amend');
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const onFixup = (api) => {
|
|
134
|
+
if (api.ui.nav.pane !== 'commits') return;
|
|
135
|
+
if (!canCommit(api.ui)) return;
|
|
136
|
+
startGitCommit(api, 'fixup');
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const cancelCommit = (api) => {
|
|
140
|
+
api.ui.mode = 'review';
|
|
141
|
+
api.ui.composer.state.commitKind = null;
|
|
142
|
+
api.ui.status = '';
|
|
62
143
|
};
|
|
63
144
|
|
|
64
|
-
const finishAsyncCommit = (
|
|
65
|
-
const
|
|
66
|
-
composer.closeCompose();
|
|
145
|
+
const finishAsyncCommit = (api, kind, message, done, write) => {
|
|
146
|
+
const ui = api.ui;
|
|
147
|
+
ui.composer.closeCompose();
|
|
67
148
|
const run = (top) => write(top, kind, message);
|
|
68
|
-
const after = () => {
|
|
69
|
-
|
|
70
|
-
options.refreshFromRepo({ doneStatus: done });
|
|
71
|
-
};
|
|
72
|
-
return void options.runner.runBusy('committing', done, run, after);
|
|
149
|
+
const after = () => refreshAfterWrite(api, { doneStatus: done });
|
|
150
|
+
return void ui.ops.runBusy('committing', done, run, after);
|
|
73
151
|
};
|
|
74
152
|
|
|
75
|
-
const finishSyncCommit = (
|
|
153
|
+
const finishSyncCommit = (api, kind, message, done) => {
|
|
154
|
+
const ui = api.ui;
|
|
76
155
|
try {
|
|
77
|
-
|
|
156
|
+
ui.repo.commit(ui.top, kind, message);
|
|
78
157
|
} catch (error) {
|
|
79
|
-
|
|
158
|
+
ui.status = error.message;
|
|
80
159
|
return;
|
|
81
160
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (!
|
|
86
|
-
|
|
161
|
+
ui.composer.closeCompose();
|
|
162
|
+
ui.collection.clearDismissed();
|
|
163
|
+
refreshAfterWrite(api);
|
|
164
|
+
if (!ui.done && !ui.pendingExtras) {
|
|
165
|
+
ui.status = done;
|
|
87
166
|
}
|
|
88
167
|
};
|
|
89
168
|
|
|
90
|
-
const finishGitCommit = (
|
|
91
|
-
const
|
|
92
|
-
const
|
|
169
|
+
const finishGitCommit = (api) => {
|
|
170
|
+
const ui = api.ui;
|
|
171
|
+
const kind = ui.composer.state.commitKind;
|
|
172
|
+
const editor = ui.composer.state.editor;
|
|
93
173
|
const message = editor ? editor.text : '';
|
|
94
174
|
if (kind !== 'fixup' && !message.trim()) {
|
|
95
|
-
|
|
175
|
+
ui.status = 'empty commit message';
|
|
96
176
|
return;
|
|
97
177
|
}
|
|
98
|
-
if (kind !== 'amend' && !hasStagedChanges(
|
|
99
|
-
|
|
100
|
-
|
|
178
|
+
if (kind !== 'amend' && !hasStagedChanges(ui)) {
|
|
179
|
+
ui.composer.closeCompose();
|
|
180
|
+
ui.status = 'nothing to commit';
|
|
101
181
|
return;
|
|
102
182
|
}
|
|
103
183
|
const done = COMMIT_STATUS[kind] ?? 'committed';
|
|
104
|
-
const uiOpen =
|
|
105
|
-
const picked = pickRepoWrite(
|
|
184
|
+
const uiOpen = ui.uiOpen;
|
|
185
|
+
const picked = pickRepoWrite(ui.repo, 'commit', uiOpen);
|
|
106
186
|
if (picked.async) {
|
|
107
|
-
finishAsyncCommit(
|
|
187
|
+
return void finishAsyncCommit(api, kind, message, done, picked.write);
|
|
188
|
+
}
|
|
189
|
+
finishSyncCommit(api, kind, message, done);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const onDropCommit = (api) => {
|
|
193
|
+
if (api.ui.nav.pane !== 'commits') return;
|
|
194
|
+
if (!canCommit(api.ui)) return;
|
|
195
|
+
const entry = selectedCommit(api);
|
|
196
|
+
if (!entry) return;
|
|
197
|
+
api.state.dropName = entry.shortSha || entry.sha;
|
|
198
|
+
api.state.dropSha = entry.sha;
|
|
199
|
+
api.ui.mode = 'confirmDrop';
|
|
200
|
+
api.ui.status = '';
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const cancelDropCommit = (api) => {
|
|
204
|
+
api.ui.mode = 'review';
|
|
205
|
+
api.state.dropName = '';
|
|
206
|
+
api.state.dropSha = '';
|
|
207
|
+
api.ui.status = '';
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const dropAsync = (api, sha, done, write) => {
|
|
211
|
+
const run = (top) => write(top, sha);
|
|
212
|
+
const after = () => refreshAfterWrite(api, { doneStatus: done });
|
|
213
|
+
return void api.ui.ops.runBusy('dropping', done, run, after);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const confirmDropCommit = (api) => {
|
|
217
|
+
const sha = api.state.dropSha;
|
|
218
|
+
const name = api.state.dropName;
|
|
219
|
+
api.ui.mode = 'review';
|
|
220
|
+
api.state.dropName = '';
|
|
221
|
+
api.state.dropSha = '';
|
|
222
|
+
if (!sha) return;
|
|
223
|
+
const done = `dropped ${name}`;
|
|
224
|
+
const repo = api.ui.repo;
|
|
225
|
+
if (!repo.dropCommit) {
|
|
226
|
+
api.ui.status = 'read only';
|
|
108
227
|
return;
|
|
109
228
|
}
|
|
110
|
-
|
|
229
|
+
const uiOpen = api.ui.uiOpen;
|
|
230
|
+
const picked = pickRepoWrite(repo, 'dropCommit', uiOpen);
|
|
231
|
+
if (picked.async) {
|
|
232
|
+
return void dropAsync(api, sha, done, picked.write);
|
|
233
|
+
}
|
|
234
|
+
const ok = api.ui.ops.runRepo(done, (top) => {
|
|
235
|
+
repo.dropCommit(top, sha);
|
|
236
|
+
});
|
|
237
|
+
if (!ok) return;
|
|
238
|
+
refreshAfterWrite(api);
|
|
239
|
+
if (!api.ui.status) api.ui.status = done;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const reset = (state) => {
|
|
243
|
+
state.commits = [];
|
|
244
|
+
state.dropName = '';
|
|
245
|
+
state.dropSha = '';
|
|
111
246
|
};
|
|
112
247
|
|
|
113
|
-
const createCommitController = (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
248
|
+
const createCommitController = (ui) => {
|
|
249
|
+
const state = {
|
|
250
|
+
commits: [],
|
|
251
|
+
dropName: '',
|
|
252
|
+
dropSha: '',
|
|
253
|
+
};
|
|
254
|
+
const api = { ui, state };
|
|
255
|
+
return stateView(
|
|
256
|
+
state,
|
|
257
|
+
{
|
|
258
|
+
reset: () => reset(state),
|
|
259
|
+
canCommit: () => canCommit(ui),
|
|
260
|
+
onCommit: () => onCommit(api),
|
|
261
|
+
onAmend: () => onAmend(api),
|
|
262
|
+
onFixup: () => onFixup(api),
|
|
263
|
+
onCommitMove: (delta) => onCommitMove(api, delta),
|
|
264
|
+
cancelCommit: () => cancelCommit(api),
|
|
265
|
+
startGitCommit: (kind) => startGitCommit(api, kind),
|
|
266
|
+
finishGitCommit: () => finishGitCommit(api),
|
|
267
|
+
refresh: () => refreshCommitList(api),
|
|
268
|
+
onDropCommit: () => onDropCommit(api),
|
|
269
|
+
cancelDropCommit: () => cancelDropCommit(api),
|
|
270
|
+
confirmDropCommit: () => confirmDropCommit(api),
|
|
271
|
+
},
|
|
272
|
+
['commits', 'dropName'],
|
|
273
|
+
);
|
|
274
|
+
};
|
|
120
275
|
|
|
121
276
|
module.exports = {
|
|
122
277
|
COMMIT_STATUS,
|
package/lib/session/compose.js
CHANGED
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
const wrap = require('../wrap.js');
|
|
4
4
|
const { cursorInWrap, wrapDoc } = wrap;
|
|
5
|
-
const render = require('../render.js');
|
|
5
|
+
const render = require('../render/render.js');
|
|
6
6
|
const { noteInnerWidth, codeInnerWidth, todoBodyWidth } = render;
|
|
7
7
|
const { Editor } = require('../editor.js');
|
|
8
8
|
const files = require('../files.js');
|
|
9
|
-
const { itemPath, isTodoItem } = files;
|
|
10
|
-
const { blockAddText } = require('../diff.js');
|
|
9
|
+
const { itemPath, isTodoItem, listPath, isReadOnlyOrigin } = files;
|
|
10
|
+
const { blockAddText } = require('../diff/diff.js');
|
|
11
11
|
const items = require('./items.js');
|
|
12
|
-
const { itemFeedKey, canEditCode } = items;
|
|
13
|
-
const { isReadOnlyOrigin } = require('./origins.js');
|
|
12
|
+
const { itemFeedKey, canEditCode, blockLinesKey } = items;
|
|
14
13
|
const review = require('../review.js');
|
|
15
14
|
const { setFeedback, setCode, checkLabel, rememberTemplate } = review;
|
|
16
15
|
const { createTemplatePick } = require('./templates.js');
|
|
17
16
|
const { createTodoCompose } = require('./todos.js');
|
|
17
|
+
const unit = require('./unit.js');
|
|
18
18
|
|
|
19
19
|
const COMPOSE_EDITS = ['backspace', 'delete', 'ctrl-z', 'ctrl-y'];
|
|
20
20
|
const COMPOSE_LEAVE = ['next', 'prev', 'todo'];
|
|
@@ -36,6 +36,14 @@ const KIND = {
|
|
|
36
36
|
command: null,
|
|
37
37
|
view: 'code',
|
|
38
38
|
},
|
|
39
|
+
file: {
|
|
40
|
+
enter: 'newline',
|
|
41
|
+
tab: 'insert-tab',
|
|
42
|
+
escape: 'save',
|
|
43
|
+
move: 'code',
|
|
44
|
+
command: null,
|
|
45
|
+
view: 'code',
|
|
46
|
+
},
|
|
39
47
|
todo: {
|
|
40
48
|
enter: 'edit-next',
|
|
41
49
|
tab: 'template',
|
|
@@ -72,6 +80,7 @@ const initialState = () => ({
|
|
|
72
80
|
templateFocus: false,
|
|
73
81
|
blinkOn: true,
|
|
74
82
|
fileDirty: false,
|
|
83
|
+
fileEditRows: null,
|
|
75
84
|
});
|
|
76
85
|
|
|
77
86
|
const reset = (state) => {
|
|
@@ -91,6 +100,18 @@ const tickBlink = (api) => {
|
|
|
91
100
|
|
|
92
101
|
const viewSize = (api) => api.ui.lastSize ?? api.ui.getSize();
|
|
93
102
|
|
|
103
|
+
const followFileEdit = (api) => {
|
|
104
|
+
if (api.state.composeKind !== 'file') return;
|
|
105
|
+
const text = api.state.editor ? api.state.editor.text : null;
|
|
106
|
+
const rows = unit.linesFor(
|
|
107
|
+
api.ui,
|
|
108
|
+
api.nav.reviewPath,
|
|
109
|
+
text,
|
|
110
|
+
api.state.fileEditRows,
|
|
111
|
+
);
|
|
112
|
+
unit.followFileCursor(api.nav, api.state.editor, api.ui.lastFrame, rows);
|
|
113
|
+
};
|
|
114
|
+
|
|
94
115
|
const afterClose = (api) => {
|
|
95
116
|
api.ui.clampIndex();
|
|
96
117
|
api.ui.syncReviewPath();
|
|
@@ -121,6 +142,7 @@ const closeCompose = (api) => {
|
|
|
121
142
|
state.composeBaseline = '';
|
|
122
143
|
state.templateIndex = -1;
|
|
123
144
|
state.templateFocus = false;
|
|
145
|
+
state.fileEditRows = null;
|
|
124
146
|
afterClose(api);
|
|
125
147
|
};
|
|
126
148
|
|
|
@@ -167,8 +189,7 @@ const saveCodeNote = (api, item, text) => {
|
|
|
167
189
|
const key = itemFeedKey(item);
|
|
168
190
|
const note = itemNote(item, text);
|
|
169
191
|
if (text === original) {
|
|
170
|
-
setCode(notes, key, { ...note, clear: true });
|
|
171
|
-
return;
|
|
192
|
+
return void setCode(notes, key, { ...note, clear: true });
|
|
172
193
|
}
|
|
173
194
|
setCode(notes, key, note);
|
|
174
195
|
};
|
|
@@ -198,6 +219,85 @@ const commitCode = (api, text) => {
|
|
|
198
219
|
return true;
|
|
199
220
|
};
|
|
200
221
|
|
|
222
|
+
const saveUnitFile = (api, text) => {
|
|
223
|
+
const rel = api.nav.reviewPath;
|
|
224
|
+
const repo = api.ui.repo;
|
|
225
|
+
if (!rel || !repo || typeof repo.writeFile !== 'function') return true;
|
|
226
|
+
const caps = api.ui.capabilities;
|
|
227
|
+
if (!caps || caps.changes !== true) {
|
|
228
|
+
api.ui.status = 'read only';
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
repo.writeFile(api.ui.top, rel, text);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
api.ui.status = error.message;
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
if (typeof api.ui.ignoreWatch === 'function') api.ui.ignoreWatch();
|
|
238
|
+
api.state.composeBaseline = text;
|
|
239
|
+
api.state.fileDirty = true;
|
|
240
|
+
return true;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const snapshotPathKeys = (list, rel) => {
|
|
244
|
+
const keys = new Set();
|
|
245
|
+
for (const item of list) {
|
|
246
|
+
if (listPath(item) !== rel) continue;
|
|
247
|
+
keys.add(blockLinesKey(item));
|
|
248
|
+
}
|
|
249
|
+
return keys;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const stageNewFileHunks = (api, rel, beforeKeys) => {
|
|
253
|
+
const repo = api.ui.repo;
|
|
254
|
+
if (!rel || !repo || typeof repo.add !== 'function') return 0;
|
|
255
|
+
const caps = api.ui.capabilities;
|
|
256
|
+
if (!caps || caps.changes !== true) return 0;
|
|
257
|
+
const list = api.ui.items ?? [];
|
|
258
|
+
let staged = 0;
|
|
259
|
+
for (const item of list) {
|
|
260
|
+
if (listPath(item) !== rel) continue;
|
|
261
|
+
if (isReadOnlyOrigin(item.origin)) continue;
|
|
262
|
+
if (item.origin === 'staged') continue;
|
|
263
|
+
if (beforeKeys.has(blockLinesKey(item))) continue;
|
|
264
|
+
try {
|
|
265
|
+
repo.add(api.ui.top, item);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
api.ui.status = error.message;
|
|
268
|
+
return staged;
|
|
269
|
+
}
|
|
270
|
+
if (api.ui.collection && api.ui.collection.keepOrigin) {
|
|
271
|
+
api.ui.collection.keepOrigin(item, 'staged');
|
|
272
|
+
}
|
|
273
|
+
if (typeof api.ui.ignoreWatch === 'function') api.ui.ignoreWatch();
|
|
274
|
+
staged += 1;
|
|
275
|
+
}
|
|
276
|
+
return staged;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const saveFileCompose = (api) => {
|
|
280
|
+
const rel = api.nav.reviewPath;
|
|
281
|
+
const beforeKeys = snapshotPathKeys(api.ui.items ?? [], rel);
|
|
282
|
+
if (api.state.editor) {
|
|
283
|
+
api.nav.unitLine = api.state.editor.linePos().line;
|
|
284
|
+
}
|
|
285
|
+
const result = saveUnitFile(api, api.state.editor.text);
|
|
286
|
+
if (result === false) return { kind: 'error' };
|
|
287
|
+
const reload = api.state.fileDirty === true;
|
|
288
|
+
api.ui.flushReview();
|
|
289
|
+
closeCompose(api);
|
|
290
|
+
if (!reload || typeof api.ui.reloadAfterChange !== 'function') {
|
|
291
|
+
api.ui.status = 'saved';
|
|
292
|
+
return { kind: 'saved' };
|
|
293
|
+
}
|
|
294
|
+
api.ui.reloadAfterChange(() => {
|
|
295
|
+
const n = stageNewFileHunks(api, rel, beforeKeys);
|
|
296
|
+
api.ui.status = n > 0 ? 'staged' : 'saved';
|
|
297
|
+
});
|
|
298
|
+
return { kind: 'saved' };
|
|
299
|
+
};
|
|
300
|
+
|
|
201
301
|
const commitCompose = (api) => {
|
|
202
302
|
const { state } = api;
|
|
203
303
|
if (!state.composeKind || !state.editor) return true;
|
|
@@ -211,6 +311,7 @@ const commitCompose = (api) => {
|
|
|
211
311
|
return true;
|
|
212
312
|
}
|
|
213
313
|
if (state.composeKind === 'code') return commitCode(api, text);
|
|
314
|
+
if (state.composeKind === 'file') return saveUnitFile(api, text);
|
|
214
315
|
return true;
|
|
215
316
|
};
|
|
216
317
|
|
|
@@ -219,6 +320,7 @@ const saveCompose = (api, extra) => {
|
|
|
219
320
|
const spec = KIND[api.state.composeKind];
|
|
220
321
|
if (spec && spec.command) return { kind: spec.command };
|
|
221
322
|
const kind = api.state.composeKind;
|
|
323
|
+
if (kind === 'file') return saveFileCompose(api);
|
|
222
324
|
const baseline = api.state.composeBaseline;
|
|
223
325
|
const result = commitCompose(api);
|
|
224
326
|
if (result === false) return { kind: 'error' };
|
|
@@ -243,6 +345,10 @@ const autosave = (api) => {
|
|
|
243
345
|
const { state } = api;
|
|
244
346
|
const spec = KIND[state.composeKind];
|
|
245
347
|
const text = state.editor ? state.editor.text : '';
|
|
348
|
+
if (state.composeKind === 'file' && state.editor) {
|
|
349
|
+
if (text === state.composeBaseline) return;
|
|
350
|
+
return void saveUnitFile(api, text);
|
|
351
|
+
}
|
|
246
352
|
const editingCode = state.composeKind === 'code';
|
|
247
353
|
const composing = state.composeKind !== null;
|
|
248
354
|
const reviewKind = composing && (!spec || !spec.command);
|
|
@@ -267,9 +373,10 @@ const composeView = (api) => {
|
|
|
267
373
|
};
|
|
268
374
|
|
|
269
375
|
const liveCodeText = (api) => {
|
|
376
|
+
const { state } = api;
|
|
377
|
+
if (state.composeKind === 'file' && state.editor) return state.editor.text;
|
|
270
378
|
const item = api.ui.current();
|
|
271
379
|
if (!canEditCode(item)) return null;
|
|
272
|
-
const { state } = api;
|
|
273
380
|
if (state.composeKind === 'code' && state.editor) return state.editor.text;
|
|
274
381
|
if (writesCodeFile(api, item)) return null;
|
|
275
382
|
const notes = api.review.store;
|
|
@@ -282,7 +389,8 @@ const liveCodeText = (api) => {
|
|
|
282
389
|
const codeOverlayView = (api) => {
|
|
283
390
|
const text = liveCodeText(api);
|
|
284
391
|
if (text === null) return null;
|
|
285
|
-
const editing =
|
|
392
|
+
const editing =
|
|
393
|
+
api.state.composeKind === 'code' || api.state.composeKind === 'file';
|
|
286
394
|
const editor = api.state.editor;
|
|
287
395
|
const cursor = editing && editor ? editor.cursor : null;
|
|
288
396
|
return { text, keepEmpty: editing, cursor };
|
|
@@ -351,14 +459,12 @@ const moveComposeLine = (api, delta) => {
|
|
|
351
459
|
if (move === 'todo') {
|
|
352
460
|
const size = viewSize(api);
|
|
353
461
|
const inner = todoBodyWidth(size.width);
|
|
354
|
-
api.todos.moveTodoCompose(delta, inner);
|
|
355
|
-
return;
|
|
462
|
+
return void api.todos.moveTodoCompose(delta, inner);
|
|
356
463
|
}
|
|
357
464
|
if (move === 'code') {
|
|
358
465
|
const size = viewSize(api);
|
|
359
466
|
const inner = codeInnerWidth(size.width, api.ui.layout);
|
|
360
|
-
api.state.editor.moveLine(delta, inner);
|
|
361
|
-
return;
|
|
467
|
+
return void api.state.editor.moveLine(delta, inner);
|
|
362
468
|
}
|
|
363
469
|
moveNoteLine(api, delta);
|
|
364
470
|
};
|
|
@@ -410,7 +516,26 @@ const onFeedback = (api) => {
|
|
|
410
516
|
openCompose(api, 'feedback', text);
|
|
411
517
|
};
|
|
412
518
|
|
|
519
|
+
const onUnitEdit = (api) => {
|
|
520
|
+
const rel = api.nav.reviewPath;
|
|
521
|
+
if (!rel) {
|
|
522
|
+
api.ui.status = 'not a diff block';
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const caps = api.ui.capabilities;
|
|
526
|
+
if (!caps || caps.changes !== true) {
|
|
527
|
+
api.ui.status = 'read only';
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
const at = unit.editCursor(api.ui);
|
|
531
|
+
openCompose(api, 'file', at.text);
|
|
532
|
+
api.state.fileEditRows = at.rows ?? null;
|
|
533
|
+
api.state.editor.cursor = Math.min(at.cursor, at.text.length);
|
|
534
|
+
followFileEdit(api);
|
|
535
|
+
};
|
|
536
|
+
|
|
413
537
|
const onCode = (api) => {
|
|
538
|
+
if (api.nav.pane === 'unit') return void onUnitEdit(api);
|
|
414
539
|
if (api.nav.pane === 'files') {
|
|
415
540
|
api.ui.status = 'not a diff block';
|
|
416
541
|
return;
|
|
@@ -438,6 +563,7 @@ const enterCompose = (api) => {
|
|
|
438
563
|
if (spec && spec.enter === 'newline') {
|
|
439
564
|
state.editor.insert('\n');
|
|
440
565
|
resetBlink(api);
|
|
566
|
+
followFileEdit(api);
|
|
441
567
|
return null;
|
|
442
568
|
}
|
|
443
569
|
if (spec && spec.enter === 'edit-next') {
|
|
@@ -484,6 +610,7 @@ const handleKey = (api, key) => {
|
|
|
484
610
|
const result = special(api);
|
|
485
611
|
if (COMPOSE_EDITS.includes(key)) api.templates.clearTemplatePick();
|
|
486
612
|
resetBlink(api);
|
|
613
|
+
followFileEdit(api);
|
|
487
614
|
return result ?? null;
|
|
488
615
|
}
|
|
489
616
|
if (!key.length) return null;
|
|
@@ -492,10 +619,22 @@ const handleKey = (api, key) => {
|
|
|
492
619
|
api.templates.clearTemplatePick();
|
|
493
620
|
api.state.editor.insert(key);
|
|
494
621
|
resetBlink(api);
|
|
622
|
+
followFileEdit(api);
|
|
495
623
|
}
|
|
496
624
|
return null;
|
|
497
625
|
};
|
|
498
626
|
|
|
627
|
+
const applyDiskText = (api, text) => {
|
|
628
|
+
if (api.state.composeKind !== 'file' || !api.state.editor) return;
|
|
629
|
+
if (text === api.state.editor.text) return;
|
|
630
|
+
const cursor = api.state.editor.cursor;
|
|
631
|
+
api.state.editor.text = text;
|
|
632
|
+
api.state.editor.cursor = Math.min(cursor, text.length);
|
|
633
|
+
api.state.composeBaseline = text;
|
|
634
|
+
api.state.fileEditRows = unit.linesFor(api.ui, api.nav.reviewPath, null);
|
|
635
|
+
followFileEdit(api);
|
|
636
|
+
};
|
|
637
|
+
|
|
499
638
|
const composerMethods = (api) => ({
|
|
500
639
|
reset: () => reset(api.state),
|
|
501
640
|
openCompose: (kind, text, todoId) => openCompose(api, kind, text, todoId),
|
|
@@ -511,6 +650,12 @@ const composerMethods = (api) => ({
|
|
|
511
650
|
onFeedback: () => onFeedback(api),
|
|
512
651
|
onCode: () => onCode(api),
|
|
513
652
|
handleKey: (key) => handleKey(api, key),
|
|
653
|
+
liveFileText: () => {
|
|
654
|
+
if (api.state.composeKind !== 'file' || !api.state.editor) return null;
|
|
655
|
+
return api.state.editor.text;
|
|
656
|
+
},
|
|
657
|
+
fileEditRows: () => api.state.fileEditRows,
|
|
658
|
+
applyDiskText: (text) => applyDiskText(api, text),
|
|
514
659
|
});
|
|
515
660
|
|
|
516
661
|
const createComposer = ({ nav, review, ui, restartBlink }) => {
|