Skip to content

Commit bdd32ae

Browse files
committed
Refactor filename handling in ChatInput to support filenames starting with non-word characters and enable removal of referenced files on click or backspace
1 parent 122721e commit bdd32ae

4 files changed

Lines changed: 98 additions & 11 deletions

File tree

‎packages/ui/src/components/editor/panel/ChatInput/ChatInput.module.scss‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,12 @@
113113
align-items: center;
114114
justify-content: center;
115115
color: var(--cwc-text-color);
116-
opacity: 0.5;
117116
font-size: var(--font-size-10px);
118-
padding: 0 3px;
119-
margin-left: 2px;
117+
padding: 0 var(--padding-3px);
118+
margin-left: var(--padding-2px);
120119
font-weight: 500;
121120
border: 1px solid var(--cwc-border-color);
122-
border-radius: 5px;
121+
border-radius: var(--border-radius-5px);
123122
pointer-events: none;
124123
}
125124
}
@@ -134,6 +133,23 @@
134133
}
135134
}
136135

136+
.file-keyword {
137+
font-size: var(--font-size-12px);
138+
background-color: var(--vscode-editor-selectionBackground);
139+
border-radius: var(--border-radius-4px);
140+
padding: 0 var(--padding-4px) 0 var(--padding-3px);
141+
display: inline-flex;
142+
align-items: center;
143+
cursor: pointer;
144+
145+
&::before {
146+
font-family: 'codicon';
147+
content: '\ea7b';
148+
margin-right: var(--padding-3px);
149+
font-size: var(--font-size-12px);
150+
}
151+
}
152+
137153
.footer {
138154
display: flex;
139155
bottom: 0;

‎packages/ui/src/components/editor/panel/ChatInput/ChatInput.tsx‎

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
218218
!value ||
219219
caret_position != value.length ||
220220
value.endsWith(' ') ||
221+
value.endsWith('\n') ||
221222
!props.context_file_paths
222223
) {
223224
return false
@@ -226,7 +227,7 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
226227
// Check if cursor is at the end of a shortened filename
227228
const text_before_cursor = value.substring(0, caret_position)
228229
const filename_match = text_before_cursor.match(
229-
/([^\s,;:.!?`]+\.[^\s,;:.!?`]+)$/
230+
/([^\s,;:.!?`]*\.[^\s,;:.!?`]+)$/
230231
)
231232

232233
if (filename_match) {
@@ -385,7 +386,7 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
385386
if (cursor_pos > 0) {
386387
const text_before = display_value.substring(0, cursor_pos)
387388
const filename_match = text_before.match(
388-
/([^\s,;:.!?`]+\.[^\s,;:.!?`]+)$/
389+
/([^\s,;:.!?`]*\.[^\s,;:.!?`]+)$/
389390
)
390391

391392
if (filename_match) {
@@ -407,6 +408,75 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
407408
handle_key_down(e)
408409
}
409410

411+
const handle_input_click = (e: React.MouseEvent<HTMLDivElement>) => {
412+
const target = e.target as HTMLElement
413+
if (target.classList.contains(styles['file-keyword'])) {
414+
e.preventDefault()
415+
e.stopPropagation()
416+
417+
const input_element = input_ref.current
418+
if (!input_element) return
419+
420+
const display_pos = get_caret_position_from_div(input_element)
421+
const raw_pos = map_display_pos_to_raw_pos(
422+
display_pos,
423+
props.value,
424+
props.context_file_paths ?? []
425+
)
426+
427+
let start_of_path = -1
428+
let end_of_path = -1
429+
430+
if (props.value[raw_pos] === '`') {
431+
// case: raw_pos at start
432+
start_of_path = raw_pos
433+
end_of_path = props.value.indexOf('`', start_of_path + 1)
434+
} else if (raw_pos > 0 && props.value[raw_pos - 1] === '`') {
435+
// case: raw_pos at end
436+
end_of_path = raw_pos - 1
437+
const text_before = props.value.substring(0, end_of_path)
438+
start_of_path = text_before.lastIndexOf('`')
439+
}
440+
441+
if (
442+
start_of_path !== -1 &&
443+
end_of_path !== -1 &&
444+
start_of_path < end_of_path
445+
) {
446+
const path_in_backticks = props.value.substring(
447+
start_of_path + 1,
448+
end_of_path
449+
)
450+
if (props.context_file_paths?.includes(path_in_backticks)) {
451+
let leading_part = props.value.substring(0, start_of_path)
452+
let trailing_part = props.value.substring(end_of_path + 1)
453+
454+
// Handle spacing.
455+
if (leading_part.endsWith(' ')) {
456+
leading_part = leading_part.slice(0, -1)
457+
} else if (trailing_part.startsWith(' ')) {
458+
trailing_part = trailing_part.substring(1)
459+
}
460+
461+
const new_value = leading_part + trailing_part
462+
const new_raw_cursor_pos = leading_part.length
463+
props.on_change(new_value)
464+
465+
setTimeout(() => {
466+
if (input_ref.current) {
467+
const display_pos = map_raw_pos_to_display_pos(
468+
new_raw_cursor_pos,
469+
new_value,
470+
props.context_file_paths ?? []
471+
)
472+
set_caret_position_for_div(input_ref.current, display_pos)
473+
}
474+
}, 0)
475+
}
476+
}
477+
}
478+
}
479+
410480
return (
411481
<div className={styles.container}>
412482
{props.has_active_selection && props.is_in_code_completions_mode && (
@@ -459,6 +529,7 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
459529
suppressContentEditableWarning={true}
460530
onInput={handle_input_change}
461531
onKeyDown={custom_handle_key_down}
532+
onClick={handle_input_click}
462533
autoFocus
463534
className={cn(styles.input, {
464535
[styles['input-with-tab-hint']]: show_tab_hint

‎packages/ui/src/components/editor/panel/ChatInput/hooks/use-handlers.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export const use_handlers = (props: ChatInputProps) => {
7979
const words = new_display_value.split(/(\s+)/)
8080
const converted_words = words.map((word) => {
8181
// Check if this looks like a filename
82-
if (/^[^\s,;:.!?]+\.[^\s,;:.!?]+$/.test(word)) {
82+
if (/^[^\s,;:.!?]*\.[^\s,;:.!?]+$/.test(word)) {
8383
// Check if any context path ends with this filename
8484
const matching_path = context_file_paths.find(
8585
(path) => path.endsWith('/' + word) || path === word

‎packages/ui/src/components/editor/panel/ChatInput/utils/get-highlighted-text.tsx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export const get_highlighted_text = (params: {
5959
)}</span>`
6060
}
6161

62-
const filename_regex = /([^\s,;:.!?`]+\.[^\s,;:.!?`]+)/g
62+
const filename_regex = /([^\s,;:.!?`]*\.[^\s,;:.!?`]+)/g
6363
return part
6464
.split(filename_regex)
6565
.map((sub_part, index) => {
@@ -68,9 +68,9 @@ export const get_highlighted_text = (params: {
6868
(path) => path.endsWith('/' + sub_part) || path == sub_part
6969
)
7070
if (is_context_file) {
71-
return `<span class="${
72-
styles['selection-keyword']
73-
}">${escape_html(sub_part)}</span>`
71+
return `<span class="${styles['file-keyword']}">${escape_html(
72+
sub_part
73+
)}</span>`
7474
}
7575
}
7676
return escape_html(sub_part)

0 commit comments

Comments
 (0)