Skip to content

Commit 9e51f01

Browse files
committed
Implement support for the #ContextAtCommit placeholder in the prompt field UI and backend processing
1 parent c5cf8a8 commit 9e51f01

12 files changed

Lines changed: 439 additions & 173 deletions

File tree

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,10 @@
214214
content: '\ebdb';
215215
}
216216

217+
&--contextatcommit &__icon::before {
218+
content: '\ea82';
219+
}
220+
217221
&--commit &__icon::before {
218222
content: '\eafc';
219223
}

‎packages/ui/src/components/editor/panel/PromptField/PromptField.stories.tsx‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,28 @@ export const WithCommit = () => (
137137
/>
138138
)
139139

140+
export const WithContextAtCommit = () => (
141+
<PromptField
142+
value='Ask about #ContextAtCommit:my-repo:a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0 "Initial commit"'
143+
chat_history={[]}
144+
on_change={(value) => console.log('Changed:', value)}
145+
on_submit={() => console.log('Submitted')}
146+
on_copy={() => console.log('Copied')}
147+
is_connected={true}
148+
is_in_code_completions_mode={false}
149+
has_active_editor={true}
150+
has_active_selection={false}
151+
on_caret_position_change={(pos) => console.log('Caret position:', pos)}
152+
on_search_click={() => console.log('Search clicked')}
153+
on_at_sign_click={() => console.log('@ clicked')}
154+
on_hash_sign_click={() => console.log('# clicked')}
155+
is_web_mode={false}
156+
on_submit_with_control={() => console.log('Submitted with control')}
157+
context_file_paths={[]}
158+
on_curly_braces_click={() => {}}
159+
/>
160+
)
161+
140162
export const WithEditFormatSelector = () => {
141163
const [edit_format, set_edit_format] = useState<EditFormat>('diff')
142164
return (

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

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const getKeywordRanges = (
2222
const ranges: { start: number; end: number }[] = []
2323
// This regex is a combination of all keyword types
2424
const regex =
25-
/`([^\s`]*\.[^\s`]+)`|(#Changes:[^\s,;:!?]+)|(#Selection)|(#SavedContext:(?:WorkspaceState|JSON)\s+"[^"]+")|(#Commit:[^:]+:[^\s"]+\s+"[^"]*")/g
25+
/`([^\s`]*\.[^\s`]+)`|(#Changes:[^\s,;:!?]+)|(#Selection)|(#SavedContext:(?:WorkspaceState|JSON)\s+"[^"]+")|(#(?:Commit|ContextAtCommit):[^:]+:[^\s"]+\s+"[^"]*")/g
2626

2727
let match
2828
while ((match = regex.exec(text)) !== null) {
@@ -104,6 +104,20 @@ const reconstruct_raw_value_from_node = (node: Node): string => {
104104
}
105105
break
106106
}
107+
case 'contextatcommit-keyword': {
108+
const repo_name = el.dataset.repoName
109+
const commit_hash = el.dataset.commitHash
110+
const commit_message = el.dataset.commitMessage
111+
if (!repo_name || !commit_hash || commit_message === undefined) {
112+
return ''
113+
}
114+
const short_hash = commit_hash.substring(0, 7)
115+
if (el.textContent?.startsWith(short_hash)) {
116+
const extra = el.textContent.substring(short_hash.length)
117+
return `#ContextAtCommit:${repo_name}:${commit_hash} "${commit_message}"${extra}`
118+
}
119+
break
120+
}
107121
}
108122

109123
if (el.childNodes.length > 0) {
@@ -276,6 +290,23 @@ export const use_handlers = (
276290
const search_pattern = `#Commit:${repo_name}:${commit_hash} "${commit_message}"`
277291
const start_index = props.value.indexOf(search_pattern)
278292

293+
if (start_index !== -1) {
294+
apply_keyword_deletion(
295+
start_index,
296+
start_index + search_pattern.length
297+
)
298+
}
299+
break
300+
}
301+
case 'contextatcommit-keyword': {
302+
const repo_name = keyword_element.dataset.repoName
303+
const commit_hash = keyword_element.dataset.commitHash
304+
const commit_message = keyword_element.dataset.commitMessage
305+
if (!repo_name || !commit_hash || commit_message === undefined) return
306+
307+
const search_pattern = `#ContextAtCommit:${repo_name}:${commit_hash} "${commit_message}"`
308+
const start_index = props.value.indexOf(search_pattern)
309+
279310
if (start_index !== -1) {
280311
apply_keyword_deletion(
281312
start_index,
@@ -538,7 +569,9 @@ export const use_handlers = (
538569
context_file_paths: string[]
539570
): boolean => {
540571
const text_before_cursor = props.value.substring(0, raw_pos)
541-
const match = text_before_cursor.match(/#Commit:[^:]+:[^\s"]+\s+"[^"]*"$/)
572+
const match = text_before_cursor.match(
573+
/#(?:Commit|ContextAtCommit):[^:]+:[^\s"]+\s+"[^"]*"$/
574+
)
542575

543576
if (match) {
544577
const start_of_match = raw_pos - match[0].length
@@ -563,23 +596,26 @@ export const use_handlers = (
563596
context_file_paths
564597
)
565598

566-
if (el.dataset.type === 'file-keyword') {
599+
if (el.dataset.type == 'file-keyword') {
567600
return handle_file_keyword_deletion(raw_pos, context_file_paths)
568601
}
569602

570-
if (el.dataset.type === 'changes-keyword') {
603+
if (el.dataset.type == 'changes-keyword') {
571604
return handle_changes_keyword_deletion(raw_pos, context_file_paths)
572605
}
573606

574-
if (el.dataset.type === 'saved-context-keyword') {
607+
if (el.dataset.type == 'saved-context-keyword') {
575608
return handle_saved_context_keyword_deletion(raw_pos, context_file_paths)
576609
}
577610

578-
if (el.dataset.type === 'selection-keyword') {
611+
if (el.dataset.type == 'selection-keyword') {
579612
return handle_selection_keyword_deletion(raw_pos, context_file_paths)
580613
}
581614

582-
if (el.dataset.type === 'commit-keyword') {
615+
if (
616+
el.dataset.type == 'commit-keyword' ||
617+
el.dataset.type == 'contextatcommit-keyword'
618+
) {
583619
return handle_commit_keyword_deletion(raw_pos, context_file_paths)
584620
}
585621

@@ -619,7 +655,8 @@ export const use_handlers = (
619655
parent.dataset.type == 'changes-keyword' ||
620656
parent.dataset.type == 'selection-keyword' ||
621657
parent.dataset.type == 'saved-context-keyword' ||
622-
parent.dataset.type == 'commit-keyword'
658+
parent.dataset.type == 'commit-keyword' ||
659+
parent.dataset.type == 'contextatcommit-keyword'
623660
) {
624661
const rangeAfter = document.createRange()
625662
rangeAfter.selectNodeContents(parent)
@@ -639,11 +676,12 @@ export const use_handlers = (
639676
) {
640677
const el = node_before_cursor as HTMLElement
641678
if (
642-
el.dataset.type === 'file-keyword' ||
643-
el.dataset.type === 'changes-keyword' ||
644-
el.dataset.type === 'selection-keyword' ||
645-
el.dataset.type === 'saved-context-keyword' ||
646-
el.dataset.type === 'commit-keyword'
679+
el.dataset.type == 'file-keyword' ||
680+
el.dataset.type == 'changes-keyword' ||
681+
el.dataset.type == 'selection-keyword' ||
682+
el.dataset.type == 'saved-context-keyword' ||
683+
el.dataset.type == 'commit-keyword' ||
684+
el.dataset.type == 'contextatcommit-keyword'
647685
) {
648686
const display_pos = get_caret_position_from_div(input_ref.current)
649687
if (handle_keyword_deletion_by_backspace(el, display_pos)) {

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ export const get_display_text = (
22
text: string,
33
context_file_paths: string[]
44
): string => {
5-
const regex =
6-
/`([^\s`]*\.[^\s`]+)`|(#Changes:[^\s,;:!?]+)|(#SavedContext:(?:WorkspaceState|JSON)\s+"([^"]+)")|(#Commit:[^:]+:([^\s"]+)\s+"[^"]*")/g
5+
const regex = /`([^\s`]*\.[^\s`]+)`|(#Changes:[^\s,;:!?]+)|(#SavedContext:(?:WorkspaceState|JSON)\s+"([^"]+)")|(#(?:Commit|ContextAtCommit):[^:]+:([^\s"]+)\s+"[^"]*")/g
76
return text.replace(
87
regex,
98
(

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ export const get_highlighted_text = (params: {
8181
.join('')
8282
}
8383

84-
const commit_regex_part = '#Commit:[^:]+:[^\\s"]+\\s+"[^"]*"'
84+
const commit_regex_part =
85+
'#(?:Commit|ContextAtCommit):[^:]+:[^\\s"]+\\s+"[^"]*"'
8586
const regex = new RegExp(
8687
`(#Selection|#Changes:[^\\s,;:!?]+|${saved_context_regex_part}|${commit_regex_part})`,
8788
'g'
@@ -133,16 +134,19 @@ export const get_highlighted_text = (params: {
133134
styles['keyword__text']
134135
}" data-role="keyword-text">Context "${escape_html(context_name)}"</span></span>`
135136
}
136-
const commit_match = part.match(/^#Commit:([^:]+):([^\s"]+)\s+"([^"]*)"$/)
137+
const commit_match = part.match(
138+
/^#(Commit|ContextAtCommit):([^:]+):([^\s"]+)\s+"([^"]*)"$/
139+
)
137140
if (part && commit_match) {
138-
const repo_name = commit_match[1]
139-
const commit_hash = commit_match[2]
140-
const commit_message = commit_match[3]
141+
const symbol = commit_match[1]
142+
const repo_name = commit_match[2]
143+
const commit_hash = commit_match[3]
144+
const commit_message = commit_match[4]
141145
const short_hash = commit_hash.substring(0, 7)
142146
return `<span class="${cn(
143147
styles['keyword'],
144-
styles['keyword--commit']
145-
)}" data-type="commit-keyword" data-repo-name="${escape_html(
148+
styles[`keyword--${symbol.toLowerCase()}`]
149+
)}" data-type="${symbol.toLowerCase()}-keyword" data-repo-name="${escape_html(
146150
repo_name
147151
)}" data-commit-hash="${escape_html(
148152
commit_hash

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

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

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

1414
while ((match = regex.exec(raw_text)) !== null) {
@@ -85,7 +85,7 @@ export const map_raw_pos_to_display_pos = (
8585
let last_raw_index = 0
8686

8787
const regex =
88-
/`([^\s`]*\.[^\s`]+)`|(#Changes:[^\s,;:!?]+)|(#Selection)|(#SavedContext:(?:WorkspaceState|JSON)\s+"([^"]+)")|(#Commit:[^:]+:([^\s"]+)\s+"[^"]*")/g
88+
/`([^\s`]*\.[^\s`]+)`|(#Changes:[^\s,;:!?]+)|(#Selection)|(#SavedContext:(?:WorkspaceState|JSON)\s+"([^"]+)")|(#(?:Commit|ContextAtCommit):[^:]+:([^\s"]+)\s+"[^"]*")/g
8989
let match
9090

9191
while ((match = regex.exec(raw_text)) !== null) {

‎packages/vscode/src/views/panel/backend/message-handlers/handle-copy-prompt.ts‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { FilesCollector } from '@/utils/files-collector'
44
import { replace_selection_placeholder } from '@/views/panel/backend/utils/replace-selection-placeholder'
55
import {
66
replace_changes_placeholder,
7-
replace_commit_placeholder
7+
replace_commit_placeholder,
8+
replace_context_at_commit_placeholder
89
} from '@/views/panel/backend/utils/replace-git-placeholders'
910
import { replace_saved_context_placeholder } from '@/utils/replace-saved-context-placeholder'
1011
import { code_completion_instructions_for_panel } from '@/constants/instructions'
@@ -123,6 +124,18 @@ export const handle_copy_prompt = async (params: {
123124
})
124125
}
125126

127+
if (instructions.includes('#ContextAtCommit:')) {
128+
pre_context_instructions = await replace_context_at_commit_placeholder({
129+
instruction: pre_context_instructions,
130+
workspace_provider: params.panel_provider.workspace_provider
131+
})
132+
post_context_instructions = await replace_context_at_commit_placeholder({
133+
instruction: post_context_instructions,
134+
after_context: true,
135+
workspace_provider: params.panel_provider.workspace_provider
136+
})
137+
}
138+
126139
if (instructions.includes('#SavedContext:')) {
127140
pre_context_instructions = await replace_saved_context_placeholder({
128141
instruction: pre_context_instructions,

‎packages/vscode/src/views/panel/backend/message-handlers/handle-edit-context.ts‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import { EditFormat } from '@shared/types/edit-format'
1717
import { ToolConfig } from '@/services/model-providers-manager'
1818
import {
1919
replace_changes_placeholder,
20-
replace_commit_placeholder
20+
replace_commit_placeholder,
21+
replace_context_at_commit_placeholder
2122
} from '@/views/panel/backend/utils/replace-git-placeholders'
2223
import { replace_saved_context_placeholder } from '@/utils/replace-saved-context-placeholder'
2324
import { replace_selection_placeholder } from '@/views/panel/backend/utils/replace-selection-placeholder'
@@ -260,12 +261,33 @@ const perform_context_editing = async (params: {
260261
instruction: pre_context_instructions,
261262
workspace_provider: params.file_tree_provider
262263
})
264+
post_context_instructions = await replace_changes_placeholder({
265+
instruction: post_context_instructions,
266+
after_context: true,
267+
workspace_provider: params.file_tree_provider
268+
})
263269
}
264270

265271
if (pre_context_instructions.includes('#Commit:')) {
266272
pre_context_instructions = await replace_commit_placeholder({
267273
instruction: pre_context_instructions
268274
})
275+
post_context_instructions = await replace_commit_placeholder({
276+
instruction: post_context_instructions,
277+
after_context: true
278+
})
279+
}
280+
281+
if (pre_context_instructions.includes('#ContextAtCommit:')) {
282+
pre_context_instructions = await replace_context_at_commit_placeholder({
283+
instruction: pre_context_instructions,
284+
workspace_provider: params.file_tree_provider
285+
})
286+
post_context_instructions = await replace_context_at_commit_placeholder({
287+
instruction: post_context_instructions,
288+
after_context: true,
289+
workspace_provider: params.file_tree_provider
290+
})
269291
}
270292

271293
if (pre_context_instructions.includes('#SavedContext:')) {

‎packages/vscode/src/views/panel/backend/message-handlers/handle-preview-preset.ts‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { FilesCollector } from '@/utils/files-collector'
55
import { replace_selection_placeholder } from '@/views/panel/backend/utils/replace-selection-placeholder'
66
import { replace_saved_context_placeholder } from '@/utils/replace-saved-context-placeholder'
77
import {
8-
replace_changes_placeholder,
8+
replace_changes_placeholder, replace_context_at_commit_placeholder,
99
replace_commit_placeholder
1010
} from '@/views/panel/backend/utils/replace-git-placeholders'
1111
import { Preset } from '@shared/types/preset'
@@ -119,6 +119,18 @@ export const handle_preview_preset = async (
119119
})
120120
}
121121

122+
if (pre_context_instructions.includes('#ContextAtCommit:')) {
123+
pre_context_instructions = await replace_context_at_commit_placeholder({
124+
instruction: pre_context_instructions,
125+
workspace_provider: panel_provider.workspace_provider
126+
})
127+
post_context_instructions = await replace_context_at_commit_placeholder({
128+
instruction: post_context_instructions,
129+
after_context: true,
130+
workspace_provider: panel_provider.workspace_provider
131+
})
132+
}
133+
122134
if (pre_context_instructions.includes('#SavedContext:')) {
123135
pre_context_instructions = await replace_saved_context_placeholder({
124136
instruction: pre_context_instructions,

‎packages/vscode/src/views/panel/backend/message-handlers/handle-send-prompt.ts‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { replace_selection_placeholder } from '@/views/panel/backend/utils/repla
55
import { apply_preset_affixes_to_instruction } from '@/utils/apply-preset-affixes'
66
import { replace_saved_context_placeholder } from '@/utils/replace-saved-context-placeholder'
77
import {
8-
replace_changes_placeholder,
8+
replace_changes_placeholder, replace_context_at_commit_placeholder,
99
replace_commit_placeholder
1010
} from '@/views/panel/backend/utils/replace-git-placeholders'
1111
import { code_completion_instructions_for_panel } from '@/constants/instructions'
@@ -200,6 +200,21 @@ export const handle_send_prompt = async (params: {
200200
})
201201
}
202202

203+
if (pre_context_instructions.includes('#ContextAtCommit:')) {
204+
pre_context_instructions = await replace_context_at_commit_placeholder(
205+
{
206+
instruction: pre_context_instructions,
207+
workspace_provider: params.panel_provider.workspace_provider
208+
}
209+
)
210+
post_context_instructions = await replace_context_at_commit_placeholder(
211+
{
212+
instruction: post_context_instructions,
213+
after_context: true,
214+
workspace_provider: params.panel_provider.workspace_provider
215+
}
216+
)
217+
}
203218
if (pre_context_instructions.includes('#SavedContext:')) {
204219
pre_context_instructions = await replace_saved_context_placeholder({
205220
instruction: pre_context_instructions,

0 commit comments

Comments
 (0)