Skip to content

Commit 891e292

Browse files
committed
Refactor the Chat Input component to correctly handle and display #SavedContext keywords as interactive, deletable tokens, mirroring the behavior of other context keywords
1 parent d595977 commit 891e292

7 files changed

Lines changed: 150 additions & 47 deletions

File tree

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@
175175
content: '\ebdb';
176176
}
177177

178+
&--saved-context &__icon::before {
179+
content: '\eab3';
180+
}
181+
178182
&--selection &__icon::before {
179183
content: '\eb84';
180184
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,21 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
300300
apply_deletion(start_index, start_index + search_pattern.length)
301301
}
302302
}
303+
// Check if it's a saved context keyword
304+
else if (
305+
keyword_element.classList.contains(styles['keyword--saved-context'])
306+
) {
307+
const context_type = keyword_element.dataset.contextType
308+
const context_name = keyword_element.dataset.contextName
309+
if (!context_type || !context_name) return
310+
311+
const search_pattern = `#SavedContext:${context_type} "${context_name}"`
312+
const start_index = props.value.indexOf(search_pattern)
313+
314+
if (start_index !== -1) {
315+
apply_deletion(start_index, start_index + search_pattern.length)
316+
}
317+
}
303318
// Check if it's a selection keyword
304319
else if (keyword_element.classList.contains(styles['keyword--selection'])) {
305320
const search_pattern = '#Selection'

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

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ const reconstruct_raw_value_from_node = (node: Node): string => {
2727
const branchName = el.dataset.branchName
2828
return branchName ? `#Changes:${branchName}` : ''
2929
}
30+
case 'saved-context-keyword': {
31+
const contextType = el.dataset.contextType
32+
const contextName = el.dataset.contextName
33+
return contextType && contextName
34+
? `#SavedContext:${contextType} "${contextName}"`
35+
: ''
36+
}
3037
case 'selection-keyword':
3138
return '#Selection'
3239
}
@@ -194,6 +201,34 @@ export const use_handlers = (
194201
return false
195202
}
196203

