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