reslop 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.md +27 -38
  3. package/bin/reslop.js +2 -2
  4. package/lib/ansi.js +6 -2
  5. package/lib/api.js +2 -2
  6. package/lib/capabilities.js +10 -14
  7. package/lib/cli.js +28 -47
  8. package/lib/clipboard.js +1 -1
  9. package/lib/deps-diff.js +712 -0
  10. package/lib/deps.js +556 -32
  11. package/lib/detect.js +1 -7
  12. package/lib/diff/align.js +7 -10
  13. package/lib/diff/diff.js +53 -0
  14. package/lib/diff/display.js +5 -4
  15. package/lib/diff/inline.js +26 -21
  16. package/lib/diff/items.js +6 -4
  17. package/lib/diff/patch.js +1 -3
  18. package/lib/diff/unit.js +392 -0
  19. package/lib/files.js +34 -8
  20. package/lib/{git/branches.js → git-branches.js} +103 -10
  21. package/lib/{git/dependencies.js → git-deps.js} +111 -38
  22. package/lib/git.js +592 -65
  23. package/lib/github.js +28 -197
  24. package/lib/gitlab.js +24 -195
  25. package/lib/highlight/bash.js +1 -3
  26. package/lib/highlight/css.js +1 -3
  27. package/lib/highlight/js.js +11 -14
  28. package/lib/highlight.js +6 -9
  29. package/lib/keys.js +22 -9
  30. package/lib/manifest.js +330 -0
  31. package/lib/remote.js +374 -0
  32. package/lib/render/branches.js +34 -66
  33. package/lib/render/chrome.js +57 -22
  34. package/lib/render/commits.js +85 -0
  35. package/lib/render/diff.js +16 -16
  36. package/lib/render/files.js +29 -37
  37. package/lib/render/frame.js +29 -13
  38. package/lib/render/notes.js +4 -3
  39. package/lib/render/primitives.js +46 -1
  40. package/lib/{render.js → render/render.js} +6 -7
  41. package/lib/render/unit.js +41 -0
  42. package/lib/review.js +750 -14
  43. package/lib/select.js +5 -10
  44. package/lib/session/accessors.js +9 -1
  45. package/lib/session/actions.js +93 -36
  46. package/lib/session/branches.js +130 -144
  47. package/lib/session/change-actions.js +206 -124
  48. package/lib/session/commit.js +217 -62
  49. package/lib/session/compose.js +158 -13
  50. package/lib/session/dispatch.js +109 -55
  51. package/lib/session/files-pane.js +145 -142
  52. package/lib/session/items.js +131 -34
  53. package/lib/session/lifecycle.js +82 -42
  54. package/lib/session/load.js +19 -33
  55. package/lib/session/navigation.js +15 -3
  56. package/lib/session/ops.js +28 -41
  57. package/lib/session/pointer.js +58 -54
  58. package/lib/session/review.js +23 -37
  59. package/lib/session/terminal.js +6 -12
  60. package/lib/session/todos.js +8 -12
  61. package/lib/session/unit.js +208 -0
  62. package/lib/session/update.js +33 -46
  63. package/lib/session/view.js +82 -11
  64. package/lib/session/watch.js +11 -19
  65. package/lib/session.js +29 -226
  66. package/lib/source.js +3 -3
  67. package/lib/update.js +40 -75
  68. package/lib/{git/proc.js → utilities.js} +75 -11
  69. package/lib/wrap.js +1 -3
  70. package/package.json +2 -2
  71. package/lib/deps/changes.js +0 -331
  72. package/lib/deps/items.js +0 -131
  73. package/lib/deps/manifest.js +0 -145
  74. package/lib/deps/merge.js +0 -120
  75. package/lib/deps/proposals.js +0 -164
  76. package/lib/deps/reports.js +0 -180
  77. package/lib/deps/rows.js +0 -177
  78. package/lib/deps/scan.js +0 -138
  79. package/lib/deps/usage.js +0 -68
  80. package/lib/deps/versions.js +0 -119
  81. package/lib/diff.js +0 -33
  82. package/lib/git/changes.js +0 -164
  83. package/lib/git/read.js +0 -253
  84. package/lib/git/run.js +0 -100
  85. package/lib/npm/commands.js +0 -100
  86. package/lib/remote/changes.js +0 -37
  87. package/lib/remote/headers.js +0 -36
  88. package/lib/remote/http.js +0 -122
  89. package/lib/remote/notes.js +0 -94
  90. package/lib/review/format.js +0 -434
  91. package/lib/review/storage.js +0 -150
  92. package/lib/review/store.js +0 -178
  93. package/lib/review/templates.js +0 -49
  94. package/lib/session/origins.js +0 -7
  95. package/lib/sys.js +0 -84
