Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,37 @@ describe('useMessageSubmit', () => {
},
});
});

it('resolves raw image picker paths with spaces on submit', () => {
const imagePath = 'C:\\Users\\Me\\Pictures\\screen shot.png';
const props = createDefaultProps({
inputText: `describe @${imagePath} please`,
});
props.fileContext.getFileReference = vi.fn((name: string) =>
name === imagePath ? imagePath : undefined,
);
const rendered = renderHookHarness(props);
root = rendered.root;
container = rendered.container;

act(() => {
rendered.api.handleSubmit(createSubmitEvent());
});

expect(props.vscode.postMessage).toHaveBeenCalledWith({
type: 'sendMessage',
data: {
text: `describe @${imagePath} please`,
context: [
{
type: 'file',
name: imagePath,
value: imagePath,
},
],
fileContext: undefined,
attachments: undefined,
},
});
});
});
57 changes: 43 additions & 14 deletions packages/vscode-ide-companion/src/webview/hooks/useMessageSubmit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,40 @@ export const shouldSendMessage = ({
return hasText || hasAttachments;
};

function findFileReferences(
text: string,
getFileReference: (fileName: string) => string | undefined,
): Array<{ name: string; value: string }> {
const references: Array<{ name: string; value: string }> = [];
let currentIndex = 0;

while (currentIndex < text.length) {
const atIndex = text.indexOf('@', currentIndex);
if (atIndex === -1) {
break;
}

let matched = false;
// ponytail: O(n²) against short composer text; replace with map-key scan if composer parsing gets hot.
for (let end = text.length; end > atIndex + 1; end -= 1) {
const name = text.slice(atIndex + 1, end).trimEnd();
const value = getFileReference(name);
if (value) {
references.push({ name, value });
currentIndex = end;
matched = true;
break;
}
}

if (!matched) {
currentIndex = atIndex + 1;
}
}

return references;
}

/**
* Message submit Hook
* Handles message submission logic and context parsing
Expand Down Expand Up @@ -137,20 +171,15 @@ export const useMessageSubmit = ({
startLine?: number;
endLine?: number;
}> = [];
const fileRefPattern = /@([^\s]+)/g;
let match;

while ((match = fileRefPattern.exec(textToSend)) !== null) {
const fileName = match[1];
const filePath = fileContext.getFileReference(fileName);

if (filePath) {
context.push({
type: 'file',
name: fileName,
value: filePath,
});
}
for (const reference of findFileReferences(
textToSend,
fileContext.getFileReference,
)) {
context.push({
type: 'file',
name: reference.name,
value: reference.value,
});
}

// Add active file selection context if present and not skipped
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -594,4 +594,76 @@ describe('useWebViewMessages', () => {
'/tmp/insight-report.html',
);
});

it('inserts resolved image attachments as raw absolute references', () => {
const rendered = renderHookHarness();
root = rendered.root;
container = rendered.container;

const input = document.createElement('div');
(
rendered.handlers.inputFieldRef as { current: HTMLDivElement | null }
).current = input;

act(() => {
window.dispatchEvent(
new MessageEvent('message', {
data: {
type: 'fileAttached',
data: {
id: 'file-1',
type: 'file',
name: 'screen shot.png',
value: 'C:\\Users\\Me\\Pictures\\screen shot.png',
},
},
}),
);
});

expect(rendered.handlers.fileContext.addFileReference).toHaveBeenCalledWith(
'screen shot.png',
'C:\\Users\\Me\\Pictures\\screen shot.png',
);
expect(rendered.handlers.fileContext.addFileReference).toHaveBeenCalledWith(
'C:\\Users\\Me\\Pictures\\screen shot.png',
'C:\\Users\\Me\\Pictures\\screen shot.png',
);
expect(input.textContent).toBe(
'@C:\\Users\\Me\\Pictures\\screen shot.png ',
);
expect(rendered.handlers.setInputText).toHaveBeenCalledWith(
'@C:\\Users\\Me\\Pictures\\screen shot.png ',
);
});

it('keeps non-image attachments as file-name references', () => {
const rendered = renderHookHarness();
root = rendered.root;
container = rendered.container;

const input = document.createElement('div');
(
rendered.handlers.inputFieldRef as { current: HTMLDivElement | null }
).current = input;

act(() => {
window.dispatchEvent(
new MessageEvent('message', {
data: {
type: 'fileAttached',
data: {
id: 'file-1',
type: 'file',
name: 'notes.txt',
value: 'C:\\Users\\Me\\Documents\\notes.txt',
},
},
}),
);
});

expect(input.textContent).toBe('@notes.txt ');
expect(rendered.handlers.setInputText).toHaveBeenCalledWith('@notes.txt ');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
type WebViewMessage,
type WebViewMessageBase,
} from './useImage.js';
import { isDisplayableImagePath } from '../../utils/imageSupport.js';

const FORCE_CLEAR_STREAM_END_REASONS = new Set([
'user_cancelled',
Expand Down Expand Up @@ -1296,9 +1297,18 @@ export const useWebViewMessages = ({

if (inputFieldRef.current) {
const currentText = inputFieldRef.current.textContent || '';
const referenceText = isDisplayableImagePath(attachment.value)
? attachment.value
: attachment.name;
if (referenceText !== attachment.name) {
handlers.fileContext.addFileReference(
referenceText,
attachment.value,
);
}
const newText = currentText
? `${currentText} @${attachment.name} `
: `@${attachment.name} `;
? `${currentText} @${referenceText} `
: `@${referenceText} `;
inputFieldRef.current.textContent = newText;
setInputText(newText);

Expand Down
Loading