mouaif 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/bin/mouaif.js +281 -0
- package/frontend/dist/assets/AgentFilePicker-CcKLJorU.js +1 -0
- package/frontend/dist/assets/CliModal-Hs5phmNZ.js +7 -0
- package/frontend/dist/assets/DictationPage-BI23lp42.js +2 -0
- package/frontend/dist/assets/FileEditor-DDl31c6d.js +2 -0
- package/frontend/dist/assets/GitModal-3EC_gpJ5.js +2 -0
- package/frontend/dist/assets/Inspector-Ba3R1w04.js +73 -0
- package/frontend/dist/assets/SettingsAbout-bvZGDEDw.js +1 -0
- package/frontend/dist/assets/SettingsActions-Dk6WX9jv.js +1 -0
- package/frontend/dist/assets/SettingsAgents-BNV0MgDB.js +1 -0
- package/frontend/dist/assets/SettingsDefaults-DbMmQbzc.js +1 -0
- package/frontend/dist/assets/SettingsHiddenContent-BZ2sloH1.js +1 -0
- package/frontend/dist/assets/SettingsMcp-DOrfbQd1.js +1 -0
- package/frontend/dist/assets/SettingsMcpEdit-BGMQ2CWC.js +3 -0
- package/frontend/dist/assets/SettingsMcpRegistry-BywXee_A.js +1 -0
- package/frontend/dist/assets/SettingsNotifications-B0LEs11a.js +1 -0
- package/frontend/dist/assets/SettingsPricing-BAg33iVF.js +1 -0
- package/frontend/dist/assets/SettingsProject-DNrKhCcZ.js +14 -0
- package/frontend/dist/assets/SettingsProjects-IqkBfDcm.js +1 -0
- package/frontend/dist/assets/SettingsPrompts-BgeiASuk.js +1 -0
- package/frontend/dist/assets/SettingsProviders-k0xJN0IK.js +1 -0
- package/frontend/dist/assets/SettingsTags-B5kjFdQi.js +1 -0
- package/frontend/dist/assets/agentNavigation-BiiCpFz5.js +1 -0
- package/frontend/dist/assets/codemirror-Bp6CUUFk.js +30 -0
- package/frontend/dist/assets/index-BGvI4n0T.js +61 -0
- package/frontend/dist/assets/index-Bgg1gnDf.css +1 -0
- package/frontend/dist/assets/index-C1sQFIC-.css +1 -0
- package/frontend/dist/assets/index-CANPYzQg.css +1 -0
- package/frontend/dist/assets/index-Crn1LdzK.css +1 -0
- package/frontend/dist/assets/index-FbCWDPiB.css +1 -0
- package/frontend/dist/assets/projectQS-D1cSZ7Gr.js +1 -0
- package/frontend/dist/assets/virtual-list-6H9b4K51.js +1 -0
- package/frontend/dist/icons/favicon-32.png +0 -0
- package/frontend/dist/icons/icon-180-apple.png +0 -0
- package/frontend/dist/icons/icon-192.png +0 -0
- package/frontend/dist/icons/icon-512.png +0 -0
- package/frontend/dist/icons/icon-maskable-512.png +0 -0
- package/frontend/dist/index.html +83 -0
- package/frontend/dist/manifest.webmanifest +33 -0
- package/frontend/dist/sw.js +482 -0
- package/package.json +98 -0
- package/scripts/patch-zimmerframe.js +58 -0
- package/src/access-auth.js +515 -0
- package/src/agentFeatures.js +294 -0
- package/src/agentFiles.js +164 -0
- package/src/agentSkills.js +147 -0
- package/src/agents.js +230 -0
- package/src/ai-chat.js +21 -0
- package/src/ai-endpoints.js +1880 -0
- package/src/ai-stream.js +2048 -0
- package/src/ai.js +68 -0
- package/src/auth.js +391 -0
- package/src/chatdb.js +816 -0
- package/src/chats.js +275 -0
- package/src/custom-actions.js +65 -0
- package/src/files.js +431 -0
- package/src/hideFileContent.js +327 -0
- package/src/http-server.js +535 -0
- package/src/index.js +15 -0
- package/src/inspector.js +731 -0
- package/src/inspectorProfiles.js +503 -0
- package/src/live-chat.js +107 -0
- package/src/mcp.js +1517 -0
- package/src/messages.js +238 -0
- package/src/modelList.js +137 -0
- package/src/notifications.js +52 -0
- package/src/oauth-anthropic.js +280 -0
- package/src/oauth-github-copilot.js +417 -0
- package/src/oauth-mcp.js +216 -0
- package/src/oauth-openrouter.js +285 -0
- package/src/package-version.js +20 -0
- package/src/projects.js +285 -0
- package/src/promptProfiles.js +256 -0
- package/src/prompts.js +384 -0
- package/src/providerShapes.js +44 -0
- package/src/providers/base.js +41 -0
- package/src/providers/index.js +25 -0
- package/src/push.js +315 -0
- package/src/qr.js +192 -0
- package/src/restart.js +47 -0
- package/src/server-handlers-access.js +306 -0
- package/src/server-handlers-actions.js +100 -0
- package/src/server-handlers-ai.js +248 -0
- package/src/server-handlers-auth.js +273 -0
- package/src/server-handlers-chats.js +1436 -0
- package/src/server-handlers-git.js +467 -0
- package/src/server-handlers-mcp-oauth.js +56 -0
- package/src/server-handlers-misc.js +783 -0
- package/src/server-handlers-projects.js +289 -0
- package/src/server-handlers-prompts.js +259 -0
- package/src/server-handlers-push.js +102 -0
- package/src/server-handlers-settings.js +406 -0
- package/src/server-handlers-tools.js +654 -0
- package/src/server-handlers-transcribe.js +399 -0
- package/src/server-shared.js +780 -0
- package/src/server-web-static.js +191 -0
- package/src/settings.js +898 -0
- package/src/statusBar.js +541 -0
- package/src/tags.js +414 -0
- package/src/toolFeedback.js +225 -0
- package/src/tools/ask.js +154 -0
- package/src/tools/authorization.js +932 -0
- package/src/tools/files.js +1150 -0
- package/src/tools/progress.js +71 -0
- package/src/tools/restart.js +32 -0
- package/src/tools/searchEngine.js +957 -0
- package/src/tools/shell.js +341 -0
- package/src/tools/subagent.js +47 -0
- package/src/tools/task.js +234 -0
- package/src/tools/webpreview.js +448 -0
- package/src/trace.js +103 -0
- package/src/transcribe.js +683 -0
- package/src/usage.js +389 -0
- package/src/util.js +151 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Per-project "hide file content" (redaction) rules.
|
|
3
|
+
//
|
|
4
|
+
// The user can mark line ranges of a specific source file that the agent
|
|
5
|
+
// file tools should not reveal, and can also select arbitrary text to hide
|
|
6
|
+
// by character range. This module owns the read side: it loads the rules
|
|
7
|
+
// from the project settings (<projectDir>/.mouaif.json or the DB-backed
|
|
8
|
+
// project row, via src/settings.js) and tells the file-tool runners which
|
|
9
|
+
// lines (and which characters within a line) of which file are hidden.
|
|
10
|
+
//
|
|
11
|
+
// The rules are stored on the project object under `hideFileContent`:
|
|
12
|
+
// [
|
|
13
|
+
// { path: 'src/index.js',
|
|
14
|
+
// ranges: [ { start: 12, end: 20 }, { start: 45, end: 45 } ], // whole lines
|
|
15
|
+
// chars: [ { startLine: 3, endLine: 3, startCol: 5, endCol: 11 } ] } // character spans
|
|
16
|
+
// ]
|
|
17
|
+
// `path` is a POSIX-relative path from the project root (the same value
|
|
18
|
+
// the file picker reports). `ranges` are 1-indexed, inclusive line spans;
|
|
19
|
+
// `chars` are 1-indexed, inclusive line & column spans. Both are optional
|
|
20
|
+
// per rule (older saved rules only have `ranges`), so existing data stays
|
|
21
|
+
// valid. A character span hides the selected characters on its boundary
|
|
22
|
+
// lines and the full lines between them.
|
|
23
|
+
//
|
|
24
|
+
// Scope: only the tools that *reveal* content are redacted — `read_file`
|
|
25
|
+
// and `search_files`. `list_files` lists paths (no content), and
|
|
26
|
+
// `write_file` / `edit_file` modify the on-disk file rather than exposing
|
|
27
|
+
// it, so they are not changed. The user-facing authorization preview card
|
|
28
|
+
// is also left intact: it is shown to the project owner who configured the
|
|
29
|
+
// redaction, not to the model.
|
|
30
|
+
//
|
|
31
|
+
// Everything here is best-effort and never throws: a missing project, a
|
|
32
|
+
// malformed rule, or a file that no longer exists must not crash a tool
|
|
33
|
+
// call. If the rules cannot be read, the tools behave exactly as if no
|
|
34
|
+
// redaction were configured.
|
|
35
|
+
const settings = require('./settings.js');
|
|
36
|
+
// Marker that replaces the body of a hidden line. Kept short so it does
|
|
37
|
+
// not dominate the model context, but distinct enough that the model can
|
|
38
|
+
// tell a line was intentionally withheld.
|
|
39
|
+
const REDACT_MARKER = '[hidden]';
|
|
40
|
+
// Return the stored redaction entries for a project. Always returns an
|
|
41
|
+
// array; any read problem yields [] so the callers can no-op.
|
|
42
|
+
function getEntries(projectDir) {
|
|
43
|
+
if (!projectDir || typeof projectDir !== 'string') return [];
|
|
44
|
+
let project;
|
|
45
|
+
try { project = settings.getProject(projectDir); }
|
|
46
|
+
catch { return []; }
|
|
47
|
+
const entries = project && project.hideFileContent;
|
|
48
|
+
return Array.isArray(entries) ? entries : [];
|
|
49
|
+
}
|
|
50
|
+
// Normalize a project file path to a POSIX-relative path with no leading
|
|
51
|
+
// './'. Mirrors src/tools/files.js toRelPath's output so rule paths match
|
|
52
|
+
// the rel paths the tools compute. Returns '' for anything non-string so a
|
|
53
|
+
// malformed rule can never match and hide the wrong file.
|
|
54
|
+
function normalizePath(p) {
|
|
55
|
+
if (typeof p !== 'string' || !p.trim()) return '';
|
|
56
|
+
// Strip a leading `./` BEFORE the slash collapsing, so a rule the user
|
|
57
|
+
// saved as `./src/a.js` (the editor reports paths with the prefix in some
|
|
58
|
+
// flows) lands on the same value src/tools/files.js toRelPath computes.
|
|
59
|
+
// Otherwise the rule is silently dead: it never matches the file it was
|
|
60
|
+
// written to hide. This was the previously-broken `while (s.startsWith`
|
|
61
|
+
// line, which ran before `s` was declared.
|
|
62
|
+
let s = p.trim().replace(/\\/g, '/');
|
|
63
|
+
while (s.startsWith('./')) s = s.slice(2);
|
|
64
|
+
s = s.replace(/\/+/g, '/').replace(/\/+$/, '');
|
|
65
|
+
return s;
|
|
66
|
+
}
|
|
67
|
+
// Sanitize a single character span to { startLine, endLine, startCol,
|
|
68
|
+
// endCol }. Columns are 1-indexed, inclusive character positions; lines
|
|
69
|
+
// are 1-indexed, inclusive. A span must be non-empty (the end position
|
|
70
|
+
// must be at or after the start) to be worth storing. Returns null for a
|
|
71
|
+
// malformed span so the caller can drop it.
|
|
72
|
+
function normalizeCharSpan(raw) {
|
|
73
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
|
|
74
|
+
const startLine = Number.isInteger(raw.startLine) ? raw.startLine : 0;
|
|
75
|
+
const endLine = Number.isInteger(raw.endLine) ? raw.endLine : 0;
|
|
76
|
+
const startCol = Number.isInteger(raw.startCol) ? raw.startCol : 0;
|
|
77
|
+
const endCol = Number.isInteger(raw.endCol) ? raw.endCol : 0;
|
|
78
|
+
// Validate line ordering first, then the column ordering for a
|
|
79
|
+
// single-line span (a multi-line span allows any column, since the
|
|
80
|
+
// middle lines are fully hidden and the boundary lines are partial).
|
|
81
|
+
if (startLine < 1 || endLine < startLine) return null;
|
|
82
|
+
if (startCol < 1 || endCol < startCol) {
|
|
83
|
+
// A multi-line span only needs its boundary columns to be at least 1.
|
|
84
|
+
if (startLine === endLine) return null;
|
|
85
|
+
if (startCol < 1 || endCol < 1) return null;
|
|
86
|
+
}
|
|
87
|
+
return { startLine, endLine, startCol, endCol };
|
|
88
|
+
}
|
|
89
|
+
// Sanitize a single rules entry to a { path, ranges, chars } shape. Invalid
|
|
90
|
+
// spans are dropped; valid spans are kept with start clamped to <= end.
|
|
91
|
+
// Returns null for a malformed entry (no path, or no spans at all) so the
|
|
92
|
+
// caller can skip it. Both `ranges` (line spans) and `chars` (character
|
|
93
|
+
// spans) are optional; a rule needs at least one of them to be worth
|
|
94
|
+
// storing.
|
|
95
|
+
function normalizeEntry(raw) {
|
|
96
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
|
|
97
|
+
const path = normalizePath(raw.path);
|
|
98
|
+
if (!path) return null;
|
|
99
|
+
const ranges = Array.isArray(raw.ranges)
|
|
100
|
+
? raw.ranges.filter((r) => r && typeof r === 'object').map((r) => {
|
|
101
|
+
const start = Number.isInteger(r.start) ? r.start : 0;
|
|
102
|
+
const end = Number.isInteger(r.end) ? r.end : 0;
|
|
103
|
+
if (start < 1 || end < start) return null;
|
|
104
|
+
return { start, end };
|
|
105
|
+
}).filter(Boolean)
|
|
106
|
+
: [];
|
|
107
|
+
const chars = Array.isArray(raw.chars)
|
|
108
|
+
? raw.chars.filter((c) => c && typeof c === 'object').map(normalizeCharSpan).filter(Boolean)
|
|
109
|
+
: [];
|
|
110
|
+
if (!ranges.length && !chars.length) return null;
|
|
111
|
+
// Keep the serialized shape clean and backward-compatible: only include
|
|
112
|
+
// `chars` when there are character spans, so a line-only rule stays
|
|
113
|
+
// `{ path, ranges }` exactly as it was before character ranges existed.
|
|
114
|
+
const entry = { path, ranges };
|
|
115
|
+
if (chars.length) entry.chars = chars;
|
|
116
|
+
return entry;
|
|
117
|
+
}
|
|
118
|
+
// Are `line` (1-indexed) and any other line within a hidden range of the
|
|
119
|
+
// given rule? `line` is a single 1-indexed line number.
|
|
120
|
+
function isLineHidden(rule, line) {
|
|
121
|
+
if (!rule || !rule.ranges || !Number.isInteger(line) || line < 1) return false;
|
|
122
|
+
return rule.ranges.some((r) => line >= r.start && line <= r.end);
|
|
123
|
+
}
|
|
124
|
+
// Apply the character spans of `rule` to a single line of text and return
|
|
125
|
+
// the redacted line (or the original). `lineNumber` is the 1-indexed line
|
|
126
|
+
// in the file; the whole-line `hidden` flag is also honoured. Character
|
|
127
|
+
// spans on a line are redacted in place: the selected characters are
|
|
128
|
+
// replaced by the marker. A single-line span hides [startCol, endCol]; a
|
|
129
|
+
// multi-line span hides from startCol on the first line, the full middle
|
|
130
|
+
// lines, and up to endCol on the last. Returns { text, redacted }.
|
|
131
|
+
function redactLine(rule, lineNumber, line, hidden) {
|
|
132
|
+
let text = line;
|
|
133
|
+
let redacted = false;
|
|
134
|
+
if (hidden) return { text: REDACT_MARKER, redacted: true };
|
|
135
|
+
const spans = rule && rule.chars ? rule.chars : [];
|
|
136
|
+
const selected = spans.filter((s) => lineNumber >= s.startLine && lineNumber <= s.endLine);
|
|
137
|
+
if (!selected.length) return { text, redacted };
|
|
138
|
+
const chars = Array.from(text);
|
|
139
|
+
const length = chars.length;
|
|
140
|
+
// Collect the absolute [start, end) character range to hide. We clamp to
|
|
141
|
+
// the line length so a stale span (line shrunk since it was saved) never
|
|
142
|
+
// throws and never hides beyond the line. Suffixes / prefixes that fall
|
|
143
|
+
// outside the file are simply not hidden.
|
|
144
|
+
const range = selected.reduce((acc, s) => {
|
|
145
|
+
let from, to;
|
|
146
|
+
if (s.startLine === s.endLine) {
|
|
147
|
+
from = s.startCol - 1;
|
|
148
|
+
to = s.endCol;
|
|
149
|
+
} else if (lineNumber === s.startLine) {
|
|
150
|
+
from = s.startCol - 1;
|
|
151
|
+
to = length;
|
|
152
|
+
} else if (lineNumber === s.endLine) {
|
|
153
|
+
from = 0;
|
|
154
|
+
to = s.endCol;
|
|
155
|
+
} else {
|
|
156
|
+
from = 0;
|
|
157
|
+
to = length;
|
|
158
|
+
}
|
|
159
|
+
if (from < 0) from = 0;
|
|
160
|
+
if (to > length) to = length;
|
|
161
|
+
if (to < from) to = from;
|
|
162
|
+
if (from < acc.from) acc.from = from;
|
|
163
|
+
if (to > acc.to) acc.to = to;
|
|
164
|
+
return acc;
|
|
165
|
+
}, { from: length, to: 0 });
|
|
166
|
+
if (range.to <= range.from) return { text, redacted };
|
|
167
|
+
const before = chars.slice(0, range.from).join('');
|
|
168
|
+
const after = chars.slice(range.to).join('');
|
|
169
|
+
text = before + REDACT_MARKER + after;
|
|
170
|
+
redacted = text !== line;
|
|
171
|
+
return { text, redacted };
|
|
172
|
+
}
|
|
173
|
+
// Replace every hidden line in `text` with the REDACT_MARKER, keeping the
|
|
174
|
+
// newline structure intact so line numbers and the total line count stay
|
|
175
|
+
// identical to the on-disk file. Whole-line ranges collapse to the marker;
|
|
176
|
+
// character spans redact the selected characters in place. Returns
|
|
177
|
+
// { text, redacted, hiddenLines }. `redacted` is true when at least one
|
|
178
|
+
// line was changed; `hiddenLines` is the count of lines that were fully
|
|
179
|
+
// hidden. A rule that no longer matches the file leaves the text
|
|
180
|
+
// untouched. `startLine` is the original 1-indexed line of the first line
|
|
181
|
+
// in a read_file slice.
|
|
182
|
+
function redactText(projectDir, relPath, text, startLine = 1) {
|
|
183
|
+
const rule = ruleForPath(projectDir, relPath);
|
|
184
|
+
if (!rule || typeof text !== 'string') {
|
|
185
|
+
return { text, redacted: false, hiddenLines: 0, rule };
|
|
186
|
+
}
|
|
187
|
+
let hiddenLines = 0;
|
|
188
|
+
let changed = false;
|
|
189
|
+
const lines = text.split('\n').map((line, idx) => {
|
|
190
|
+
const lineNumber = idx + startLine;
|
|
191
|
+
if (isLineHidden(rule, lineNumber)) {
|
|
192
|
+
hiddenLines++;
|
|
193
|
+
changed = true;
|
|
194
|
+
return REDACT_MARKER;
|
|
195
|
+
}
|
|
196
|
+
const out = redactLine(rule, lineNumber, line, false);
|
|
197
|
+
if (out.redacted) changed = true;
|
|
198
|
+
return out.text;
|
|
199
|
+
});
|
|
200
|
+
return { text: lines.join('\n'), redacted: changed, hiddenLines, rule };
|
|
201
|
+
}
|
|
202
|
+
// Build a normalized (read-only) rule list for a project. Also used by the
|
|
203
|
+
// settings UI to show a clean view of what is stored.
|
|
204
|
+
function getRules(projectDir) {
|
|
205
|
+
return getEntries(projectDir)
|
|
206
|
+
.map(normalizeEntry)
|
|
207
|
+
.filter(Boolean);
|
|
208
|
+
}
|
|
209
|
+
// Build one lookup index for a whole search: the normalized rules plus a
|
|
210
|
+
// Map from normalized path to rule, and a Map from normalized path to the
|
|
211
|
+
// rules that match it by path *suffix*.
|
|
212
|
+
//
|
|
213
|
+
// This exists because the redaction check used to call `getRules()` (and
|
|
214
|
+
// therefore re-read and re-parse the project settings) once per line of
|
|
215
|
+
// every scanned file — 27k settings reads for one 2-second search. Callers
|
|
216
|
+
// with many paths to test build the index once and hand it to
|
|
217
|
+
// `matchIsHiddenIn` / `lineIsHiddenIn`.
|
|
218
|
+
//
|
|
219
|
+
// The suffix map is the safety net: the engine that produces the paths can
|
|
220
|
+
// report them differently from the rule (`./src/a.js` vs `src/a.js`,
|
|
221
|
+
// absolute vs relative) and a rule that fails to match is a leak, not a
|
|
222
|
+
// cosmetic bug. So a rule also applies to any path that ends with the rule
|
|
223
|
+
// path on a `/` boundary.
|
|
224
|
+
function buildRuleIndex(projectDir) {
|
|
225
|
+
const rules = getRules(projectDir);
|
|
226
|
+
const byPath = new Map();
|
|
227
|
+
const bySuffix = new Map();
|
|
228
|
+
for (const rule of rules) {
|
|
229
|
+
byPath.set(rule.path, rule);
|
|
230
|
+
const parts = rule.path.split('/');
|
|
231
|
+
for (let i = 1; i < parts.length; i++) {
|
|
232
|
+
const suffix = parts.slice(i).join('/');
|
|
233
|
+
if (!bySuffix.has(suffix)) bySuffix.set(suffix, []);
|
|
234
|
+
bySuffix.get(suffix).push(rule);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { rules, byPath, bySuffix };
|
|
238
|
+
}
|
|
239
|
+
// Resolve the rules that apply to one path against a prebuilt index.
|
|
240
|
+
// Exact normalized match first, then the suffix map. Returns [] when
|
|
241
|
+
// nothing applies. Never throws.
|
|
242
|
+
function rulesForPathIn(index, relPath) {
|
|
243
|
+
if (!index || !relPath) return [];
|
|
244
|
+
const target = normalizePath(relPath);
|
|
245
|
+
if (!target) return [];
|
|
246
|
+
const exact = index.byPath.get(target);
|
|
247
|
+
const suffix = index.bySuffix.get(target);
|
|
248
|
+
if (exact && suffix) return [exact].concat(suffix.filter((r) => r !== exact));
|
|
249
|
+
if (exact) return [exact];
|
|
250
|
+
return suffix || [];
|
|
251
|
+
}
|
|
252
|
+
// Return the { path, ranges, chars } rule for a given project file, or null.
|
|
253
|
+
function ruleForPath(projectDir, relPath) {
|
|
254
|
+
const target = normalizePath(relPath);
|
|
255
|
+
if (!target) return null;
|
|
256
|
+
for (const rule of getRules(projectDir)) {
|
|
257
|
+
if (rule.path === target) return rule;
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
// Predicate over a prebuilt rule index: true when the given 1-indexed line
|
|
262
|
+
// is fully hidden by any rule that applies to `relPath`. The no-index
|
|
263
|
+
// `lineIsHidden` wrapper below keeps the single-path callers (and tests)
|
|
264
|
+
// working.
|
|
265
|
+
function lineIsHiddenIn(index, relPath, line) {
|
|
266
|
+
if (!Number.isInteger(line) || line < 1) return false;
|
|
267
|
+
for (const rule of rulesForPathIn(index, relPath)) {
|
|
268
|
+
if (isLineHidden(rule, line)) return true;
|
|
269
|
+
}
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
// Same, for a match that occupies columns [colStart, colEnd] on the line.
|
|
273
|
+
// The whole line, or an overlapping character span, suppresses the match.
|
|
274
|
+
function matchIsHiddenIn(index, relPath, line, colStart, colEnd) {
|
|
275
|
+
if (!Number.isInteger(line) || line < 1) return false;
|
|
276
|
+
for (const rule of rulesForPathIn(index, relPath)) {
|
|
277
|
+
if (isLineHidden(rule, line)) return true;
|
|
278
|
+
const spans = rule.chars || [];
|
|
279
|
+
for (const s of spans) {
|
|
280
|
+
if (line < s.startLine || line > s.endLine) continue;
|
|
281
|
+
if (typeof colStart !== 'number' || typeof colEnd !== 'number') return true;
|
|
282
|
+
const startCol = line === s.startLine ? s.startCol : 1;
|
|
283
|
+
const endCol = line === s.endLine ? s.endCol : Number.MAX_SAFE_INTEGER;
|
|
284
|
+
if (Math.max(startCol, colStart) <= Math.min(endCol, colEnd)) return true;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
// A predicate used by callers that only have a project dir and a path (the
|
|
290
|
+
// search engine, when it did not bother to build an index). Returns true
|
|
291
|
+
// when the given 1-indexed line of the given file is hidden and should
|
|
292
|
+
// therefore be excluded from any match list. `colStart`/`colEnd` are
|
|
293
|
+
// 1-indexed inclusive character columns occupied by the match on that line;
|
|
294
|
+
// when omitted the whole line is treated as potentially hidden so a search
|
|
295
|
+
// result is conservatively suppressed. Never throws.
|
|
296
|
+
function matchIsHidden(projectDir, relPath, line, colStart, colEnd) {
|
|
297
|
+
return matchIsHiddenIn(buildRuleIndex(projectDir), relPath, line, colStart, colEnd);
|
|
298
|
+
}
|
|
299
|
+
// Convenience predicate for callers that only care about whole lines.
|
|
300
|
+
function lineIsHidden(projectDir, relPath, line) {
|
|
301
|
+
return lineIsHiddenIn(buildRuleIndex(projectDir), relPath, line);
|
|
302
|
+
}
|
|
303
|
+
// The rule that applies to a path, resolved through the same exact-then-
|
|
304
|
+
// suffix lookup the search engine uses, so a one-path caller and the
|
|
305
|
+
// engine never disagree about whether a file is covered.
|
|
306
|
+
function ruleForPathIn(index, relPath) {
|
|
307
|
+
const matches = rulesForPathIn(index, relPath);
|
|
308
|
+
return matches.length ? matches[0] : null;
|
|
309
|
+
}
|
|
310
|
+
module.exports = {
|
|
311
|
+
REDACT_MARKER,
|
|
312
|
+
getEntries,
|
|
313
|
+
getRules,
|
|
314
|
+
buildRuleIndex,
|
|
315
|
+
rulesForPathIn,
|
|
316
|
+
ruleForPath,
|
|
317
|
+
ruleForPathIn,
|
|
318
|
+
isLineHidden,
|
|
319
|
+
redactText,
|
|
320
|
+
lineIsHidden,
|
|
321
|
+
lineIsHiddenIn,
|
|
322
|
+
matchIsHidden,
|
|
323
|
+
matchIsHiddenIn,
|
|
324
|
+
normalizePath,
|
|
325
|
+
normalizeEntry,
|
|
326
|
+
normalizeCharSpan
|
|
327
|
+
};
|