mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
fix(terminal): end spaced paths at line suffixes and generic labels
A spaced path is no longer taken across a `path:line` suffix (grep -n, rg -n and compiler output) and an absolute path after a generic label such as `Error:` or `Note:` goes back to the ordinary detectors: any label must now be a path label. `Saved the report to: /mnt/c/My Docs/a.docx` splits as on master, pinned as a documented trade-off. Also: - a rooted candidate needs a directory before its first space, and an unlinkable one reserves its span only while its last word holds a `/`, so `/usr/bin/env node build.js` and `/review the changes in src/foo.ts` keep their links while `/etc/secret files/passwords.txt` still links nothing - a run of spaces is a column gap, not part of a name - a leading `./` is not read as a segment - the response viewer carries a bold path label that starts a later line of a paragraph (after a `<br>`), and degrades if the label pattern is missing - response viewer tests render with the options chat uses
This commit is contained in:
1 parent
74a107cf4f
commit
405c9928d9
5 files changed
+123
-28
No files matched your search
+11
-6
@@ -3015,18 +3015,23 @@ class CodemanApp {
|
||||
// invalidates the traversal.
|
||||
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
||||
const targets = [];
|
||||
// Same promise as the guard above: a stale constants.js without the label
|
||||
// pattern only loses the carried label, it never throws.
|
||||
const labelPattern = typeof SPACED_FILE_PATH_LABEL_PATTERN !== 'undefined' ? SPACED_FILE_PATH_LABEL_PATTERN : null;
|
||||
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
||||
if (node.parentElement?.closest('a')) continue;
|
||||
// Markdown puts **Saved to:** in a separate <strong> node. Carry only
|
||||
// an explicit leading path label into detection; never arbitrary prose
|
||||
// or a label from another block. The label itself stays untouched.
|
||||
// an explicit path label that starts its line into detection; never
|
||||
// arbitrary prose or a label from another block. Chat renders with
|
||||
// `breaks: true`, so a later line of a paragraph starts after a <br>.
|
||||
// The label itself stays untouched.
|
||||
const label = node.previousSibling;
|
||||
const beforeLabel = label?.previousSibling;
|
||||
let beforeLabel = label?.previousSibling;
|
||||
if (beforeLabel?.nodeType === 3 && !beforeLabel.nodeValue.trim()) beforeLabel = beforeLabel.previousSibling;
|
||||
const labelText = label?.nodeName === 'STRONG' ? label.textContent : '';
|
||||
const leadingLabel =
|
||||
/^(?:P|LI)$/.test(node.parentElement?.tagName || '') &&
|
||||
(!beforeLabel || (beforeLabel.nodeType === 3 && !beforeLabel.nodeValue.trim() && !beforeLabel.previousSibling));
|
||||
const prefix = leadingLabel && SPACED_FILE_PATH_LABEL_PATTERN.test(labelText) ? `${labelText} ` : '';
|
||||
/^(?:P|LI)$/.test(node.parentElement?.tagName || '') && (!beforeLabel || beforeLabel.nodeName === 'BR');
|
||||
const prefix = leadingLabel && labelPattern?.test(labelText) ? `${labelText} ` : '';
|
||||
const found = findFilePathLinks(prefix + (node.nodeValue || ''))
|
||||
.filter((hit) => hit.index >= prefix.length)
|
||||
.map((hit) => ({ ...hit, index: hit.index - prefix.length }));
|
||||
|
||||
+30
-12
@@ -2234,17 +2234,24 @@ const SPACED_FILE_PATH_LABEL_PATTERN =
|
||||
* - /mnt/c/My Docs/report.docx after a list marker (`-`, `*`, `•`, `1.`, `2)`)
|
||||
* Saved to: 03 Attachments/Build SIPOC Design/plan.pptx after a short label (one to four words + `:`)
|
||||
*
|
||||
* An absolute path may stand alone or follow a marker. Relative and `~/`
|
||||
* paths require an explicit path label (SPACED_FILE_PATH_LABEL_PATTERN), with
|
||||
* an optional marker before it: generic labels and bullets also introduce prose. One trailing
|
||||
* sentence mark is left out. The candidate must read as ONE path: it ends on a
|
||||
* known extension, and no earlier word ends on one or `/`; later words cannot
|
||||
* start a new `/`, `~/`, `./` or `../` path. No earlier word ends
|
||||
* in `:` or is an arrow, so two paths on a line are never merged.
|
||||
* An absolute path may stand alone or follow a marker. Any label, before an
|
||||
* absolute, relative or `~/` path, must be an explicit path label
|
||||
* (SPACED_FILE_PATH_LABEL_PATTERN), with an optional marker before it: generic
|
||||
* labels (`Error:`, `Note:`) and bare bullets also introduce prose, so a relative
|
||||
* path needs a path label to be taken spaced. One trailing sentence mark is left
|
||||
* out, and a leading `./` is not a segment. The candidate must read as ONE path:
|
||||
* it ends on a known extension, a rooted one has a directory before its first
|
||||
* space, no earlier word ends on an extension or `/`, and none holds a `:` and a
|
||||
* digit (`a.ts:42`, a line suffix ends the path). Later words cannot start a new
|
||||
* `/`, `~/`, `./` or `../` path, no earlier word ends in `:` or is an arrow, and
|
||||
* a run of spaces (a column gap) is refused, so two paths on a line are never
|
||||
* merged.
|
||||
*
|
||||
* A candidate that is one path but not a linkable one (an unknown root such as
|
||||
* `/etc`, a `.`/`..`/empty segment) comes back with `link: false`: its span links
|
||||
* nothing, rather than the fragment after its last space.
|
||||
* `/etc`, a `.`/`..`/empty segment) comes back with `link: false` when its last
|
||||
* word still holds a `/`: its span links nothing, rather than the fragment after
|
||||
* its last space. Otherwise (`/usr/bin/env node build.js`) the line is left to
|
||||
* the ordinary detectors.
|
||||
*
|
||||
* Linear: one bounded prefix match, then string scans.
|
||||
*
|
||||
@@ -2257,18 +2264,29 @@ function spacedFilePathOnLine(line) {
|
||||
const index = prefix[0].length;
|
||||
const path = text.slice(index).trimEnd().replace(/[.,;:!?]$/, '');
|
||||
if (!path.includes(' ') || !path.includes('/') || path.length > 1000) return null;
|
||||
if (!path.startsWith('/') && !SPACED_FILE_PATH_LABEL_PATTERN.test(prefix[2] || '')) return null;
|
||||
// After a label, only a path label introduces a path; with no label, only an
|
||||
// absolute path may stand alone. `Error: /a/b contains c.md` is prose.
|
||||
if (prefix[2] ? !SPACED_FILE_PATH_LABEL_PATTERN.test(prefix[2]) : !path.startsWith('/')) return null;
|
||||
if (!PATH_TOKEN_ENDS_WITH_EXTENSION.test(path)) return null;
|
||||
if (/[\t"'`<>|;&\x00-\x1f\\]/.test(path) || path.includes('://')) return null;
|
||||
// A run of spaces is a column gap (`git status`, `ls -l`), not a name.
|
||||
if (/[\t"'`<>|;&\x00-\x1f\\]/.test(path) || path.includes('://') || path.includes(' ')) return null;
|
||||
const words = path.split(' ');
|
||||
// A rooted candidate must have a directory before its first space: `/usr/bin/env node a.js`
|
||||
// or `/review the changes in src/a.ts` is a command, not a spaced path.
|
||||
if (path.startsWith('/') && !/^\/[^/]+\//.test(words[0])) return null;
|
||||
for (const word of words.slice(0, -1)) {
|
||||
if (word.endsWith('/') || word.endsWith(':') || /^(?:—|–|→|=>|->|<-)$/.test(word)) return null;
|
||||
// `a.ts:42` (grep -n, compiler output): the path already ended at the line suffix.
|
||||
if (/:\d/.test(word)) return null;
|
||||
if (PATH_TOKEN_ENDS_WITH_EXTENSION.test(word.replace(/[.,;:!?)\]}]+$/, ''))) return null;
|
||||
}
|
||||
if (words.slice(1).some((word) => /^(?:\/|~\/|\.{1,2}\/)/.test(word))) return null;
|
||||
const rooted = SPACED_PATH_ROOT.test(path) || path.startsWith('~/');
|
||||
const segments = path.replace(/^~?\//, '').split('/');
|
||||
const segments = path.replace(/^(?:~?\/|\.\/)/, '').split('/');
|
||||
const link = (rooted || !path.startsWith('/')) && segments.every((seg) => seg && seg !== '.' && seg !== '..');
|
||||
// Reserve an unlinkable span only when it still reads as a path to its end;
|
||||
// otherwise leave the line to the ordinary detectors.
|
||||
if (!link && !words[words.length - 1].includes('/')) return null;
|
||||
return { path, index, link };
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user