204+
const handle_saved_context_keyword_deletion = (
205+
raw_pos: number,
206+
context_file_paths: string[]
207+
): boolean => {
208+
const text_before_cursor = props.value.substring(0, raw_pos)
209+
const match = text_before_cursor.match(
210+
/#SavedContext:(?:WorkspaceState|JSON)\s+"[^"]+"$/
211+
)
212+
213+
if (match) {
214+
const start_of_match = raw_pos - match[0].length
215+
const new_value =
216+
props.value.substring(0, start_of_match) +
217+
props.value.substring(raw_pos)
218+
const new_raw_cursor_pos = start_of_match
219+
props.on_change(new_value)
220+
221+
set_caret_position_after_change(
222+
input_ref,
223+
new_raw_cursor_pos,
224+
new_value,
225+
context_file_paths
226+
)
227+
return true
228+
}
229+
return false
230+
}
231+
197232
const handle_selection_keyword_deletion = (
198233
raw_pos: number,
199234
context_file_paths: string[]
@@ -239,6 +274,10 @@ export const use_handlers = (
239274
return handle_changes_keyword_deletion(raw_pos, context_file_paths)
240275
}
241276

277+
if (el.dataset.type === 'saved-context-keyword') {
278+
return handle_saved_context_keyword_deletion(raw_pos, context_file_paths)
279+
}
280+
242281
if (el.dataset.type === 'selection-keyword') {
243282
return handle_selection_keyword_deletion(raw_pos, context_file_paths)
244283
}
@@ -301,7 +340,8 @@ export const use_handlers = (
301340
if (
302341
el.dataset.type === 'file-keyword' ||
303342
el.dataset.type === 'changes-keyword' ||
304-
el.dataset.type === 'selection-keyword'
343+
el.dataset.type === 'selection-keyword' ||
344+
el.dataset.type === 'saved-context-keyword'
305345
) {
306346
const display_pos = get_caret_position_from_div(input_ref.current)
307347
if (handle_keyword_deletion_by_backspace(el, display_pos)) {

‎packages/ui/src/components/editor/panel/ChatInput/utils/get-display-text.ts‎

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,31 @@ export const get_display_text = (
22
text: string,
33
context_file_paths: string[]
44
): string => {
5-
const regex = /`([^\s`]*\.[^\s`]+)`|(#Changes:[^\s,;:!?]+)/g
6-
return text.replace(regex, (match, file_path, changes_keyword) => {
7-
if (file_path) {
8-
if (context_file_paths.includes(file_path)) {
9-
const filename = file_path.split('/').pop() || file_path
10-
return filename
5+
const regex =
6+
/`([^\s`]*\.[^\s`]+)`|(#Changes:[^\s,;:!?]+)|(#SavedContext:(?:WorkspaceState|JSON)\s+"([^"]+)")/g
7+
return text.replace(
8+
regex,
9+
(
10+
match,
11+
file_path,
12+
changes_keyword,
13+
saved_context_keyword,
14+
context_name
15+
) => {
16+
if (file_path) {
17+
if (context_file_paths.includes(file_path)) {
18+
const filename = file_path.split('/').pop() || file_path
19+
return filename
20+
}
21+
return match
22+
}
23+
if (changes_keyword) {
24+
return 'Changes'
25+
}
26+
if (saved_context_keyword) {
27+
return `Saved context ${context_name}`
1128
}
1229
return match
1330
}
14-
if (changes_keyword) {
15-
return 'Changes'
16-
}
17-
return match
18-
})
31+
)
1932
}

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

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -47,28 +47,41 @@ export const get_highlighted_text = (params: {
4747
has_active_selection: boolean
4848
context_file_paths: string[]
4949
}): string => {
50+
const saved_context_regex_part =
51+
'#SavedContext:(?:WorkspaceState|JSON)\\s+"[^"]+"'
5052
if (params.is_in_code_completions_mode) {
51-
const regex = /(#SavedContext:(?:WorkspaceState|JSON)\s+"[^"]+")/g
53+
const regex = new RegExp(`(${saved_context_regex_part})`, 'g')
5254
const parts = params.text.split(regex)
5355
return parts
5456
.map((part) => {
55-
if (
56-
part &&
57-
/^#SavedContext:(?:WorkspaceState|JSON)\s+"[^"]+"$/.test(part)
58-
) {
59-
return `<span class="${
60-
styles['selection-keyword']
61-
}" contentEditable="false" data-type="selection-keyword">${escape_html(
62-
part
63-
)}</span>`
57+
const saved_context_match = part.match(
58+
/^#SavedContext:(WorkspaceState|JSON)\s+"([^"]+)"$/
59+
)
60+
if (part && saved_context_match) {
61+
const context_type = saved_context_match[1]
62+
const context_name = saved_context_match[2]
63+
return `<span class="${cn(
64+
styles['keyword'],
65+
styles['keyword--saved-context']
66+
)}" contentEditable="false" data-type="saved-context-keyword" data-context-type="${escape_html(
67+
context_type
68+
)}" data-context-name="${escape_html(
69+
context_name
70+
)}" title="Saved Context: ${escape_html(
71+
context_name
72+
)}"><span class="${styles['keyword__icon']}"></span><span class="${
73+
styles['keyword__text']
74+
}">Saved context ${escape_html(context_name)}</span></span>`
6475
}
6576
return process_text_part_for_files(part, params.context_file_paths)
6677
})
6778
.join('')
6879
}
6980

70-
const regex =
71-
/(#Selection|#Changes:[^\s,;:!?]+|#SavedContext:(?:WorkspaceState|JSON)\s+"[^"]+")/g
81+
const regex = new RegExp(
82+
`(#Selection|#Changes:[^\\s,;:!?]+|${saved_context_regex_part})`,
83+
'g'
84+
)
7285
const parts = params.text.split(regex)
7386

7487
return parts
@@ -92,15 +105,24 @@ export const get_highlighted_text = (params: {
92105
styles['keyword__text']
93106
}">Changes</span></span>`
94107
}
95-
if (
96-
part &&
97-
/^#SavedContext:(?:WorkspaceState|JSON)\s+"[^"]+"$/.test(part)
98-
) {
99-
return `<span class="${
100-
styles['selection-keyword']
101-
}" contentEditable="false" data-type="selection-keyword">${escape_html(
102-
part
103-
)}</span>`
108+
const saved_context_match = part.match(
109+
/^#SavedContext:(WorkspaceState|JSON)\s+"([^"]+)"$/
110+
)
111+
if (part && saved_context_match) {
112+
const context_type = saved_context_match[1]
113+
const context_name = saved_context_match[2]
114+
return `<span class="${cn(
115+
styles['keyword'],
116+
styles['keyword--saved-context']
117+
)}" contentEditable="false" data-type="saved-context-keyword" data-context-type="${escape_html(
118+
context_type
119+
)}" data-context-name="${escape_html(
120+
context_name
121+
)}" title="Saved Context: ${escape_html(context_name)}"><span class="${
122+
styles['keyword__icon']
123+
}"></span><span class="${
124+
styles['keyword__text']
125+
}">Saved context ${escape_html(context_name)}</span></span>`
104126
}
105127

106128
return process_text_part_for_files(part, params.context_file_paths)

‎packages/ui/src/components/editor/panel/ChatInput/utils/position-mapping.ts‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ export const map_display_pos_to_raw_pos = (
77
let current_display_pos = 0
88
let last_raw_index = 0
99

10-
const regex = /`([^\s`]*\.[^\s`]+)`|(#Changes:[^\s,;:!?]+)|(#Selection)/g
10+
const regex =
11+
/`([^\s`]*\.[^\s`]+)`|(#Changes:[^\s,;:!?]+)|(#Selection)|(#SavedContext:(?:WorkspaceState|JSON)\s+"([^"]+)")/g
1112
let match
1213

1314
while ((match = regex.exec(raw_text)) !== null) {
1415
const file_path = match[1]
1516
const changes_keyword = match[2]
1617
const selection_keyword = match[3]
18+
const saved_context_keyword = match[4]
19+
const context_name = match[5]
1720

1821
let is_replacement_match = false
1922
let display_match_length = 0
@@ -28,6 +31,9 @@ export const map_display_pos_to_raw_pos = (
2831
} else if (selection_keyword) {
2932
display_match_length = 'Selection'.length
3033
is_replacement_match = true
34+
} else if (saved_context_keyword) {
35+
display_match_length = `Saved context ${context_name}`.length
36+
is_replacement_match = true
3137
}
3238

3339
if (!is_replacement_match) {
@@ -71,13 +77,16 @@ export const map_raw_pos_to_display_pos = (
7177
let current_raw_pos = 0
7278
let last_raw_index = 0
7379

74-
const regex = /`([^\s`]*\.[^\s`]+)`|(#Changes:[^\s,;:!?]+)|(#Selection)/g
80+
const regex =
81+
/`([^\s`]*\.[^\s`]+)`|(#Changes:[^\s,;:!?]+)|(#Selection)|(#SavedContext:(?:WorkspaceState|JSON)\s+"([^"]+)")/g
7582
let match
7683

7784
while ((match = regex.exec(raw_text)) !== null) {
7885
const file_path = match[1]
7986
const changes_keyword = match[2]
8087
const selection_keyword = match[3]
88+
const saved_context_keyword = match[4]
89+
const context_name = match[5]
8190

8291
let is_replacement_match = false
8392
let display_match_length = 0
@@ -92,6 +101,9 @@ export const map_raw_pos_to_display_pos = (
92101
} else if (selection_keyword) {
93102
display_match_length = 'Selection'.length
94103
is_replacement_match = true
104+
} else if (saved_context_keyword) {
105+
display_match_length = `Saved context ${context_name}`.length
106+
is_replacement_match = true
95107
}
96108

97109
if (!is_replacement_match) {

‎packages/vscode/src/views/panel/backend/utils/hash-sign-quick-pick.ts‎

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,16 @@ export async function hash_sign_quick_pick(params: {
1212
}): Promise<string | undefined> {
1313
let items = [
1414
{
15-
label: '#Selection',
16-
description:
17-
'Places symbol that is then replaced by text selection from the active editor'
15+
label: '$(list-flat) Selection',
16+
description: 'Text selection from the active editor'
1817
},
1918
{
20-
label: '#Changes',
21-
description:
22-
'Places symbol that is then replaced by a diff of changes between the selected branch'
19+
label: '$(git-pull-request-draft) Changes',
20+
description: 'Diff of changes between the selected branch'
2321
},
2422
{
25-
label: '#SavedContext',
26-
description:
27-
'Places symbol that is then replaced by files from a saved context'
23+
label: '$(checklist) Saved context',
24+
description: 'Include files from the saved context'
2825
}
2926
]
3027

@@ -43,11 +40,11 @@ export async function hash_sign_quick_pick(params: {
4340
return
4441
}
4542

46-
if (selected.label == '#Selection') {
43+
if (selected.label == '$(list-flat) Selection') {
4744
return '#Selection '
4845
}
4946

50-
if (selected.label == '#Changes') {
47+
if (selected.label == '$(git-pull-request-draft) Changes') {
5148
try {
5249
const workspace_folders = vscode.workspace.workspaceFolders
5350
if (!workspace_folders || workspace_folders.length == 0) {
@@ -134,7 +131,7 @@ export async function hash_sign_quick_pick(params: {
134131
continue
135132
}
136133

137-
if (selected.label == '#SavedContext') {
134+
if (selected.label == '$(checklist) Saved context') {
138135
const workspace_root = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath
139136
if (!workspace_root) {
140137
vscode.window.showErrorMessage(

0 commit comments

Comments
 (0)