mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
Fix bugs breaking drag and drop of files. (#4887)
Co-authored-by: matt korwel <matt.korwel@gmail.com>
This commit is contained in:
@@ -164,7 +164,8 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
|
|
||||||
const handleInput = useCallback(
|
const handleInput = useCallback(
|
||||||
(key: Key) => {
|
(key: Key) => {
|
||||||
if (!focus) {
|
/// We want to handle paste even when not focused to support drag and drop.
|
||||||
|
if (!focus && !key.paste) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,7 +350,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
useKeypress(handleInput, { isActive: focus });
|
useKeypress(handleInput, { isActive: true });
|
||||||
|
|
||||||
const linesToRender = buffer.viewportVisualLines;
|
const linesToRender = buffer.viewportVisualLines;
|
||||||
const [cursorVisualRowAbsolute, cursorVisualColAbsolute] =
|
const [cursorVisualRowAbsolute, cursorVisualColAbsolute] =
|
||||||
|
|||||||
@@ -407,8 +407,8 @@ describe('useTextBuffer', () => {
|
|||||||
useTextBuffer({ viewport, isValidPath: () => true }),
|
useTextBuffer({ viewport, isValidPath: () => true }),
|
||||||
);
|
);
|
||||||
const filePath = '/path/to/a/valid/file.txt';
|
const filePath = '/path/to/a/valid/file.txt';
|
||||||
act(() => result.current.insert(filePath));
|
act(() => result.current.insert(filePath, { paste: true }));
|
||||||
expect(getBufferState(result).text).toBe(`@${filePath}`);
|
expect(getBufferState(result).text).toBe(`@${filePath} `);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not prepend @ to an invalid file path on insert', () => {
|
it('should not prepend @ to an invalid file path on insert', () => {
|
||||||
@@ -416,7 +416,7 @@ describe('useTextBuffer', () => {
|
|||||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||||
);
|
);
|
||||||
const notAPath = 'this is just some long text';
|
const notAPath = 'this is just some long text';
|
||||||
act(() => result.current.insert(notAPath));
|
act(() => result.current.insert(notAPath, { paste: true }));
|
||||||
expect(getBufferState(result).text).toBe(notAPath);
|
expect(getBufferState(result).text).toBe(notAPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -425,8 +425,8 @@ describe('useTextBuffer', () => {
|
|||||||
useTextBuffer({ viewport, isValidPath: () => true }),
|
useTextBuffer({ viewport, isValidPath: () => true }),
|
||||||
);
|
);
|
||||||
const filePath = "'/path/to/a/valid/file.txt'";
|
const filePath = "'/path/to/a/valid/file.txt'";
|
||||||
act(() => result.current.insert(filePath));
|
act(() => result.current.insert(filePath, { paste: true }));
|
||||||
expect(getBufferState(result).text).toBe(`@/path/to/a/valid/file.txt`);
|
expect(getBufferState(result).text).toBe(`@/path/to/a/valid/file.txt `);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not prepend @ to short text that is not a path', () => {
|
it('should not prepend @ to short text that is not a path', () => {
|
||||||
@@ -434,7 +434,7 @@ describe('useTextBuffer', () => {
|
|||||||
useTextBuffer({ viewport, isValidPath: () => true }),
|
useTextBuffer({ viewport, isValidPath: () => true }),
|
||||||
);
|
);
|
||||||
const shortText = 'ab';
|
const shortText = 'ab';
|
||||||
act(() => result.current.insert(shortText));
|
act(() => result.current.insert(shortText, { paste: true }));
|
||||||
expect(getBufferState(result).text).toBe(shortText);
|
expect(getBufferState(result).text).toBe(shortText);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -449,7 +449,7 @@ describe('useTextBuffer', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const filePath = '/path/to/a/valid/file.txt';
|
const filePath = '/path/to/a/valid/file.txt';
|
||||||
act(() => result.current.insert(filePath));
|
act(() => result.current.insert(filePath, { paste: true }));
|
||||||
expect(getBufferState(result).text).toBe(filePath); // No @ prefix
|
expect(getBufferState(result).text).toBe(filePath); // No @ prefix
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -462,7 +462,7 @@ describe('useTextBuffer', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const quotedFilePath = "'/path/to/a/valid/file.txt'";
|
const quotedFilePath = "'/path/to/a/valid/file.txt'";
|
||||||
act(() => result.current.insert(quotedFilePath));
|
act(() => result.current.insert(quotedFilePath, { paste: true }));
|
||||||
expect(getBufferState(result).text).toBe(quotedFilePath); // No @ prefix, keeps quotes
|
expect(getBufferState(result).text).toBe(quotedFilePath); // No @ prefix, keeps quotes
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -475,7 +475,7 @@ describe('useTextBuffer', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const notAPath = 'this is just some text';
|
const notAPath = 'this is just some text';
|
||||||
act(() => result.current.insert(notAPath));
|
act(() => result.current.insert(notAPath, { paste: true }));
|
||||||
expect(getBufferState(result).text).toBe(notAPath);
|
expect(getBufferState(result).text).toBe(notAPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -488,7 +488,7 @@ describe('useTextBuffer', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const shortText = 'ls';
|
const shortText = 'ls';
|
||||||
act(() => result.current.insert(shortText));
|
act(() => result.current.insert(shortText, { paste: true }));
|
||||||
expect(getBufferState(result).text).toBe(shortText); // No @ prefix for short text
|
expect(getBufferState(result).text).toBe(shortText); // No @ prefix for short text
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -849,6 +849,7 @@ describe('useTextBuffer', () => {
|
|||||||
ctrl: false,
|
ctrl: false,
|
||||||
meta: false,
|
meta: false,
|
||||||
shift: false,
|
shift: false,
|
||||||
|
paste: false,
|
||||||
sequence: '\x7f',
|
sequence: '\x7f',
|
||||||
});
|
});
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
@@ -856,6 +857,7 @@ describe('useTextBuffer', () => {
|
|||||||
ctrl: false,
|
ctrl: false,
|
||||||
meta: false,
|
meta: false,
|
||||||
shift: false,
|
shift: false,
|
||||||
|
paste: false,
|
||||||
sequence: '\x7f',
|
sequence: '\x7f',
|
||||||
});
|
});
|
||||||
result.current.handleInput({
|
result.current.handleInput({
|
||||||
@@ -863,6 +865,7 @@ describe('useTextBuffer', () => {
|
|||||||
ctrl: false,
|
ctrl: false,
|
||||||
meta: false,
|
meta: false,
|
||||||
shift: false,
|
shift: false,
|
||||||
|
paste: false,
|
||||||
sequence: '\x7f',
|
sequence: '\x7f',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -990,9 +993,9 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
|||||||
|
|
||||||
// Simulate pasting the long text multiple times
|
// Simulate pasting the long text multiple times
|
||||||
act(() => {
|
act(() => {
|
||||||
result.current.insert(longText);
|
result.current.insert(longText, { paste: true });
|
||||||
result.current.insert(longText);
|
result.current.insert(longText, { paste: true });
|
||||||
result.current.insert(longText);
|
result.current.insert(longText, { paste: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
const state = getBufferState(result);
|
const state = getBufferState(result);
|
||||||
|
|||||||
@@ -1023,26 +1023,27 @@ export function useTextBuffer({
|
|||||||
}, [visualCursor, visualScrollRow, viewport]);
|
}, [visualCursor, visualScrollRow, viewport]);
|
||||||
|
|
||||||
const insert = useCallback(
|
const insert = useCallback(
|
||||||
(ch: string): void => {
|
(ch: string, { paste = false }: { paste?: boolean } = {}): void => {
|
||||||
if (/[\n\r]/.test(ch)) {
|
if (/[\n\r]/.test(ch)) {
|
||||||
dispatch({ type: 'insert', payload: ch });
|
dispatch({ type: 'insert', payload: ch });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const minLengthToInferAsDragDrop = 3;
|
const minLengthToInferAsDragDrop = 3;
|
||||||
if (ch.length >= minLengthToInferAsDragDrop && !shellModeActive) {
|
if (
|
||||||
let potentialPath = ch;
|
ch.length >= minLengthToInferAsDragDrop &&
|
||||||
if (
|
!shellModeActive &&
|
||||||
potentialPath.length > 2 &&
|
paste
|
||||||
potentialPath.startsWith("'") &&
|
) {
|
||||||
potentialPath.endsWith("'")
|
let potentialPath = ch.trim();
|
||||||
) {
|
const quoteMatch = potentialPath.match(/^'(.*)'$/);
|
||||||
potentialPath = ch.slice(1, -1);
|
if (quoteMatch) {
|
||||||
|
potentialPath = quoteMatch[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
potentialPath = potentialPath.trim();
|
potentialPath = potentialPath.trim();
|
||||||
if (isValidPath(unescapePath(potentialPath))) {
|
if (isValidPath(unescapePath(potentialPath))) {
|
||||||
ch = `@${potentialPath}`;
|
ch = `@${potentialPath} `;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1203,7 +1204,7 @@ export function useTextBuffer({
|
|||||||
backspace();
|
backspace();
|
||||||
else if (key.name === 'delete' || (key.ctrl && key.name === 'd')) del();
|
else if (key.name === 'delete' || (key.ctrl && key.name === 'd')) del();
|
||||||
else if (input && !key.ctrl && !key.meta) {
|
else if (input && !key.ctrl && !key.meta) {
|
||||||
insert(input);
|
insert(input, { paste: key.paste });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[newline, move, deleteWordLeft, deleteWordRight, backspace, del, insert],
|
[newline, move, deleteWordLeft, deleteWordRight, backspace, del, insert],
|
||||||
@@ -1306,7 +1307,7 @@ export interface TextBuffer {
|
|||||||
/**
|
/**
|
||||||
* Insert a single character or string without newlines.
|
* Insert a single character or string without newlines.
|
||||||
*/
|
*/
|
||||||
insert: (ch: string) => void;
|
insert: (ch: string, opts?: { paste?: boolean }) => void;
|
||||||
newline: () => void;
|
newline: () => void;
|
||||||
backspace: () => void;
|
backspace: () => void;
|
||||||
del: () => void;
|
del: () => void;
|
||||||
|
|||||||
Reference in New Issue
Block a user