Skip to content

Commit cb52426

Browse files
committed
Add editable prefix field for prune context instructions
1 parent 26adab5 commit cb52426

20 files changed

Lines changed: 321 additions & 90 deletions

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
}
3535
}
3636

37+
.input-wrapper {
38+
position: relative;
39+
}
40+
3741
.error {
3842
position: absolute;
3943
z-index: 2;
@@ -92,6 +96,20 @@
9296
}
9397
}
9498

99+
.prune-context-prefix {
100+
padding: var(--padding);
101+
background-color: transparent;
102+
border: none;
103+
border-bottom: 1px solid var(--cwc-border-color);
104+
resize: none;
105+
color: var(--vscode-descriptionForeground);
106+
overflow: hidden;
107+
108+
&:focus {
109+
outline: none;
110+
}
111+
}
112+
95113
.clear-button {
96114
position: absolute;
97115
top: 0;

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

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ export const Empty = () => (
2828
on_invocation_count_change={(count) =>
2929
console.log('Invocation count changed:', count)
3030
}
31-
prune_context_instructions=""
31+
on_go_to_file={(path) => console.log('Go to file:', path)}
32+
prune_context_instructions_prefix=""
33+
on_prune_context_instructions_prefix_change={(val) =>
34+
console.log('Prune prefix changed:', val)
35+
}
3236
/>
3337
)
3438

@@ -55,7 +59,11 @@ export const WithText = () => (
5559
on_invocation_count_change={(count) =>
5660
console.log('Invocation count changed:', count)
5761
}
58-
prune_context_instructions=""
62+
on_go_to_file={(path) => console.log('Go to file:', path)}
63+
prune_context_instructions_prefix=""
64+
on_prune_context_instructions_prefix_change={(val) =>
65+
console.log('Prune prefix changed:', val)
66+
}
5967
/>
6068
)
6169

@@ -82,7 +90,11 @@ export const LongText = () => (
8290
on_invocation_count_change={(count) =>
8391
console.log('Invocation count changed:', count)
8492
}
85-
prune_context_instructions=""
93+
on_go_to_file={(path) => console.log('Go to file:', path)}
94+
prune_context_instructions_prefix=""
95+
on_prune_context_instructions_prefix_change={(val) =>
96+
console.log('Prune prefix changed:', val)
97+
}
8698
/>
8799
)
88100

@@ -108,8 +120,12 @@ export const WithPlaceholderSavedContext = () => (
108120
on_invocation_count_change={(count) =>
109121
console.log('Invocation count changed:', count)
110122
}
123+
on_go_to_file={(path) => console.log('Go to file:', path)}
111124
on_curly_braces_click={() => {}}
112-
prune_context_instructions=""
125+
prune_context_instructions_prefix=""
126+
on_prune_context_instructions_prefix_change={(val) =>
127+
console.log('Prune prefix changed:', val)
128+
}
113129
/>
114130
)
115131

@@ -135,8 +151,12 @@ export const WithPlaceholderSelection = () => (
135151
on_invocation_count_change={(count) =>
136152
console.log('Invocation count changed:', count)
137153
}
154+
on_go_to_file={(path) => console.log('Go to file:', path)}
138155
on_curly_braces_click={() => {}}
139-
prune_context_instructions=""
156+
prune_context_instructions_prefix=""
157+
on_prune_context_instructions_prefix_change={(val) =>
158+
console.log('Prune prefix changed:', val)
159+
}
140160
/>
141161
)
142162

@@ -162,8 +182,12 @@ export const WithCommit = () => (
162182
on_invocation_count_change={(count) =>
163183
console.log('Invocation count changed:', count)
164184
}
185+
on_go_to_file={(path) => console.log('Go to file:', path)}
165186
on_curly_braces_click={() => {}}
166-
prune_context_instructions=""
187+
prune_context_instructions_prefix=""
188+
on_prune_context_instructions_prefix_change={(val) =>
189+
console.log('Prune prefix changed:', val)
190+
}
167191
/>
168192
)
169193

@@ -189,8 +213,12 @@ export const WithCommitWithQuotes = () => (
189213
on_invocation_count_change={(count) =>
190214
console.log('Invocation count changed:', count)
191215
}
216+
on_go_to_file={(path) => console.log('Go to file:', path)}
192217
on_curly_braces_click={() => {}}
193-
prune_context_instructions=""
218+
prune_context_instructions_prefix=""
219+
on_prune_context_instructions_prefix_change={(val) =>
220+
console.log('Prune prefix changed:', val)
221+
}
194222
/>
195223
)
196224
export const WithContextAtCommit = () => (
@@ -215,8 +243,12 @@ export const WithContextAtCommit = () => (
215243
on_invocation_count_change={(count) =>
216244
console.log('Invocation count changed:', count)
217245
}
246+
on_go_to_file={(path) => console.log('Go to file:', path)}
218247
on_curly_braces_click={() => {}}
219-
prune_context_instructions=""
248+
prune_context_instructions_prefix=""
249+
on_prune_context_instructions_prefix_change={(val) =>
250+
console.log('Prune prefix changed:', val)
251+
}
220252
/>
221253
)
222254

@@ -246,7 +278,11 @@ export const WithEditFormatSelector = () => {
246278
context_file_paths={[]}
247279
invocation_count={1}
248280
on_invocation_count_change={set_edit_format as any} // Mocking for demo
249-
prune_context_instructions=""
281+
on_go_to_file={(path) => console.log('Go to file:', path)}
282+
prune_context_instructions_prefix=""
283+
on_prune_context_instructions_prefix_change={(val) =>
284+
console.log('Prune prefix changed:', val)
285+
}
250286
/>
251287
)
252288
}
@@ -275,6 +311,9 @@ export const WithFilePaths = () => (
275311
on_invocation_count_change={(count) =>
276312
console.log('Invocation count changed:', count)
277313
}
278-
prune_context_instructions=""
314+
prune_context_instructions_prefix=""
315+
on_prune_context_instructions_prefix_change={(val) =>
316+
console.log('Prune prefix changed:', val)
317+
}
279318
/>
280319
)

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

Lines changed: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useRef, useEffect, useMemo, useState, useCallback } from 'react'
2+
import TextareaAutosize from 'react-textarea-autosize'
23
import styles from './PromptField.module.scss'
34
import cn from 'classnames'
45
import { Icon } from '../../common/Icon'
@@ -9,6 +10,7 @@ import { use_ghost_text } from './hooks/use-ghost-text'
910
import { use_drag_drop } from './hooks/use-drag-drop'
1011
import { use_keyboard_shortcuts } from './hooks/use-keyboard-shortcuts'
1112
import { use_edit_format_compacting } from './hooks/use-edit-format-compacting'
13+
import { use_prune_context_instructions } from './hooks/use-prune-context-instructions'
1214
import { DropdownMenu } from '../../common/DropdownMenu'
1315
import { use_is_mac } from '@shared/hooks'
1416
import {
@@ -54,8 +56,9 @@ export type PromptFieldProps = {
5456
currently_open_file_text?: string
5557
invocation_count: number
5658
on_invocation_count_change: (count: number) => void
57-
on_go_to_file?: (file_path: string) => void
58-
prune_context_instructions: string
59+
on_go_to_file: (file_path: string) => void
60+
prune_context_instructions_prefix: string
61+
on_prune_context_instructions_prefix_change: (value: string) => void
5962
}
6063

6164
export const PromptField: React.FC<PromptFieldProps> = (props) => {
@@ -70,6 +73,14 @@ export const PromptField: React.FC<PromptFieldProps> = (props) => {
7073
const [hovered_edit_format, set_hovered_edit_format] =
7174
useState<EditFormat | null>(null)
7275

76+
const { prune_instructions, set_prune_instructions } =
77+
use_prune_context_instructions({
78+
prune_context_instructions_prefix:
79+
props.prune_context_instructions_prefix,
80+
on_prune_context_instructions_prefix_change:
81+
props.on_prune_context_instructions_prefix_change
82+
})
83+
7384
const toggle_invocation_dropdown = useCallback(() => {
7485
set_is_invocation_dropdown_open((prev) => !prev)
7586
}, [])
@@ -312,60 +323,72 @@ export const PromptField: React.FC<PromptFieldProps> = (props) => {
312323
onKeyDown={handle_container_key_down}
313324
onClick={() => input_ref.current?.focus()}
314325
>
315-
{props.value && (
316-
<button
317-
className={cn(styles['clear-button'], 'codicon', 'codicon-close')}
318-
onClick={() => {
319-
set_should_show_ghost_text(false)
320-
handle_clear()
321-
input_ref.current?.focus()
326+
{props.prompt_type == 'prune-context' && (
327+
<TextareaAutosize
328+
className={styles['prune-context-prefix']}
329+
value={prune_instructions}
330+
onChange={(e) => {
331+
set_prune_instructions(e.target.value)
322332
}}
323-
title="Clear"
333+
onClick={(e) => e.stopPropagation()}
324334
/>
325335
)}
326-
<div
327-
ref={input_ref}
328-
contentEditable={true}
329-
suppressContentEditableWarning={true}
330-
onInput={(e) => {
331-
set_should_show_ghost_text(true)
332-
handle_input_change(e)
333-
}}
334-
onKeyDown={(e) => {
335-
if (e.key == 'ArrowRight') {
336-
set_should_show_ghost_text(false)
337-
const ghost_text_node = input_ref.current?.querySelector(
338-
'span[data-type="ghost-text"]'
339-
)
340-
if (ghost_text_node && !e.ctrlKey && !e.altKey && !e.metaKey) {
341-
ghost_text_node.remove()
342-
e.preventDefault()
343-
const selection = window.getSelection()
344-
if (selection) {
345-
const type = e.shiftKey ? 'extend' : 'move'
346-
selection.modify(type, 'forward', 'character')
336+
<div className={styles['input-wrapper']}>
337+
{props.value && (
338+
<button
339+
className={cn(styles['clear-button'], 'codicon', 'codicon-close')}
340+
onClick={() => {
341+
set_should_show_ghost_text(false)
342+
handle_clear()
343+
input_ref.current?.focus()
344+
}}
345+
title="Clear"
346+
/>
347+
)}
348+
<div
349+
ref={input_ref}
350+
contentEditable={true}
351+
suppressContentEditableWarning={true}
352+
onInput={(e) => {
353+
set_should_show_ghost_text(true)
354+
handle_input_change(e)
355+
}}
356+
onKeyDown={(e) => {
357+
if (e.key == 'ArrowRight') {
358+
set_should_show_ghost_text(false)
359+
const ghost_text_node = input_ref.current?.querySelector(
360+
'span[data-type="ghost-text"]'
361+
)
362+
if (ghost_text_node && !e.ctrlKey && !e.altKey && !e.metaKey) {
363+
ghost_text_node.remove()
364+
e.preventDefault()
365+
const selection = window.getSelection()
366+
if (selection) {
367+
const type = e.shiftKey ? 'extend' : 'move'
368+
selection.modify(type, 'forward', 'character')
369+
}
347370
}
371+
} else {
372+
set_should_show_ghost_text(true)
348373
}
349-
} else {
350-
set_should_show_ghost_text(true)
351-
}
352-
handle_key_down(e)
353-
}}
354-
onCopy={handle_copy}
355-
onPaste={handle_paste}
356-
onClick={handle_input_click}
357-
onMouseDown={handle_mouse_down}
358-
onDragStart={handle_drag_start}
359-
onDrop={handle_drop}
360-
onDragOver={handle_drag_over}
361-
onDragEnd={handle_drag_end}
362-
onFocus={() => set_is_focused(true)}
363-
onBlur={() => set_is_focused(false)}
364-
className={cn(styles.input, {
365-
[styles['input--empty']]: !props.value
366-
})}
367-
data-placeholder={placeholder}
368-
/>
374+
handle_key_down(e)
375+
}}
376+
onCopy={handle_copy}
377+
onPaste={handle_paste}
378+
onClick={handle_input_click}
379+
onMouseDown={handle_mouse_down}
380+
onDragStart={handle_drag_start}
381+
onDrop={handle_drop}
382+
onDragOver={handle_drag_over}
383+
onDragEnd={handle_drag_end}
384+
onFocus={() => set_is_focused(true)}
385+
onBlur={() => set_is_focused(false)}
386+
className={cn(styles.input, {
387+
[styles['input--empty']]: !props.value
388+
})}
389+
data-placeholder={placeholder}
390+
/>
391+
</div>
369392

370393
<div
371394
className={styles.footer}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ export const use_handlers = (
445445
)
446446
if (file_keyword_element) {
447447
const file_path = file_keyword_element.getAttribute('title')
448-
if (file_path && props.on_go_to_file) {
448+
if (file_path) {
449449
props.on_go_to_file(file_path)
450450
}
451451
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { useState, useRef, useEffect } from 'react'
2+
import type { PromptFieldProps } from '../PromptField'
3+
4+
type UsePruneContextInstructionsParams = Pick<
5+
PromptFieldProps,
6+
| 'prune_context_instructions_prefix'
7+
| 'on_prune_context_instructions_prefix_change'
8+
>
9+
10+
export const use_prune_context_instructions = ({
11+
prune_context_instructions_prefix,
12+
on_prune_context_instructions_prefix_change
13+
}: UsePruneContextInstructionsParams) => {
14+
const [prune_instructions, set_prune_instructions] = useState(
15+
prune_context_instructions_prefix
16+
)
17+
18+
const last_synced_prune_instructions_ref = useRef(
19+
prune_context_instructions_prefix
20+
)
21+
22+
useEffect(() => {
23+
if (
24+
prune_context_instructions_prefix !==
25+
last_synced_prune_instructions_ref.current
26+
) {
27+
set_prune_instructions(prune_context_instructions_prefix)
28+
last_synced_prune_instructions_ref.current =
29+
prune_context_instructions_prefix
30+
}
31+
}, [prune_context_instructions_prefix])
32+
33+
useEffect(() => {
34+
const timer = setTimeout(() => {
35+
if (prune_instructions != prune_context_instructions_prefix) {
36+
last_synced_prune_instructions_ref.current = prune_instructions
37+
on_prune_context_instructions_prefix_change(prune_instructions)
38+
}
39+
}, 500)
40+
41+
return () => clearTimeout(timer)
42+
}, [
43+
prune_instructions,
44+
prune_context_instructions_prefix,
45+
on_prune_context_instructions_prefix_change
46+
])
47+
48+
return {
49+
prune_instructions,
50+
set_prune_instructions
51+
}
52+
}

‎packages/vscode/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@
685685
"scope": "resource",
686686
"default": "You're a helpful coding assistant. Don't explain changes in inline comments."
687687
},
688-
"codeWebChat.pruneContextInstructions": {
688+
"codeWebChat.pruneContextInstructionsPrefix": {
689689
"type": "string",
690690
"scope": "resource"
691691
},

0 commit comments

Comments
 (0)