Skip to content

Commit 82e7d18

Browse files
committed
Add clickable pasted lines in prompt field to navigate to specific file locations with line selection
1 parent a0adae3 commit 82e7d18

16 files changed

Lines changed: 310 additions & 49 deletions

File tree

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,20 @@
239239
content: '\eb84';
240240
}
241241

242+
&--pasted-lines &__text {
243+
cursor: pointer;
244+
}
245+
246+
&--pasted-lines &__text {
247+
&:hover {
248+
background-color: color-mix(
249+
in srgb,
250+
var(--vscode-charts-blue) 30%,
251+
transparent
252+
);
253+
}
254+
}
255+
242256
&--selection-error {
243257
background-color: color-mix(
244258
in srgb,

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

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const Empty = () => (
1414
on_copy={() => console.log('Copied')}
1515
is_connected={true}
1616
prompt_type="edit-context"
17-
current_selection=""
17+
current_selection={null}
1818
currently_open_file_path="/path/to/file"
1919
on_caret_position_change={(pos) => console.log('Caret position:', pos)}
2020
is_web_mode={false}
@@ -33,6 +33,9 @@ export const Empty = () => (
3333
on_prune_context_instructions_prefix_change={(val) =>
3434
console.log('Prune prefix changed:', val)
3535
}
36+
on_pasted_lines_click={(path, start, end) =>
37+
console.log('Pasted lines clicked:', path, start, end)
38+
}
3639
/>
3740
)
3841

@@ -45,7 +48,7 @@ export const WithText = () => (
4548
on_copy={() => console.log('Copied')}
4649
is_connected={true}
4750
prompt_type="edit-context"
48-
current_selection=""
51+
current_selection={null}
4952
currently_open_file_path="/path/to/file"
5053
on_caret_position_change={(pos) => console.log('Caret position:', pos)}
5154
is_web_mode={false}
@@ -64,6 +67,9 @@ export const WithText = () => (
6467
on_prune_context_instructions_prefix_change={(val) =>
6568
console.log('Prune prefix changed:', val)
6669
}
70+
on_pasted_lines_click={(path, start, end) =>
71+
console.log('Pasted lines clicked:', path, start, end)
72+
}
6773
/>
6874
)
6975

@@ -76,7 +82,7 @@ export const LongText = () => (
7682
on_copy={() => console.log('Copied')}
7783
is_connected={true}
7884
prompt_type="edit-context"
79-
current_selection=""
85+
current_selection={null}
8086
currently_open_file_path="/path/to/file"
8187
on_caret_position_change={(pos) => console.log('Caret position:', pos)}
8288
is_web_mode={false}
@@ -95,6 +101,9 @@ export const LongText = () => (
95101
on_prune_context_instructions_prefix_change={(val) =>
96102
console.log('Prune prefix changed:', val)
97103
}
104+
on_pasted_lines_click={(path, start, end) =>
105+
console.log('Pasted lines clicked:', path, start, end)
106+
}
98107
/>
99108
)
100109

@@ -107,7 +116,7 @@ export const WithPlaceholderSavedContext = () => (
107116
on_copy={() => console.log('Copied')}
108117
is_connected={true}
109118
prompt_type="edit-context"
110-
current_selection=""
119+
current_selection={null}
111120
currently_open_file_path="/path/to/file"
112121
on_caret_position_change={(pos) => console.log('Caret position:', pos)}
113122
on_search_click={() => console.log('Search clicked')}
@@ -126,6 +135,9 @@ export const WithPlaceholderSavedContext = () => (
126135
on_prune_context_instructions_prefix_change={(val) =>
127136
console.log('Prune prefix changed:', val)
128137
}
138+
on_pasted_lines_click={(path, start, end) =>
139+
console.log('Pasted lines clicked:', path, start, end)
140+
}
129141
/>
130142
)
131143

@@ -138,7 +150,13 @@ export const WithPlaceholderSelection = () => (
138150
on_copy={() => console.log('Copied')}
139151
is_connected={true}
140152
prompt_type="edit-context"
141-
current_selection="Selected text content"
153+
current_selection={{
154+
text: 'Selected text content',
155+
start_line: 1,
156+
start_col: 1,
157+
end_line: 1,
158+
end_col: 22
159+
}}
142160
currently_open_file_path="/path/to/file"
143161
on_caret_position_change={(pos) => console.log('Caret position:', pos)}
144162
on_search_click={() => console.log('Search clicked')}
@@ -157,6 +175,9 @@ export const WithPlaceholderSelection = () => (
157175
on_prune_context_instructions_prefix_change={(val) =>
158176
console.log('Prune prefix changed:', val)
159177
}
178+
on_pasted_lines_click={(path, start, end) =>
179+
console.log('Pasted lines clicked:', path, start, end)
180+
}
160181
/>
161182
)
162183

@@ -169,7 +190,7 @@ export const WithCommit = () => (
169190
on_copy={() => console.log('Copied')}
170191
is_connected={true}
171192
prompt_type="edit-context"
172-
current_selection=""
193+
current_selection={null}
173194
currently_open_file_path="/path/to/file"
174195
on_caret_position_change={(pos) => console.log('Caret position:', pos)}
175196
on_search_click={() => console.log('Search clicked')}
@@ -188,6 +209,9 @@ export const WithCommit = () => (
188209
on_prune_context_instructions_prefix_change={(val) =>
189210
console.log('Prune prefix changed:', val)
190211
}
212+
on_pasted_lines_click={(path, start, end) =>
213+
console.log('Pasted lines clicked:', path, start, end)
214+
}
191215
/>
192216
)
193217

@@ -200,7 +224,7 @@ export const WithCommitWithQuotes = () => (
200224
on_copy={() => console.log('Copied')}
201225
is_connected={true}
202226
prompt_type="edit-context"
203-
current_selection=""
227+
current_selection={null}
204228
currently_open_file_path="/path/to/file"
205229
on_caret_position_change={(pos) => console.log('Caret position:', pos)}
206230
on_search_click={() => console.log('Search clicked')}
@@ -219,6 +243,9 @@ export const WithCommitWithQuotes = () => (
219243
on_prune_context_instructions_prefix_change={(val) =>
220244
console.log('Prune prefix changed:', val)
221245
}
246+
on_pasted_lines_click={(path, start, end) =>
247+
console.log('Pasted lines clicked:', path, start, end)
248+
}
222249
/>
223250
)
224251
export const WithContextAtCommit = () => (
@@ -230,7 +257,7 @@ export const WithContextAtCommit = () => (
230257
on_copy={() => console.log('Copied')}
231258
is_connected={true}
232259
prompt_type="edit-context"
233-
current_selection=""
260+
current_selection={null}
234261
currently_open_file_path="/path/to/file"
235262
on_caret_position_change={(pos) => console.log('Caret position:', pos)}
236263
on_search_click={() => console.log('Search clicked')}
@@ -249,6 +276,9 @@ export const WithContextAtCommit = () => (
249276
on_prune_context_instructions_prefix_change={(val) =>
250277
console.log('Prune prefix changed:', val)
251278
}
279+
on_pasted_lines_click={(path, start, end) =>
280+
console.log('Pasted lines clicked:', path, start, end)
281+
}
252282
/>
253283
)
254284

@@ -263,7 +293,7 @@ export const WithEditFormatSelector = () => {
263293
on_copy={() => console.log('Copied')}
264294
is_connected={true}
265295
prompt_type="edit-context"
266-
current_selection=""
296+
current_selection={null}
267297
currently_open_file_path="/path/to/file"
268298
on_caret_position_change={(pos) => console.log('Caret position:', pos)}
269299
is_web_mode={false}
@@ -283,6 +313,9 @@ export const WithEditFormatSelector = () => {
283313
on_prune_context_instructions_prefix_change={(val) =>
284314
console.log('Prune prefix changed:', val)
285315
}
316+
on_pasted_lines_click={(path, start, end) =>
317+
console.log('Pasted lines clicked:', path, start, end)
318+
}
286319
/>
287320
)
288321
}
@@ -296,7 +329,7 @@ export const WithFilePaths = () => (
296329
on_copy={() => console.log('Copied')}
297330
is_connected={true}
298331
prompt_type="edit-context"
299-
current_selection=""
332+
current_selection={null}
300333
currently_open_file_path="/path/to/file"
301334
on_caret_position_change={(pos) => console.log('Caret position:', pos)}
302335
is_web_mode={false}
@@ -315,5 +348,8 @@ export const WithFilePaths = () => (
315348
on_prune_context_instructions_prefix_change={(val) =>
316349
console.log('Prune prefix changed:', val)
317350
}
351+
on_pasted_lines_click={(path, start, end) =>
352+
console.log('Pasted lines clicked:', path, start, end)
353+
}
318354
/>
319355
)

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ import { ApiPromptType, WebPromptType } from '@shared/types/prompt-types'
2525

2626
export type EditFormat = 'whole' | 'truncated' | 'diff' | 'before-after'
2727

28+
export type SelectionState = {
29+
text: string
30+
start_line: number
31+
start_col: number
32+
end_line: number
33+
end_col: number
34+
}
35+
2836
export type PromptFieldProps = {
2937
value: string
3038
chat_history: string[]
@@ -34,7 +42,7 @@ export type PromptFieldProps = {
3442
on_copy: () => void
3543
is_connected: boolean
3644
prompt_type: WebPromptType | ApiPromptType
37-
current_selection: string
45+
current_selection?: SelectionState | null
3846
on_caret_position_change: (caret_position: number) => void
3947
is_web_mode: boolean
4048
on_search_click: () => void
@@ -58,6 +66,7 @@ export type PromptFieldProps = {
5866
on_go_to_file: (file_path: string) => void
5967
prune_context_instructions_prefix: string
6068
on_prune_context_instructions_prefix_change: (value: string) => void
69+
on_pasted_lines_click: (path: string, start?: string, end?: string) => void
6170
}
6271

6372
export const PromptField: React.FC<PromptFieldProps> = (props) => {

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

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const getKeywordRanges = (
2121
const ranges: { start: number; end: number }[] = []
2222
// This regex is a combination of all keyword types
2323
const regex =
24-
/`([^\s`]*\.[^\s`]+)`|(#Changes:[^\s,;:!?]+)|(#Selection)|(#SavedContext:(?:WorkspaceState|JSON)\s+"[^"]+")|(#(?:Commit|ContextAtCommit):[^:]+:[^\s"]+\s+"(?:\\.|[^"\\])*")|(<fragment path="[^"]+">\n[\s\S]*?\n<\/fragment>)/g
24+
/`([^\s`]*\.[^\s`]+)`|(#Changes:[^\s,;:!?]+)|(#Selection)|(#SavedContext:(?:WorkspaceState|JSON)\s+"[^"]+")|(#(?:Commit|ContextAtCommit):[^:]+:[^\s"]+\s+"(?:\\.|[^"\\])*")|(<fragment path="[^"]+"(?: [^>]+)?>\n[\s\S]*?\n<\/fragment>)/g
2525

2626
let match
2727
while ((match = regex.exec(text)) !== null) {
@@ -129,8 +129,14 @@ const reconstruct_raw_value_from_node = (node: Node): string => {
129129
} else if (el.dataset.type == 'pasted-lines-keyword') {
130130
const path = el.dataset.path
131131
const content = el.dataset.content
132+
const start = el.dataset.start
133+
const end = el.dataset.end
132134
if (!path || content === undefined) return ''
133135

136+
let attributes = `path="${path}"`
137+
if (start) attributes += ` start="${start}"`
138+
if (end) attributes += ` end="${end}"`
139+
134140
const line_count = content.split('\n').length
135141
const lines_text = line_count == 1 ? 'line' : 'lines'
136142
const label = `Pasted ${line_count} ${lines_text}`
@@ -139,10 +145,10 @@ const reconstruct_raw_value_from_node = (node: Node): string => {
139145
if (index != -1) {
140146
const prefix = inner_content.substring(0, index)
141147
const suffix = inner_content.substring(index + label.length)
142-
return `${prefix}<fragment path="${path}">\n${content}\n</fragment>${suffix}`
148+
return `${prefix}<fragment ${attributes}>\n${content}\n</fragment>${suffix}`
143149
}
144150

145-
return `<fragment path="${path}">\n${content}\n</fragment>`
151+
return `<fragment ${attributes}>\n${content}\n</fragment>`
146152
}
147153

148154
return inner_content
@@ -310,12 +316,18 @@ export const use_handlers = (
310316
} else if (keyword_type == 'pasted-lines-keyword') {
311317
const path = keyword_element.dataset.path
312318
const content = keyword_element.dataset.content
319+
const start = keyword_element.dataset.start
320+
const end = keyword_element.dataset.end
313321
if (!path || content === undefined) return
314322

323+
let attributes = `path="${path}"`
324+
if (start) attributes += ` start="${start}"`
325+
if (end) attributes += ` end="${end}"`
326+
315327
// Since we can't reliably search for multiline content with indexOf easily if formatting varies,
316328
// and we just clicked a specific element, we might need a better way.
317329
// However, we construct exact strings.
318-
const search_pattern = `<fragment path="${path}">\n${content}\n</fragment>`
330+
const search_pattern = `<fragment ${attributes}>\n${content}\n</fragment>`
319331
const start_index = props.value.indexOf(search_pattern)
320332

321333
if (start_index !== -1) {
@@ -391,10 +403,12 @@ export const use_handlers = (
391403

392404
if (
393405
props.current_selection &&
394-
text === props.current_selection &&
406+
text == props.current_selection.text &&
395407
props.currently_open_file_path
396408
) {
397-
text_to_insert = `<fragment path="${props.currently_open_file_path}">\n${text}\n</fragment>`
409+
const { start_line, start_col, end_line, end_col } =
410+
props.current_selection
411+
text_to_insert = `<fragment path="${props.currently_open_file_path}" start="${start_line}:${start_col}" end="${end_line}:${end_col}">\n${text}\n</fragment>`
398412

399413
let has_space_after = false
400414
if (raw_end < props.value.length) {
@@ -450,6 +464,18 @@ export const use_handlers = (
450464
}
451465
}
452466

467+
const pasted_lines_keyword_element = text_element.closest<HTMLElement>(
468+
'[data-type="pasted-lines-keyword"]'
469+
)
470+
if (pasted_lines_keyword_element) {
471+
const path = pasted_lines_keyword_element.dataset.path
472+
const start = pasted_lines_keyword_element.dataset.start
473+
const end = pasted_lines_keyword_element.dataset.end
474+
if (path) {
475+
props.on_pasted_lines_click(path, start, end)
476+
}
477+
}
478+
453479
if (input_ref.current) {
454480
input_ref.current.focus()
455481
}
@@ -635,7 +661,7 @@ export const use_handlers = (
635661
const handle_pasted_lines_keyword_deletion = (raw_pos: number): boolean => {
636662
const text_before_cursor = props.value.substring(0, raw_pos)
637663

638-
const regex = /<fragment path="[^"]+">\n[\s\S]*?\n<\/fragment>/g
664+
const regex = /<fragment path="[^"]+"(?: [^>]+)?>\n[\s\S]*?\n<\/fragment>/g
639665
let match
640666
let last_match: RegExpExecArray | null = null
641667

0 commit comments

Comments
 (0)