reslop 0.1.9 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -1
- package/README.md +3 -1
- package/bin/reslop.js +2 -2
- package/lib/ansi.js +148 -5
- package/lib/api.js +4 -2
- package/lib/capabilities.js +13 -14
- package/lib/cli.js +38 -49
- package/lib/clipboard.js +1 -1
- package/lib/{deps/changes.js → deps-diff.js} +309 -19
- package/lib/deps.js +498 -29
- package/lib/detect.js +1 -7
- package/lib/diff/align.js +7 -10
- package/lib/diff/diff.js +53 -0
- package/lib/diff/display.js +5 -4
- package/lib/diff/inline.js +26 -21
- package/lib/diff/items.js +6 -4
- package/lib/diff/patch.js +1 -3
- package/lib/diff/unit.js +392 -0
- package/lib/files.js +5 -8
- package/lib/{git/branches.js → git-branches.js} +191 -11
- package/lib/{git/dependencies.js → git-deps.js} +111 -38
- package/lib/git-reword-todo.js +20 -0
- package/lib/git.js +606 -68
- package/lib/github.js +28 -197
- package/lib/gitlab.js +24 -195
- package/lib/highlight/bash.js +1 -3
- package/lib/highlight/css.js +1 -3
- package/lib/highlight/js.js +11 -14
- package/lib/highlight.js +6 -9
- package/lib/keys.js +14 -8
- package/lib/manifest.js +330 -0
- package/lib/remote.js +374 -0
- package/lib/render/branches.js +18 -100
- package/lib/render/chrome.js +71 -14
- package/lib/render/commits.js +69 -60
- package/lib/render/diff.js +20 -20
- package/lib/render/files.js +6 -34
- package/lib/render/frame.js +36 -13
- package/lib/render/notes.js +26 -8
- package/lib/render/primitives.js +99 -11
- package/lib/{render.js → render/render.js} +8 -8
- package/lib/render/unit.js +41 -0
- package/lib/review-merge.js +270 -0
- package/lib/review.js +782 -14
- package/lib/select.js +5 -10
- package/lib/session/accessors.js +9 -1
- package/lib/session/actions.js +61 -18
- package/lib/session/branches.js +116 -128
- package/lib/session/change-actions.js +202 -123
- package/lib/session/commit.js +201 -115
- package/lib/session/compose.js +181 -15
- package/lib/session/dispatch.js +110 -60
- package/lib/session/files-pane.js +156 -148
- package/lib/session/items.js +22 -30
- package/lib/session/lifecycle.js +151 -54
- package/lib/session/load.js +19 -33
- package/lib/session/navigation.js +12 -1
- package/lib/session/ops.js +28 -41
- package/lib/session/pointer.js +57 -57
- package/lib/session/review.js +23 -37
- package/lib/session/terminal.js +6 -12
- package/lib/session/todos.js +37 -13
- package/lib/session/unit.js +208 -0
- package/lib/session/update.js +33 -46
- package/lib/session/view.js +78 -9
- package/lib/session/watch.js +128 -24
- package/lib/session.js +22 -246
- package/lib/source.js +3 -3
- package/lib/update.js +40 -75
- package/lib/{git/proc.js → utilities.js} +75 -11
- package/lib/wrap.js +1 -3
- package/package.json +2 -2
- package/lib/deps/items.js +0 -134
- package/lib/deps/manifest.js +0 -152
- package/lib/deps/merge.js +0 -120
- package/lib/deps/proposals.js +0 -164
- package/lib/deps/reports.js +0 -180
- package/lib/deps/rows.js +0 -176
- package/lib/deps/scan.js +0 -138
- package/lib/deps/usage.js +0 -68
- package/lib/deps/versions.js +0 -119
- package/lib/diff.js +0 -33
- package/lib/git/changes.js +0 -164
- package/lib/git/read.js +0 -253
- package/lib/git/run.js +0 -100
- package/lib/npm/commands.js +0 -100
- package/lib/remote/changes.js +0 -37
- package/lib/remote/headers.js +0 -36
- package/lib/remote/http.js +0 -122
- package/lib/remote/notes.js +0 -94
- package/lib/review/format.js +0 -434
- package/lib/review/storage.js +0 -150
- package/lib/review/store.js +0 -178
- package/lib/review/templates.js +0 -49
- package/lib/session/origins.js +0 -7
- package/lib/sys.js +0 -84
package/lib/session/update.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { stateView } = require('./accessors.js');
|
|
4
|
+
|
|
3
5
|
const update = require('../update.js');
|
|
4
6
|
const { checkUpdate, installUpdate, markSkipped } = update;
|
|
5
7
|
|
|
6
8
|
const optsOf = (api) => {
|
|
7
|
-
const value = api.
|
|
9
|
+
const value = api.ui.updateOpts;
|
|
8
10
|
if (!value || value === true) return {};
|
|
9
11
|
return value;
|
|
10
12
|
};
|
|
@@ -19,15 +21,16 @@ const reset = (state) => {
|
|
|
19
21
|
|
|
20
22
|
const maybePrompt = (api) => {
|
|
21
23
|
if (!api.state.offer) return;
|
|
22
|
-
if (!api.
|
|
23
|
-
if (api.
|
|
24
|
-
api.
|
|
24
|
+
if (!api.ui.loader.didLoad) return;
|
|
25
|
+
if (api.ui.mode !== 'review') return;
|
|
26
|
+
api.ui.mode = 'confirmUpdate';
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
const runInstall = async (api, version) => {
|
|
28
|
-
api.
|
|
29
|
-
api.
|
|
30
|
-
api.
|
|
30
|
+
api.ui.status = 'updating reslop';
|
|
31
|
+
api.ui.progressFrame = 0;
|
|
32
|
+
api.ui.progress.start('install');
|
|
33
|
+
api.ui.paint();
|
|
31
34
|
let ok = false;
|
|
32
35
|
try {
|
|
33
36
|
await installUpdate(version, optsOf(api));
|
|
@@ -35,23 +38,23 @@ const runInstall = async (api, version) => {
|
|
|
35
38
|
} catch {
|
|
36
39
|
// ignore install errors
|
|
37
40
|
}
|
|
38
|
-
api.
|
|
39
|
-
if (api.
|
|
40
|
-
api.
|
|
41
|
-
api.
|
|
41
|
+
api.ui.progress.stop('install');
|
|
42
|
+
if (api.ui.done || !api.ui.uiOpen) return;
|
|
43
|
+
api.ui.status = ok ? 'updated' : 'update failed';
|
|
44
|
+
api.ui.paint();
|
|
42
45
|
};
|
|
43
46
|
|
|
44
47
|
const acceptUpdate = (api) => {
|
|
45
|
-
api.
|
|
48
|
+
api.ui.mode = 'review';
|
|
46
49
|
api.state.offer = false;
|
|
47
50
|
const version = api.state.to;
|
|
48
51
|
api.state.installPromise = runInstall(api, version);
|
|
49
52
|
};
|
|
50
53
|
|
|
51
54
|
const declineUpdate = (api) => {
|
|
52
|
-
api.
|
|
55
|
+
api.ui.mode = 'review';
|
|
53
56
|
api.state.offer = false;
|
|
54
|
-
api.
|
|
57
|
+
api.ui.status = '';
|
|
55
58
|
markSkipped(api.state.to, optsOf(api));
|
|
56
59
|
};
|
|
57
60
|
|
|
@@ -62,7 +65,7 @@ const runOpenUpdate = async (api) => {
|
|
|
62
65
|
} catch {
|
|
63
66
|
return;
|
|
64
67
|
}
|
|
65
|
-
if (api.
|
|
68
|
+
if (api.ui.done) return;
|
|
66
69
|
if (plan.action === 'install') {
|
|
67
70
|
api.state.installPromise = runInstall(api, plan.latest);
|
|
68
71
|
await api.state.installPromise;
|
|
@@ -73,17 +76,17 @@ const runOpenUpdate = async (api) => {
|
|
|
73
76
|
api.state.to = plan.latest;
|
|
74
77
|
api.state.offer = true;
|
|
75
78
|
maybePrompt(api);
|
|
76
|
-
api.
|
|
79
|
+
api.ui.paint();
|
|
77
80
|
};
|
|
78
81
|
|
|
79
82
|
const openUpdate = (api) => {
|
|
80
|
-
if (api.
|
|
83
|
+
if (api.ui.updateOpts === false) return Promise.resolve();
|
|
81
84
|
if (api.state.checkPromise) return api.state.checkPromise;
|
|
82
85
|
api.state.checkPromise = runOpenUpdate(api);
|
|
83
86
|
return api.state.checkPromise;
|
|
84
87
|
};
|
|
85
88
|
|
|
86
|
-
const createUpdateCoordinator = (
|
|
89
|
+
const createUpdateCoordinator = (ui) => {
|
|
87
90
|
const state = {
|
|
88
91
|
offer: false,
|
|
89
92
|
from: '',
|
|
@@ -91,35 +94,19 @@ const createUpdateCoordinator = (options) => {
|
|
|
91
94
|
checkPromise: null,
|
|
92
95
|
installPromise: null,
|
|
93
96
|
};
|
|
94
|
-
const api = {
|
|
95
|
-
return
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return state.from;
|
|
104
|
-
},
|
|
105
|
-
get to() {
|
|
106
|
-
return state.to;
|
|
97
|
+
const api = { ui, state };
|
|
98
|
+
return stateView(
|
|
99
|
+
state,
|
|
100
|
+
{
|
|
101
|
+
reset: () => reset(state),
|
|
102
|
+
maybePrompt: () => maybePrompt(api),
|
|
103
|
+
accept: () => acceptUpdate(api),
|
|
104
|
+
decline: () => declineUpdate(api),
|
|
105
|
+
open: () => openUpdate(api),
|
|
107
106
|
},
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
get installPromise() {
|
|
112
|
-
return state.installPromise;
|
|
113
|
-
},
|
|
114
|
-
set installPromise(value) {
|
|
115
|
-
state.installPromise = value;
|
|
116
|
-
},
|
|
117
|
-
reset: () => reset(state),
|
|
118
|
-
maybePrompt: () => maybePrompt(api),
|
|
119
|
-
accept: () => acceptUpdate(api),
|
|
120
|
-
decline: () => declineUpdate(api),
|
|
121
|
-
open: () => openUpdate(api),
|
|
122
|
-
};
|
|
107
|
+
['offer', 'installPromise'],
|
|
108
|
+
['from', 'to', 'checkPromise'],
|
|
109
|
+
);
|
|
123
110
|
};
|
|
124
111
|
|
|
125
112
|
module.exports = { createUpdateCoordinator };
|
package/lib/session/view.js
CHANGED
|
@@ -6,11 +6,8 @@ const files = require('../files.js');
|
|
|
6
6
|
const { fileEntries, REPO_TODOS_LABEL } = files;
|
|
7
7
|
const review = require('../review.js');
|
|
8
8
|
const { noteCounts } = review;
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
'github-pr': 'pr',
|
|
12
|
-
'gitlab-mr': 'mr',
|
|
13
|
-
};
|
|
9
|
+
const diff = require('../diff/diff.js');
|
|
10
|
+
const { unitLines, itemsForPath, splitEditor, mergeEditRows } = diff;
|
|
14
11
|
|
|
15
12
|
const relabelStatus = (entries, from, to) => {
|
|
16
13
|
if (!to) return entries;
|
|
@@ -43,14 +40,60 @@ const todosEntry = (counts) => ({
|
|
|
43
40
|
unstaged: 0,
|
|
44
41
|
});
|
|
45
42
|
|
|
43
|
+
const blankEntry = (rel) => ({
|
|
44
|
+
path: rel,
|
|
45
|
+
origins: [],
|
|
46
|
+
firstIndex: 0,
|
|
47
|
+
openIndex: 0,
|
|
48
|
+
remaining: 0,
|
|
49
|
+
added: 0,
|
|
50
|
+
removed: 0,
|
|
51
|
+
stagedAdded: 0,
|
|
52
|
+
stagedRemoved: 0,
|
|
53
|
+
unstagedAdded: 0,
|
|
54
|
+
unstagedRemoved: 0,
|
|
55
|
+
staged: 0,
|
|
56
|
+
unstaged: 0,
|
|
57
|
+
date: '',
|
|
58
|
+
status: '',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const listedNames = (api) => {
|
|
62
|
+
const repo = api.ui.repo;
|
|
63
|
+
if (!repo || typeof repo.listFiles !== 'function') return [];
|
|
64
|
+
try {
|
|
65
|
+
return repo.listFiles(api.ui.top || api.ui.cwd, api.ui.paths ?? [], {
|
|
66
|
+
commit: api.ui.rev,
|
|
67
|
+
});
|
|
68
|
+
} catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const mergeUnitFiles = (entries, names) => {
|
|
74
|
+
const have = new Set();
|
|
75
|
+
for (const entry of entries) have.add(entry.path);
|
|
76
|
+
const extra = [];
|
|
77
|
+
for (const rel of names) {
|
|
78
|
+
if (!rel || have.has(rel)) continue;
|
|
79
|
+
extra.push(blankEntry(rel));
|
|
80
|
+
}
|
|
81
|
+
const merged = [...entries, ...extra];
|
|
82
|
+
merged.sort((left, right) => left.path.localeCompare(right.path, 'en'));
|
|
83
|
+
return merged;
|
|
84
|
+
};
|
|
85
|
+
|
|
46
86
|
const fileList = (api, notes = noteCounts(api.review.store)) => {
|
|
47
87
|
const entries = fileEntries(api.collection.items);
|
|
48
88
|
const sourceLabel = api.ui.sourceLabel;
|
|
49
89
|
const from = sourceLabel ? 'pr' : 'commit';
|
|
50
90
|
const to = sourceLabel || api.ui.revShort;
|
|
51
91
|
const labeled = relabelStatus(entries, from, to);
|
|
52
|
-
|
|
53
|
-
|
|
92
|
+
const names = listedNames(api);
|
|
93
|
+
const scoped =
|
|
94
|
+
api.nav.fileScope === 'file' ? mergeUnitFiles(labeled, names) : labeled;
|
|
95
|
+
scoped.unshift(todosEntry(notes));
|
|
96
|
+
return scoped;
|
|
54
97
|
};
|
|
55
98
|
|
|
56
99
|
const counts = (api, notes = noteCounts(api.review.store)) => {
|
|
@@ -76,11 +119,34 @@ const counts = (api, notes = noteCounts(api.review.store)) => {
|
|
|
76
119
|
|
|
77
120
|
const viewStatus = (api) => api.ui.status || api.ui.busy;
|
|
78
121
|
|
|
122
|
+
const unitFileText = (api) => {
|
|
123
|
+
const rel = api.nav.reviewPath;
|
|
124
|
+
if (!rel) return '';
|
|
125
|
+
const repo = api.ui.repo;
|
|
126
|
+
if (repo && typeof repo.fileText === 'function') {
|
|
127
|
+
return repo.fileText(api.ui.top, rel, api.ui.rev);
|
|
128
|
+
}
|
|
129
|
+
return '';
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const unitBodyLines = (api) => {
|
|
133
|
+
if (api.nav.pane !== 'unit') return [];
|
|
134
|
+
const rel = api.nav.reviewPath;
|
|
135
|
+
const group = itemsForPath(api.collection.items, rel);
|
|
136
|
+
const live = api.composer.liveFileText();
|
|
137
|
+
if (live !== null) {
|
|
138
|
+
const base = api.composer.fileEditRows();
|
|
139
|
+
if (base && base.length) return mergeEditRows(base, splitEditor(live));
|
|
140
|
+
return mergeEditRows(unitLines(live, group), splitEditor(live));
|
|
141
|
+
}
|
|
142
|
+
return unitLines(unitFileText(api), group);
|
|
143
|
+
};
|
|
144
|
+
|
|
79
145
|
const view = (api) => {
|
|
80
146
|
const { composer, nav, ui, gitBranches, commits, updater } = api;
|
|
81
147
|
const templates = composer.shownTemplates();
|
|
82
148
|
const change = ui.change;
|
|
83
|
-
const sourceKind = (change &&
|
|
149
|
+
const sourceKind = (change && change.source) || '';
|
|
84
150
|
const notes = noteCounts(api.review.store);
|
|
85
151
|
return {
|
|
86
152
|
item: ui.current(),
|
|
@@ -94,9 +160,12 @@ const view = (api) => {
|
|
|
94
160
|
counts: counts(api, notes),
|
|
95
161
|
selection: nav.selection,
|
|
96
162
|
pane: nav.pane,
|
|
163
|
+
fileScope: nav.fileScope,
|
|
97
164
|
files: fileList(api, notes),
|
|
98
165
|
fileCursor: nav.fileCursor,
|
|
99
166
|
reviewPath: nav.reviewPath,
|
|
167
|
+
unitLine: nav.unitLine,
|
|
168
|
+
unitLines: unitBodyLines(api),
|
|
100
169
|
branch: ui.branch,
|
|
101
170
|
branches: gitBranches.branches,
|
|
102
171
|
branchCursor: nav.branchCursor,
|
|
@@ -128,4 +197,4 @@ const createView = (api) => ({
|
|
|
128
197
|
view: () => view(api),
|
|
129
198
|
});
|
|
130
199
|
|
|
131
|
-
module.exports = {
|
|
200
|
+
module.exports = { relabelStatus, createView };
|
package/lib/session/watch.js
CHANGED
|
@@ -72,25 +72,127 @@ const closeWatcher = (watcher) => {
|
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
74
|
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
const reviewMarkdown = (rel) => {
|
|
76
|
+
const norm = `${rel}`.replaceAll('\\', '/');
|
|
77
|
+
if (!norm.startsWith('.review/')) return false;
|
|
78
|
+
const rest = norm.slice('.review/'.length);
|
|
79
|
+
if (!rest || rest.includes('/')) return false;
|
|
80
|
+
return rest.endsWith('.md');
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const isReviewDir = (root, dir) => {
|
|
84
|
+
const rel = path.relative(root, dir).replaceAll('\\', '/');
|
|
85
|
+
return rel === '.review' || rel.startsWith('.review/');
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const hasFile = (full) => {
|
|
89
|
+
try {
|
|
90
|
+
fs.accessSync(full);
|
|
91
|
+
return true;
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const isBareReviewName = (root, dir, rel) => {
|
|
98
|
+
if (!rel || rel.includes('/') || !rel.endsWith('.md')) return false;
|
|
99
|
+
const base = path.basename(rel);
|
|
100
|
+
if (hasFile(path.join(dir, base))) return false;
|
|
101
|
+
return hasFile(path.join(root, '.review', base));
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const isReviewEvent = (root, dir, name) => {
|
|
105
|
+
if (isReviewDir(root, dir)) return true;
|
|
106
|
+
if (!name) return false;
|
|
107
|
+
const rel = path.relative(root, path.join(dir, name));
|
|
108
|
+
const norm = rel.replaceAll('\\', '/');
|
|
109
|
+
if (reviewMarkdown(norm)) return true;
|
|
110
|
+
return isBareReviewName(root, dir, norm);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const isOwnDirEvent = (dir, name) => {
|
|
114
|
+
if (!name) return false;
|
|
115
|
+
const base = path.basename(dir);
|
|
116
|
+
const slash = `${name}`.replaceAll('\\', '/');
|
|
117
|
+
const bare = slash.startsWith('./') ? slash.slice(2) : slash;
|
|
118
|
+
const trimmed = bare.endsWith('/') ? bare.slice(0, -1) : bare;
|
|
119
|
+
if (trimmed === base) {
|
|
120
|
+
try {
|
|
121
|
+
return !fs.statSync(path.join(dir, trimmed)).isFile();
|
|
122
|
+
} catch {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const full = path.resolve(dir, name);
|
|
127
|
+
if (full === path.resolve(dir)) return true;
|
|
128
|
+
if (path.basename(full) !== base) return false;
|
|
129
|
+
try {
|
|
130
|
+
return fs.realpathSync(full) === fs.realpathSync(dir);
|
|
131
|
+
} catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const schedule = (state, timerKey, fire) => {
|
|
137
|
+
if (state.closed || !fire) return;
|
|
138
|
+
if (state[timerKey] !== null) clearTimeout(state[timerKey]);
|
|
139
|
+
state[timerKey] = setTimeout(() => {
|
|
140
|
+
state[timerKey] = null;
|
|
80
141
|
if (state.closed) return;
|
|
81
|
-
|
|
142
|
+
fire();
|
|
82
143
|
}, state.debounceMs);
|
|
144
|
+
const timer = state[timerKey];
|
|
145
|
+
if (timer && typeof timer.unref === 'function') timer.unref();
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const REVIEW_HOLD_MS = 200;
|
|
149
|
+
|
|
150
|
+
const reviewBurst = (state) => {
|
|
151
|
+
const seen = state.reviewSeenAt;
|
|
152
|
+
if (!seen) return false;
|
|
153
|
+
const hold = Math.max(state.debounceMs * 2, REVIEW_HOLD_MS);
|
|
154
|
+
return Date.now() - seen < hold;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const scheduleChange = (state) => {
|
|
158
|
+
schedule(state, 'timer', () => {
|
|
159
|
+
if (reviewBurst(state)) return;
|
|
160
|
+
if (state.onChange) state.onChange();
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const scheduleUnnamed = (state) => {
|
|
165
|
+
if (reviewBurst(state)) return;
|
|
166
|
+
schedule(state, 'looseTimer', () => {
|
|
167
|
+
if (reviewBurst(state)) return;
|
|
168
|
+
scheduleChange(state);
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const dropChange = (state) => {
|
|
173
|
+
if (state.timer !== null) clearTimeout(state.timer);
|
|
174
|
+
if (state.looseTimer !== null) clearTimeout(state.looseTimer);
|
|
175
|
+
state.timer = null;
|
|
176
|
+
state.looseTimer = null;
|
|
83
177
|
};
|
|
84
178
|
|
|
85
179
|
const handleWatchEvent = (state, dir, eventType, filename) => {
|
|
86
180
|
if (state.closed) return;
|
|
87
181
|
const name = filename ? `${filename}` : '';
|
|
88
182
|
const rel = name ? path.relative(state.root, path.join(dir, name)) : '';
|
|
183
|
+
if (isReviewEvent(state.root, dir, name)) {
|
|
184
|
+
state.reviewSeenAt = Date.now();
|
|
185
|
+
dropChange(state);
|
|
186
|
+
return void schedule(state, 'reviewTimer', state.onReview);
|
|
187
|
+
}
|
|
89
188
|
if (rel && ignoredRel(rel)) return;
|
|
189
|
+
if (reviewBurst(state)) return;
|
|
190
|
+
if (!name) return void scheduleUnnamed(state);
|
|
191
|
+
if (isOwnDirEvent(dir, name)) return;
|
|
90
192
|
if (eventType === 'rename' && name) {
|
|
91
193
|
state.watchNewDir(path.join(dir, name));
|
|
92
194
|
}
|
|
93
|
-
|
|
195
|
+
scheduleChange(state);
|
|
94
196
|
};
|
|
95
197
|
|
|
96
198
|
const watchOne = (state, target, extra = {}) => {
|
|
@@ -100,9 +202,9 @@ const watchOne = (state, target, extra = {}) => {
|
|
|
100
202
|
const onEvent = (eventType, filename) => {
|
|
101
203
|
handleWatchEvent(state, target, eventType, filename);
|
|
102
204
|
};
|
|
103
|
-
const watcher =
|
|
205
|
+
const watcher = fs.watch(
|
|
104
206
|
target,
|
|
105
|
-
{ persistent:
|
|
207
|
+
{ persistent: false, encoding: 'utf8', ...extra },
|
|
106
208
|
onEvent,
|
|
107
209
|
);
|
|
108
210
|
if (typeof watcher.on === 'function') {
|
|
@@ -120,7 +222,7 @@ const watchTree = (state, dir) => {
|
|
|
120
222
|
if (state.recursive) return;
|
|
121
223
|
let entries;
|
|
122
224
|
try {
|
|
123
|
-
entries =
|
|
225
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
124
226
|
} catch {
|
|
125
227
|
return;
|
|
126
228
|
}
|
|
@@ -135,11 +237,12 @@ const maybeWatchNewDir = (state, full) => {
|
|
|
135
237
|
if (state.recursive) return;
|
|
136
238
|
let stat;
|
|
137
239
|
try {
|
|
138
|
-
stat =
|
|
240
|
+
stat = fs.statSync(full);
|
|
139
241
|
} catch {
|
|
140
242
|
return;
|
|
141
243
|
}
|
|
142
244
|
if (!stat.isDirectory()) return;
|
|
245
|
+
if (path.basename(full) === '.review') return void watchOne(state, full);
|
|
143
246
|
if (skipDirName(path.basename(full))) return;
|
|
144
247
|
watchTree(state, full);
|
|
145
248
|
};
|
|
@@ -148,15 +251,11 @@ const watchScope = (state, spec) => {
|
|
|
148
251
|
const target = path.resolve(state.root, spec);
|
|
149
252
|
let stat;
|
|
150
253
|
try {
|
|
151
|
-
stat =
|
|
254
|
+
stat = fs.statSync(target);
|
|
152
255
|
} catch {
|
|
153
|
-
watchOne(state, path.dirname(target));
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
if (stat.isDirectory()) {
|
|
157
|
-
watchTree(state, target);
|
|
158
|
-
return;
|
|
256
|
+
return void watchOne(state, path.dirname(target));
|
|
159
257
|
}
|
|
258
|
+
if (stat.isDirectory()) return void watchTree(state, target);
|
|
160
259
|
watchOne(state, path.dirname(target));
|
|
161
260
|
};
|
|
162
261
|
|
|
@@ -168,8 +267,12 @@ const watchGit = (state) => {
|
|
|
168
267
|
|
|
169
268
|
const stopState = (state) => {
|
|
170
269
|
state.closed = true;
|
|
171
|
-
if (state.timer !== null)
|
|
270
|
+
if (state.timer !== null) clearTimeout(state.timer);
|
|
271
|
+
if (state.reviewTimer !== null) clearTimeout(state.reviewTimer);
|
|
272
|
+
if (state.looseTimer !== null) clearTimeout(state.looseTimer);
|
|
172
273
|
state.timer = null;
|
|
274
|
+
state.reviewTimer = null;
|
|
275
|
+
state.looseTimer = null;
|
|
173
276
|
while (state.watchers.length) closeWatcher(state.watchers.pop());
|
|
174
277
|
state.watched.clear();
|
|
175
278
|
};
|
|
@@ -186,24 +289,24 @@ const createDiskWatcher = (options) => {
|
|
|
186
289
|
const recursive = options.recursive ?? canWatchRecursive();
|
|
187
290
|
const state = {
|
|
188
291
|
root: path.resolve(options.root),
|
|
189
|
-
watchFn: options.watch ?? fs.watch,
|
|
190
|
-
readdirSync: options.readdirSync ?? fs.readdirSync,
|
|
191
|
-
statSync: options.statSync ?? fs.statSync,
|
|
192
|
-
setTimeoutFn: options.setTimeout ?? setTimeout,
|
|
193
|
-
clearTimeoutFn: options.clearTimeout ?? clearTimeout,
|
|
194
292
|
debounceMs: options.debounceMs ?? DEBOUNCE_MS,
|
|
195
293
|
onChange: options.onChange,
|
|
196
294
|
recursive,
|
|
197
295
|
dirWatchOpts: recursive ? { recursive: true } : {},
|
|
198
296
|
watchers: [],
|
|
199
297
|
watched: new Set(),
|
|
298
|
+
onReview: options.onReview,
|
|
200
299
|
timer: null,
|
|
300
|
+
reviewTimer: null,
|
|
301
|
+
looseTimer: null,
|
|
302
|
+
reviewSeenAt: 0,
|
|
201
303
|
closed: false,
|
|
202
304
|
watchNewDir: (full) => maybeWatchNewDir(state, full),
|
|
203
305
|
};
|
|
204
306
|
const scopes = options.paths && options.paths.length ? options.paths : ['.'];
|
|
205
307
|
for (const spec of scopes) watchScope(state, spec);
|
|
206
308
|
watchGit(state);
|
|
309
|
+
watchOne(state, path.resolve(state.root, '.review'));
|
|
207
310
|
return {
|
|
208
311
|
close: () => stopState(state),
|
|
209
312
|
watchFiles: (rels) => watchFiles(state, rels),
|
|
@@ -215,5 +318,6 @@ module.exports = {
|
|
|
215
318
|
POLL_MS,
|
|
216
319
|
canWatchRecursive,
|
|
217
320
|
ignoredRel,
|
|
321
|
+
isOwnDirEvent,
|
|
218
322
|
createDiskWatcher,
|
|
219
323
|
};
|