mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 18:39:33 +01:00
fix: apply the review items for #687 at merge
- constants.js:2296,2301 spacedFilePathOnLine: refuse a first word ending in `$` and an earlier word with no letter or digit (other than a bare `-`), so a shell prompt showing an absolute working directory (`/mnt/c/proj ❯ code notes.md`, `/home/me/proj$ cat notes.md`) keeps master's links; the six rows from the review are pinned, plus OneDrive, `Program Files (x86)` and `Docs - Copy` still linking whole. - constants.js:2312: the leading strip is now `/^(?:~?\/|(?:\.\.?\/)+)/`, so `Saved to: ../My Docs/plan.md` links whole; an interior `..` still links nothing. Tests for the detector and the terminal provider. - app.js:3032 _linkifyFilePaths: carry any leading bold label and let spacedFilePathOnLine() judge it, and give a text node that follows another inline element a placeholder word, so `**Note:** /home/me/proj/out report.pdf` and ``Use `--config` /home/me/proj/cfg data.json`` split as in the terminal; a spaced path inside inline code still links whole. The now-unused label-pattern guard is gone. - JSDoc (findFilePathLinks, SPACED_FILE_PATH_LABEL_PATTERN, spacedFilePathOnLine) and the spaced-path paragraph in docs/architecture-invariants.md updated to match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
df9f93d3ee
commit
e52cc60c7c
5 files changed
+91
-18
No files matched your search
+13
-10
@@ -3015,23 +3015,26 @@ 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 path label that starts its line into detection; never
|
||||
// arbitrary prose or a label from another block. Chat renders with
|
||||
// Markdown puts **Saved to:** in a separate <strong> node. Carry a bold
|
||||
// label that starts its line into detection and let spacedFilePathOnLine()
|
||||
// judge it, as it does the same line in the terminal (`Note:` is not a
|
||||
// path label); never 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.
|
||||
// A text node after any other inline element (`Use <code>--config</code>
|
||||
// /a/b c.json`) starts mid-line, so a placeholder word stands in for what
|
||||
// precedes it. The label itself stays untouched.
|
||||
const label = node.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.nodeName === 'BR');
|
||||
const prefix = leadingLabel && labelPattern?.test(labelText) ? `${labelText} ` : '';
|
||||
label?.nodeName === 'STRONG' &&
|
||||
/^(?:P|LI)$/.test(node.parentElement?.tagName || '') &&
|
||||
(!beforeLabel || beforeLabel.nodeName === 'BR');
|
||||
let prefix = '';
|
||||
if (leadingLabel) prefix = `${label.textContent} `;
|
||||
else if (label?.nodeType === 1 && label.nodeName !== 'BR') prefix = 'x ';
|
||||
const found = findFilePathLinks(prefix + (node.nodeValue || ''))
|
||||
.filter((hit) => hit.index >= prefix.length)
|
||||
.map((hit) => ({ ...hit, index: hit.index - prefix.length }));
|
||||
|
||||
@@ -2159,8 +2159,10 @@ const FILE_LINK_EXTENSION_GROUP = /\\\.(\(\?:[^()]*\))\)\\b$/.exec(FILE_PATH_LIN
|
||||
* and folder detectors then skip reserved spans, earlier matches and URLs.
|
||||
*
|
||||
* The one entry point for both consumers (terminal link provider, response
|
||||
* viewer). The viewer restores a leading bold path label's context, but paths
|
||||
* split across other inline elements are still detected per text node.
|
||||
* viewer). The viewer carries a leading bold label into the text it passes
|
||||
* (spacedFilePathOnLine judges it, as for the same line in the terminal) and
|
||||
* reads a text node that follows another inline element as mid-line; paths
|
||||
* split across inline elements are still detected per text node.
|
||||
*
|
||||
* @param {string} text
|
||||
* @returns {Array<{path: string, index: number, folder?: true}>} `index` is where `path` starts in
|
||||
@@ -2218,7 +2220,7 @@ function findFilePathLinks(text) {
|
||||
return found.sort((a, b) => a.index - b.index);
|
||||
}
|
||||
|
||||
/** Labels that explicitly identify a path, shared with the rendered-markdown linkifier. */
|
||||
/** Labels that explicitly identify a path, for {@link spacedFilePathOnLine}. */
|
||||
const SPACED_FILE_PATH_LABEL_PATTERN =
|
||||
/^(?:path|files?|saved (?:to|as)|wrote|written to|output|created|location|attachment):[ \t]*$/i;
|
||||
|
||||
@@ -2239,11 +2241,12 @@ const SPACED_FILE_PATH_LABEL_PATTERN =
|
||||
* (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:
|
||||
* out, and a leading run of `./` or `../` 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 holds an extension (`a.md,`, `a.ts#L12`) or ends in `/`,
|
||||
* 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
|
||||
* `/`, `~/`, `./` or `../` path, no earlier word ends in `:`, is an arrow or has
|
||||
* no letter or digit (a prompt glyph such as `❯` or `$`; a bare `-` is kept), and
|
||||
* a run of spaces (a column gap) is refused, so two paths on a line are never
|
||||
* merged. A relative candidate whose only `/` word is its last is prose after the
|
||||
* label (`Wrote: 120 lines to src/foo.ts`) and is left alone, which also splits
|
||||
@@ -2252,7 +2255,8 @@ const SPACED_FILE_PATH_LABEL_PATTERN =
|
||||
* or a bare `=` makes the candidate a sentence (`/tmp/out has the logs in run.log`),
|
||||
* so a name such as `Pros and Cons.md` splits as master does. A command line is
|
||||
* refused: an executable under `bin/`, `.bin/`, `scripts/` or `tools/` as the
|
||||
* first word, or any `-x`/`--x` flag word.
|
||||
* first word, a first word ending in `$` (a shell prompt, `/home/me/proj$ cat a.md`),
|
||||
* or any `-x`/`--x` flag word.
|
||||
*
|
||||
* 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` when its last
|
||||
@@ -2287,8 +2291,14 @@ function spacedFilePathOnLine(line) {
|
||||
if (/\/(?:\.?bin|scripts?|tools)\/[^/]+$/.test(words[0]) || words.some((word) => /^-{1,2}\w/.test(word))) {
|
||||
return null;
|
||||
}
|
||||
// A shell prompt that prints its working directory in full (`/home/me/proj$ cat
|
||||
// notes.md`; WSL's `/mnt/c/...` is outside `$HOME`) is a command line too.
|
||||
if (words[0].endsWith('$')) return null;
|
||||
for (const word of words.slice(0, -1)) {
|
||||
if (word.endsWith('/') || word.endsWith(':') || /^(?:—|–|→|=>|->|<-|=)$/.test(word)) return null;
|
||||
// A word with no letter or digit (a prompt glyph `❯` or `$`, a `✓`) is never
|
||||
// part of a file name, apart from a bare `-` (`OneDrive - Contoso`).
|
||||
if (word !== '-' && !/[\p{L}\p{N}]/u.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;
|
||||
// An extension anywhere in an earlier word (`a.md,`, `a.ts#L12`) ends a path there.
|
||||
@@ -2299,7 +2309,7 @@ function spacedFilePathOnLine(line) {
|
||||
}
|
||||
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 !== '..');
|
||||
// A path label vouches that a path follows, not for the words before it: when
|
||||
// only the last word holds a `/` (`Wrote: 120 lines to src/foo.ts`), the spaces
|
||||
|
||||
Reference in new issue
Block a user