mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-15 02:49:33 +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
@@ -58,7 +58,9 @@ vm.runInContext(
|
||||
readFileSync(resolve(import.meta.dirname, '../src/web/public/vendor/marked.min.js'), 'utf8'),
|
||||
markedContext
|
||||
);
|
||||
const marked = markedContext.marked as { parse(text: string): string };
|
||||
const marked = markedContext.marked as { parse(text: string, options?: object): string };
|
||||
/** The options `_renderMarkdown` passes, so the tests see chat's real markup (a newline becomes `<br>`). */
|
||||
const renderChat = (markdown: string) => marked.parse(markdown, { breaks: true, gfm: true });
|
||||
|
||||
const CodemanApp = loadCodemanAppClass();
|
||||
const APP_SOURCE = readFileSync(resolve(import.meta.dirname, '../src/web/public/app.js'), 'utf8');
|
||||
@@ -89,8 +91,16 @@ describe('response viewer file-path linkifier', () => {
|
||||
['**Saved to:** /mnt/c/My Docs/a.docx', ['/mnt/c/My Docs/a.docx']],
|
||||
['**Tests:** 45/45 pass in test/foo.test.ts', ['test/foo.test.ts']],
|
||||
['**Error:** I/O failure reading data.json', ['data.json']],
|
||||
[
|
||||
'Done.\n**Saved to:** 03 Attachments/Build SIPOC Design/Build-SIPOC.pptx',
|
||||
['03 Attachments/Build SIPOC Design/Build-SIPOC.pptx'],
|
||||
],
|
||||
[
|
||||
'**Size:** 2 MB\n**Saved to:** 03 Attachments/Build SIPOC Design/Build-SIPOC.pptx',
|
||||
['03 Attachments/Build SIPOC Design/Build-SIPOC.pptx'],
|
||||
],
|
||||
])('links rendered Markdown with consumed markers and labels: %s', (markdown, expected) => {
|
||||
const html = marked.parse(markdown);
|
||||
const html = renderChat(markdown);
|
||||
const original = document.createElement('div');
|
||||
original.innerHTML = html;
|
||||
const root = linkify(html);
|
||||
@@ -103,8 +113,9 @@ describe('response viewer file-path linkifier', () => {
|
||||
for (const markdown of [
|
||||
'See **Saved to:** 03 Attachments/Build SIPOC Design/Build-SIPOC.pptx',
|
||||
'**Saved to:**\n\n03 Attachments/Build SIPOC Design/Build-SIPOC.pptx',
|
||||
'Done.\nSee **Saved to:** 03 Attachments/Build SIPOC Design/Build-SIPOC.pptx',
|
||||
]) {
|
||||
expect(paths(linkify(marked.parse(markdown))).map((a) => a.getAttribute('data-path'))).toEqual([
|
||||
expect(paths(linkify(renderChat(markdown))).map((a) => a.getAttribute('data-path'))).toEqual([
|
||||
'Design/Build-SIPOC.pptx',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -67,12 +67,15 @@ describe('spaced file paths', () => {
|
||||
expect(paths(`${label} ${p}`)).toEqual([p]);
|
||||
});
|
||||
|
||||
it.each(['- ', '* ', '• ', '1. ', '2) '])('links a spaced path after the list marker %j', (marker) => {
|
||||
const rel = '03 Attachments/Build SIPOC Design/Build-SIPOC.pptx';
|
||||
expect(paths(`${marker}${rel}`)).toEqual(['Design/Build-SIPOC.pptx']);
|
||||
expect(paths(`${marker}Path: ${rel}`)).toEqual([rel]);
|
||||
expect(paths(` ${marker}${LONG}`)).toEqual([LONG]);
|
||||
});
|
||||
it.each(['- ', '* ', '• ', '1. ', '2) '])(
|
||||
'defers a relative spaced path after a bare list marker %j, links it after a path label',
|
||||
(marker) => {
|
||||
const rel = '03 Attachments/Build SIPOC Design/Build-SIPOC.pptx';
|
||||
expect(paths(`${marker}${rel}`)).toEqual(['Design/Build-SIPOC.pptx']);
|
||||
expect(paths(`${marker}Path: ${rel}`)).toEqual([rel]);
|
||||
expect(paths(` ${marker}${LONG}`)).toEqual([LONG]);
|
||||
}
|
||||
);
|
||||
|
||||
it('works line by line in code-block text, where list markers remain literal', () => {
|
||||
const text = `Wrote two files:\n- ${LONG}\n- Path: notes/a b/c.md\n`;
|
||||
@@ -165,6 +168,64 @@ describe('spaced file paths', () => {
|
||||
});
|
||||
});
|
||||
|
||||
/** The links master returned for `line`, in order: `[path, folder?]` pairs, each at its first occurrence. */
|
||||
const masterLinks = (line: string, ...links: Array<[string, true?]>) =>
|
||||
links.map(([path, folder]) => ({ path, index: line.indexOf(path), ...(folder ? { folder } : {}) }));
|
||||
|
||||
describe('spaced file paths: shapes that keep master links', () => {
|
||||
// grep -n, rg -n and compiler output: a `:` plus a digit inside an earlier
|
||||
// word is a line suffix, so the path already ended there.
|
||||
it.each([
|
||||
['/home/me/proj/README.md:12:See docs/setup.md', '/home/me/proj/README.md', 'docs/setup.md'],
|
||||
[
|
||||
' /home/me/proj/CLAUDE.md:42:Deep detail lives in docs/architecture-invariants.md',
|
||||
'/home/me/proj/CLAUDE.md',
|
||||
'docs/architecture-invariants.md',
|
||||
],
|
||||
['/home/me/proj/src/a.ts:42:5 - error in b.ts', '/home/me/proj/src/a.ts', 'b.ts'],
|
||||
['/home/me/proj/src/index.ts:10 updated config.json', '/home/me/proj/src/index.ts', 'config.json'],
|
||||
])('ends the path at a line suffix: %s', (line, first, second) => {
|
||||
expect(findFilePathLinks(line)).toEqual(masterLinks(line, [first], [second]));
|
||||
});
|
||||
|
||||
// A generic label introduces prose for an absolute path as much as a relative one.
|
||||
it.each([
|
||||
['Error: /home/me/proj contains a.md', '/home/me/proj', 'a.md'],
|
||||
['Warning: /home/me/proj is not tracked, see notes.md', '/home/me/proj', 'notes.md'],
|
||||
['Working directory: /home/me/proj, see README.md', '/home/me/proj', 'README.md'],
|
||||
['- Note: /tmp/out has the logs in run.log', '/tmp/out', 'run.log'],
|
||||
])('defers an absolute path after a generic label: %s', (line, folder, file) => {
|
||||
expect(findFilePathLinks(line)).toEqual(masterLinks(line, [folder, true], [file]));
|
||||
});
|
||||
|
||||
it('documented trade-off: an absolute spaced path after a non-path label splits as on master', () => {
|
||||
const line = 'Saved the report to: /mnt/c/My Docs/a.docx';
|
||||
expect(findFilePathLinks(line)).toEqual(masterLinks(line, ['/mnt/c/My', true], ['Docs/a.docx']));
|
||||
});
|
||||
|
||||
it.each([
|
||||
['/usr/bin/python3 script.py', 'script.py'],
|
||||
['/usr/bin/env node build.js', 'build.js'],
|
||||
['/review the changes in src/foo.ts', 'src/foo.ts'],
|
||||
])('does not reserve a command line that only looks like one path: %s', (line, file) => {
|
||||
expect(findFilePathLinks(line)).toEqual(masterLinks(line, [file]));
|
||||
});
|
||||
|
||||
it('still refuses an unlinkable spaced path that reads as one path to its end', () => {
|
||||
expect(paths('/etc/secret files/passwords.txt')).toEqual([]);
|
||||
});
|
||||
|
||||
it('does not merge a column gap into one path', () => {
|
||||
const line = '/home/me/proj modified notes.md';
|
||||
expect(findFilePathLinks(line)).toEqual(masterLinks(line, ['/home/me/proj', true], ['notes.md']));
|
||||
});
|
||||
|
||||
it('reads a leading ./ as the workspace, not a segment', () => {
|
||||
expect(paths('Saved to: ./My Docs/plan.md')).toEqual(['./My Docs/plan.md']);
|
||||
expect(paths('Saved to: ./notes.md')).toEqual(['./notes.md']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('terminal link provider with spaced paths', () => {
|
||||
type Link = { text: string; activate: (event: unknown, text: string) => void };
|
||||
const TERMINAL_UI = readFileSync(resolve(import.meta.dirname, '../src/web/public/terminal-ui.js'), 'utf8');
|
||||
|
||||
Reference in new issue
Block a user