reslop 0.2.5 → 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 +12 -1
- package/LICENSE +1 -1
- package/README.md +11 -11
- package/bin/reslop.js +3 -8
- package/lib/ansi.js +32 -19
- package/lib/capabilities.js +8 -8
- package/lib/cli.js +40 -74
- package/lib/clipboard.js +39 -47
- 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 +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 +71 -89
- package/lib/git-branches.js +254 -416
- package/lib/git-deps.js +147 -272
- 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 +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 +49 -82
- 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 +220 -333
- package/lib/render/commits.js +160 -361
- 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 -258
- package/lib/render/files.js +40 -85
- package/lib/render/frame.js +78 -117
- package/lib/render/notes.js +139 -210
- package/lib/render/npm.js +213 -177
- package/lib/render/present.js +4 -11
- package/lib/render/primitives.js +72 -174
- 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 -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 +68 -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 +61 -43
- package/lib/session/branches.js +166 -222
- package/lib/session/change-actions.js +49 -119
- package/lib/session/commit.js +274 -355
- package/lib/session/compose.js +556 -714
- package/lib/session/dashboard.js +385 -0
- package/lib/session/dispatch.js +162 -170
- package/lib/session/files-pane.js +76 -44
- package/lib/session/items.js +6 -17
- package/lib/session/lifecycle.js +393 -466
- package/lib/session/load.js +1 -14
- package/lib/session/navigation.js +2 -2
- package/lib/session/npm.js +403 -420
- package/lib/session/ops.js +5 -13
- package/lib/session/pointer.js +16 -14
- package/lib/session/progress.js +39 -30
- package/lib/session/review.js +1 -3
- package/lib/session/tasks.js +276 -235
- package/lib/session/templates.js +51 -59
- package/lib/session/terminal.js +85 -132
- package/lib/session/view.js +47 -81
- package/lib/session/watch.js +196 -212
- package/lib/session.js +81 -161
- package/lib/update.js +44 -111
- package/lib/utilities.js +46 -88
- package/lib/wrap.js +29 -55
- package/package.json +1 -1
package/lib/session/compose.js
CHANGED
|
@@ -19,151 +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
26
|
const clipboard = require('../clipboard.js');
|
|
27
27
|
|
|
28
28
|
const COMPOSE_EDITS = ['backspace', 'delete', 'ctrl-z', 'ctrl-y'];
|
|
29
|
+
const COMPOSE_CLIP = ['ctrl-x', 'ctrl-v'];
|
|
29
30
|
const COMPOSE_LEAVE = ['next', 'prev', 'tasks'];
|
|
30
31
|
|
|
31
32
|
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);
|
|
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' },
|
|
118
40
|
};
|
|
119
41
|
|
|
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
|
-
};
|
|
42
|
+
const COMMAND_KINDS = ['commit', 'branch', 'npm'];
|
|
43
|
+
const NOTE_VIEWS = ['feedback', 'commit', 'branch', 'npm'];
|
|
167
44
|
|
|
168
45
|
const itemNote = (item, text) => {
|
|
169
46
|
const hunk = item.hunk;
|
|
@@ -178,641 +55,606 @@ const itemNote = (item, text) => {
|
|
|
178
55
|
};
|
|
179
56
|
};
|
|
180
57
|
|
|
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));
|
|
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() === '';
|
|
195
64
|
};
|
|
196
65
|
|
|
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
|
-
};
|
|
66
|
+
const filledLines = (text) =>
|
|
67
|
+
text.split('\n').filter((line) => line.trim() !== '').length;
|
|
204
68
|
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
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;
|
|
212
75
|
}
|
|
213
|
-
|
|
76
|
+
return count;
|
|
214
77
|
};
|
|
215
78
|
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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;
|
|
221
195
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
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);
|
|
227
201
|
}
|
|
228
|
-
dropCodeNote(api, item, text);
|
|
229
|
-
api.state.fileDirty = true;
|
|
230
|
-
return true;
|
|
231
|
-
};
|
|
232
202
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return true;
|
|
239
|
-
};
|
|
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
|
+
}
|
|
240
208
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
api.ui.status = 'read only';
|
|
248
|
-
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 });
|
|
249
215
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
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;
|
|
255
239
|
}
|
|
256
|
-
api.ui.ignoreWatch();
|
|
257
|
-
api.state.composeBaseline = text;
|
|
258
|
-
api.state.fileDirty = true;
|
|
259
|
-
return true;
|
|
260
|
-
};
|
|
261
240
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
+
}
|
|
269
249
|
try {
|
|
270
|
-
repo.
|
|
250
|
+
ui.repo.writeFile(ui.top, rel, text);
|
|
271
251
|
} catch (error) {
|
|
272
|
-
|
|
252
|
+
ui.status = error.message;
|
|
273
253
|
return false;
|
|
274
254
|
}
|
|
275
|
-
|
|
255
|
+
ui.ignoreWatch();
|
|
256
|
+
this.composeBaseline = text;
|
|
257
|
+
this.fileDirty = true;
|
|
276
258
|
return true;
|
|
277
259
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (
|
|
283
|
-
if (
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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;
|
|
274
|
+
}
|
|
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;
|
|
289
288
|
}
|
|
290
|
-
|
|
291
|
-
staged
|
|
289
|
+
if (staged) ui.ignoreWatch();
|
|
290
|
+
return staged > 0;
|
|
292
291
|
}
|
|
293
|
-
if (staged) api.ui.ignoreWatch();
|
|
294
|
-
return staged > 0;
|
|
295
|
-
};
|
|
296
292
|
|
|
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' };
|
|
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
|
+
});
|
|
315
304
|
}
|
|
316
|
-
api.ui.reloadAfterChange(() => {
|
|
317
|
-
if (!api.ui.status) api.ui.status = doneStatus;
|
|
318
|
-
});
|
|
319
|
-
return { kind: 'saved' };
|
|
320
|
-
};
|
|
321
305
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if (
|
|
328
|
-
|
|
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');
|
|
316
|
+
return { kind: 'saved' };
|
|
329
317
|
}
|
|
330
|
-
|
|
331
|
-
|
|
318
|
+
|
|
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);
|
|
332
333
|
return true;
|
|
333
334
|
}
|
|
334
|
-
if (state.composeKind === 'code') return commitCode(api, text);
|
|
335
|
-
if (state.composeKind === 'file') return saveUnitFile(api, text);
|
|
336
|
-
return true;
|
|
337
|
-
};
|
|
338
335
|
|
|
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;
|
|
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' };
|
|
372
364
|
}
|
|
373
|
-
if (opts.editNext === true) api.tasks.editNextTask();
|
|
374
|
-
return { kind: 'saved' };
|
|
375
|
-
};
|
|
376
365
|
|
|
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
|
-
|
|
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
|
+
}
|
|
454
462
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
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);
|
|
460
472
|
}
|
|
461
|
-
return api.templates.applyTemplate();
|
|
462
|
-
};
|
|
463
473
|
|
|
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;
|
|
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);
|
|
473
479
|
}
|
|
474
|
-
|
|
475
|
-
|
|
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);
|
|
476
491
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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);
|
|
482
498
|
}
|
|
483
|
-
|
|
484
|
-
|
|
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);
|
|
485
505
|
return true;
|
|
486
506
|
}
|
|
487
|
-
return false;
|
|
488
|
-
};
|
|
489
507
|
|
|
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;
|
|
508
|
+
home(extend) {
|
|
509
|
+
if (!extend && this.jumpTemplates(false)) return;
|
|
510
|
+
this.editor.home(extend);
|
|
497
511
|
}
|
|
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
512
|
|
|
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);
|
|
513
|
+
end(extend) {
|
|
514
|
+
if (!extend && this.jumpTemplates(true)) return;
|
|
515
|
+
this.editor.end(extend);
|
|
528
516
|
}
|
|
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
517
|
|
|
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;
|
|
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 : '');
|
|
573
527
|
}
|
|
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
528
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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();
|
|
560
|
+
}
|
|
619
561
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
}
|
|
562
|
+
insertNewline() {
|
|
563
|
+
this.editor.insert('\n');
|
|
564
|
+
this.followFileEdit();
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
625
567
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
+
}
|
|
633
586
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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();
|
|
638
594
|
}
|
|
639
|
-
return count;
|
|
640
|
-
};
|
|
641
595
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
count += 1;
|
|
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';
|
|
648
601
|
}
|
|
649
|
-
return count;
|
|
650
|
-
};
|
|
651
602
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
return insertNewline(api);
|
|
658
|
-
};
|
|
603
|
+
cut() {
|
|
604
|
+
if (!this.editor.hasSelect()) return;
|
|
605
|
+
this.copy();
|
|
606
|
+
this.editor.removeSpan();
|
|
607
|
+
}
|
|
659
608
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
return api.templates.selectTemplate(state.templateIndex);
|
|
609
|
+
paste() {
|
|
610
|
+
const text = clipboard.pasteText();
|
|
611
|
+
if (text) this.editor.insert(text);
|
|
664
612
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
return saveCompose(api, { editNext: true });
|
|
613
|
+
|
|
614
|
+
afterEdit() {
|
|
615
|
+
this.followFileEdit();
|
|
616
|
+
this.syncCodeScroll();
|
|
670
617
|
}
|
|
671
|
-
return saveCompose(api);
|
|
672
|
-
};
|
|
673
618
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
+
}
|
|
679
642
|
return null;
|
|
680
643
|
}
|
|
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
644
|
|
|
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
|
-
};
|
|
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
|
+
}
|
|
814
659
|
|
|
815
|
-
module.exports = {
|
|
816
|
-
COMPOSE_LEAVE,
|
|
817
|
-
createComposer,
|
|
818
|
-
};
|
|
660
|
+
module.exports = { COMPOSE_LEAVE, Composer };
|