@@ -0,0 +1,208 @@
1
+ 'use strict';
2
+
3
+ const files = require('../files.js');
4
+ const { listPath, isTodosEntry } = files;
5
+ const diff = require('../diff/diff.js');
6
+ const { unitLines, itemsForPath, blockLineIndex } = diff;
7
+ const { hunkLineRange, sameHunk, hunkKey } = diff;
8
+ const { fileOffset, splitEditor, mergeEditRows, fileLineIndex } = diff;
9
+
10
+ const fileText = (ui, rel) => {
11
+ if (!rel) return '';
12
+ const repo = ui.repo;
13
+ if (repo && typeof repo.fileText === 'function') {
14
+ return repo.fileText(ui.top, rel, ui.rev);
15
+ }
16
+ return '';
17
+ };
18
+
19
+ const pathItems = (ui, rel) => itemsForPath(ui.items ?? [], rel);
20
+
21
+ const linesFor = (
22
+ ui,
23
+ rel,
24
+ overlay = ui.composer.liveFileText(),
25
+ base = null,
26
+ ) => {
27
+ const text = overlay === null ? fileText(ui, rel) : overlay;
28
+ const group = pathItems(ui, rel);
29
+ if (overlay !== null && base && base.length) {
30
+ return mergeEditRows(base, splitEditor(text));
31
+ }
32
+ return unitLines(text, group);
33
+ };
34
+
35
+ const BODY_SHIFT = 1;
36
+
37
+ const clampUnitLine = (nav, length) => {
38
+ if (!length) {
39
+ nav.unitLine = 0;
40
+ return;
41
+ }
42
+ if (nav.unitLine < 0) nav.unitLine = 0;
43
+ if (nav.unitLine >= length) nav.unitLine = length - 1;
44
+ };
45
+
46
+ const CONTEXT = 1;
47
+
48
+ const frameBodyH = (frame, ui) => {
49
+ if (frame && frame.bodyH) return frame.bodyH;
50
+ const size = ui?.lastSize ?? ui?.getSize?.();
51
+ const height = size && size.height ? size.height : 24;
52
+ return Math.max(1, height - 4);
53
+ };
54
+
55
+ const ensureRangeVisible = (nav, firstLine, lastLine, frame, ui) => {
56
+ const bodyH = frameBodyH(frame, ui);
57
+ const diffFirst = firstLine + BODY_SHIFT;
58
+ const diffLast = lastLine + BODY_SHIFT;
59
+ const diffSpan = diffLast - diffFirst + 1;
60
+ let first = diffFirst;
61
+ let last = diffLast;
62
+ if (diffSpan <= bodyH) {
63
+ const padFirst = Math.max(0, firstLine - CONTEXT) + BODY_SHIFT;
64
+ const padLast = lastLine + CONTEXT + BODY_SHIFT;
65
+ if (padLast - padFirst + 1 <= bodyH) {
66
+ first = padFirst;
67
+ last = padLast;
68
+ }
69
+ }
70
+ const span = last - first + 1;
71
+ let scroll = nav.scroll ?? 0;
72
+ if (span > bodyH) {
73
+ scroll = first;
74
+ } else {
75
+ if (first < scroll) scroll = first;
76
+ const viewLast = scroll + bodyH - 1;
77
+ if (last > viewLast) scroll = last - bodyH + 1;
78
+ }
79
+ nav.scroll = Math.max(0, scroll);
80
+ };
81
+
82
+ const ensureVisible = (nav, length, frame) => {
83
+ clampUnitLine(nav, length);
84
+ ensureRangeVisible(nav, nav.unitLine, nav.unitLine, frame);
85
+ };
86
+
87
+ const followFileCursor = (nav, editor, frame, rows) => {
88
+ if (!editor) return;
89
+ const fileLine = editor.linePos().line;
90
+ if (rows && rows.length) {
91
+ nav.unitLine = fileLineIndex(rows, fileLine);
92
+ ensureVisible(nav, rows.length, frame);
93
+ return;
94
+ }
95
+ const length = `${editor.text}`.split('\n').length;
96
+ nav.unitLine = fileLine;
97
+ ensureVisible(nav, length, frame);
98
+ };
99
+
100
+ const jumpToItem = (ui, item) => {
101
+ const nav = ui.nav;
102
+ const rel = nav.reviewPath;
103
+ const lines = linesFor(ui, rel);
104
+ const items = ui.items ?? [];
105
+ const idx = items.indexOf(item);
106
+ if (idx >= 0) nav.index = idx;
107
+ const range = hunkLineRange(lines, item);
108
+ nav.unitLine = range.start;
109
+ ensureRangeVisible(nav, range.start, range.end, ui.lastFrame, ui);
110
+ };
111
+
112
+ const viewLine = (nav, length) => {
113
+ if (!length) return 0;
114
+ const top = Math.max(0, (nav.scroll ?? 0) - 1);
115
+ return Math.min(top, length - 1);
116
+ };
117
+
118
+ const moveUnitBlock = (ui, step) => {
119
+ const nav = ui.nav;
120
+ const rel = nav.reviewPath;
121
+ if (!rel) return;
122
+ const items = ui.items ?? [];
123
+ const indexes = [];
124
+ const seen = new Set();
125
+ for (let i = 0; i < items.length; i++) {
126
+ if (listPath(items[i]) !== rel) continue;
127
+ const key = hunkKey(items[i]);
128
+ if (seen.has(key)) continue;
129
+ seen.add(key);
130
+ indexes.push(i);
131
+ }
132
+ if (!indexes.length) {
133
+ ui.status = 'not a diff block';
134
+ return;
135
+ }
136
+ let at = -1;
137
+ const current = items[nav.index];
138
+ for (let i = 0; i < indexes.length; i++) {
139
+ if (!sameHunk(items[indexes[i]], current)) continue;
140
+ at = i;
141
+ break;
142
+ }
143
+ if (at < 0) at = step > 0 ? -1 : 0;
144
+ const next = at + step;
145
+ if (next < 0 || next >= indexes.length) return;
146
+ const item = items[indexes[next]];
147
+ jumpToItem(ui, item);
148
+ ui.status = '';
149
+ nav.clearSelection();
150
+ };
151
+
152
+ const openUnitFile = (ui, entry) => {
153
+ if (!entry || isTodosEntry(entry)) return;
154
+ const nav = ui.nav;
155
+ nav.reviewPath = entry.path;
156
+ nav.todoOpen = false;
157
+ nav.pane = 'unit';
158
+ nav.scroll = 0;
159
+ nav.unitLine = 0;
160
+ nav.clearSelection();
161
+ const items = ui.items ?? [];
162
+ const group = pathItems(ui, entry.path);
163
+ if (group.length) {
164
+ const want = items[entry.openIndex] ?? items[entry.firstIndex] ?? group[0];
165
+ const item = group.includes(want) ? want : group[0];
166
+ jumpToItem(ui, item);
167
+ } else {
168
+ nav.unitLine = 0;
169
+ }
170
+ ui.status = '';
171
+ };
172
+
173
+ const setFileScope = (ui, next) => {
174
+ if (ui.nav.pane !== 'files') return;
175
+ if (ui.nav.fileScope === next) return;
176
+ ui.nav.fileScope = next;
177
+ ui.nav.restoreFileCursor(ui.fileList());
178
+ ui.status = next === 'file' ? 'file scope' : 'diff mode';
179
+ };
180
+
181
+ const editCursor = (ui) => {
182
+ const rel = ui.nav.reviewPath;
183
+ const text = fileText(ui, rel);
184
+ const group = pathItems(ui, rel);
185
+ const lines = unitLines(text, group);
186
+ const items = ui.items ?? [];
187
+ const item = items[ui.nav.index];
188
+ const atBlock = item && listPath(item) === rel;
189
+ const at = atBlock
190
+ ? blockLineIndex(lines, item)
191
+ : viewLine(ui.nav, lines.length);
192
+ return {
193
+ text,
194
+ cursor: fileOffset(lines, at),
195
+ rows: lines,
196
+ };
197
+ };
198
+
199
+ module.exports = {
200
+ fileText,
201
+ linesFor,
202
+ moveUnitBlock,
203
+ openUnitFile,
204
+ setFileScope,
205
+ editCursor,
206
+ ensureVisible,
207
+ followFileCursor,
208
+ };
@@ -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.options.updateOpts();
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.options.didLoad()) return;
23
- if (api.options.getMode() !== 'review') return;
24
- api.options.setMode('confirmUpdate');
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.options.setStatus('updating reslop');
29
- api.options.startInstallProgress();
30
- api.options.paint();
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.options.stopInstallProgress();
39
- if (api.options.isDone() || !api.options.uiOpen()) return;
40
- api.options.setStatus(ok ? 'updated' : 'update failed');
41
- api.options.paint();
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.options.setMode('review');
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.options.setMode('review');
55
+ api.ui.mode = 'review';
53
56
  api.state.offer = false;
54
- api.options.setStatus('');
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.options.isDone()) return;
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.options.paint();
79
+ api.ui.paint();
77
80
  };
78
81
 
79
82
  const openUpdate = (api) => {
80
- if (api.options.updateOpts() === false) return Promise.resolve();
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 = (options) => {
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 = { options, state };
95
- return {
96
- get offer() {
97
- return state.offer;
98
- },
99
- set offer(value) {
100
- state.offer = value;
101
- },
102
- get from() {
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
- get checkPromise() {
109
- return state.checkPromise;
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 };
@@ -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 SOURCE_KIND = {
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
- labeled.unshift(todosEntry(notes));
53
- return labeled;
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
- const { composer, nav, ui, gitBranches, updater } = api;
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 && SOURCE_KIND[change.source]) || '';
149
+ const sourceKind = (change && change.source) || '';
84
150
  const notes = noteCounts(api.review.store);
85
151
  return {
86
152
  item: ui.current(),
@@ -94,12 +160,17 @@ 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,
172
+ commits: commits.commits,
173
+ commitCursor: nav.commitCursor,
103
174
  repoName: repoNameOf(api),
104
175
  revShort: ui.revShort || '',
105
176
  sourceLabel: ui.sourceLabel || '',
@@ -115,7 +186,7 @@ const view = (api) => {
115
186
  templateIndex: composer.clampedTemplateIndex(templates),
116
187
  updateFrom: updater.from,
117
188
  updateTo: updater.to,
118
- dropName: gitBranches.dropName,
189
+ dropName: gitBranches.dropName || commits.dropName,
119
190
  };
120
191
  };
121
192
 
@@ -126,4 +197,4 @@ const createView = (api) => ({
126
197
  view: () => view(api),
127
198
  });
128
199
 
129
- module.exports = { SOURCE_KIND, relabelStatus, createView };
200
+ module.exports = { relabelStatus, createView };
@@ -74,12 +74,13 @@ const closeWatcher = (watcher) => {
74
74
 
75
75
  const schedule = (state) => {
76
76
  if (state.closed) return;
77
- if (state.timer !== null) state.clearTimeoutFn(state.timer);
78
- state.timer = state.setTimeoutFn(() => {
77
+ if (state.timer !== null) clearTimeout(state.timer);
78
+ state.timer = setTimeout(() => {
79
79
  state.timer = null;
80
80
  if (state.closed) return;
81
81
  state.onChange();
82
82
  }, state.debounceMs);
83
+ if (typeof state.timer.unref === 'function') state.timer.unref();
83
84
  };
84
85
 
85
86
  const handleWatchEvent = (state, dir, eventType, filename) => {
@@ -100,9 +101,9 @@ const watchOne = (state, target, extra = {}) => {
100
101
  const onEvent = (eventType, filename) => {
101
102
  handleWatchEvent(state, target, eventType, filename);
102
103
  };
103
- const watcher = state.watchFn(
104
+ const watcher = fs.watch(
104
105
  target,
105
- { persistent: true, encoding: 'utf8', ...extra },
106
+ { persistent: false, encoding: 'utf8', ...extra },
106
107
  onEvent,
107
108
  );
108
109
  if (typeof watcher.on === 'function') {
@@ -120,7 +121,7 @@ const watchTree = (state, dir) => {
120
121
  if (state.recursive) return;
121
122
  let entries;
122
123
  try {
123
- entries = state.readdirSync(dir, { withFileTypes: true });
124
+ entries = fs.readdirSync(dir, { withFileTypes: true });
124
125
  } catch {
125
126
  return;
126
127
  }
@@ -135,7 +136,7 @@ const maybeWatchNewDir = (state, full) => {
135
136
  if (state.recursive) return;
136
137
  let stat;
137
138
  try {
138
- stat = state.statSync(full);
139
+ stat = fs.statSync(full);
139
140
  } catch {
140
141
  return;
141
142
  }
@@ -148,15 +149,11 @@ const watchScope = (state, spec) => {
148
149
  const target = path.resolve(state.root, spec);
149
150
  let stat;
150
151
  try {
151
- stat = state.statSync(target);
152
+ stat = fs.statSync(target);
152
153
  } catch {
153
- watchOne(state, path.dirname(target));
154
- return;
155
- }
156
- if (stat.isDirectory()) {
157
- watchTree(state, target);
158
- return;
154
+ return void watchOne(state, path.dirname(target));
159
155
  }
156
+ if (stat.isDirectory()) return void watchTree(state, target);
160
157
  watchOne(state, path.dirname(target));
161
158
  };
162
159
 
@@ -168,7 +165,7 @@ const watchGit = (state) => {
168
165
 
169
166
  const stopState = (state) => {
170
167
  state.closed = true;
171
- if (state.timer !== null) state.clearTimeoutFn(state.timer);
168
+ if (state.timer !== null) clearTimeout(state.timer);
172
169
  state.timer = null;
173
170
  while (state.watchers.length) closeWatcher(state.watchers.pop());
174
171
  state.watched.clear();
@@ -186,11 +183,6 @@ const createDiskWatcher = (options) => {
186
183
  const recursive = options.recursive ?? canWatchRecursive();
187
184
  const state = {
188
185
  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
186
  debounceMs: options.debounceMs ?? DEBOUNCE_MS,
195
187
  onChange: options.onChange,
196
188
  recursive,