Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/panel/editCodePrompt.tsx
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as l10n from '@vscode/l10n';
7
import { AssistantMessage, BasePromptElementProps, Chunk, PrioritizedList, PromptElement, PromptReference, PromptSizing, SystemMessage, TextChunk, UserMessage } from '@vscode/prompt-tsx';
8
import { ChatLocation } from '../../../../platform/chat/common/commonTypes';
9
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
10
import { NotebookDocumentSnapshot } from '../../../../platform/editing/common/notebookDocumentSnapshot';
11
import { TextDocumentSnapshot } from '../../../../platform/editing/common/textDocumentSnapshot';
12
import { modelPrefersInstructionsAfterHistory } from '../../../../platform/endpoint/common/chatModelCapabilities';
13
import { IIgnoreService } from '../../../../platform/ignore/common/ignoreService';
14
import { IChatEndpoint } from '../../../../platform/networking/common/networking';
15
import { IPromptPathRepresentationService } from '../../../../platform/prompts/common/promptPathRepresentationService';
16
import { IExperimentationService } from '../../../../platform/telemetry/common/nullExperimentationService';
17
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
18
import { filepathCodeBlockMarker } from '../../../../util/common/markdown';
19
import { isLocation, isUri } from '../../../../util/common/types';
20
import { ResourceMap } from '../../../../util/vs/base/common/map';
21
import { Schemas } from '../../../../util/vs/base/common/network';
22
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
23
import { Range, Uri } from '../../../../vscodeTypes';
24
import { GenericBasePromptElementProps } from '../../../context/node/resolvers/genericPanelIntentInvocation';
25
import { IEditStepBuildPromptContext, PreviousEditCodeStep } from '../../../intents/node/editCodeStep';
26
import { ChatVariablesCollection } from '../../../prompt/common/chatVariablesCollection';
27
import { Turn } from '../../../prompt/common/conversation';
28
import { INotebookWorkingSetEntry, isTextDocumentWorkingSetEntry, ITextDocumentWorkingSetEntry, IWorkingSet, WorkingSetEntryState } from '../../../prompt/common/intents';
29
import { CompositeElement } from '../base/common';
30
import { CopilotIdentityRules } from '../base/copilotIdentity';
31
import { InstructionMessage } from '../base/instructionMessage';
32
import { ResponseTranslationRules } from '../base/responseTranslationRules';
33
import { LegacySafetyRules } from '../base/safetyRules';
34
import { Tag } from '../base/tag';
35
import { DocumentSummarizer, NotebookDocumentSummarizer } from '../inline/summarizedDocument/summarizeDocumentHelpers';
36
import { ChatToolReferences, ChatVariables, UserQuery } from './chatVariables';
37
import { EXISTING_CODE_MARKER } from './codeBlockFormattingRules';
38
import { CustomInstructions } from './customInstructions';
39
import { fileVariableCostFn } from './fileVariable';
40
import { NotebookFormat, NotebookReminderInstructions } from './notebookEditCodePrompt';
41
import { ProjectLabels } from './projectLabels';
42
import { CodeBlock, ExampleCodeBlock } from './safeElements';
43
import { ChatToolCalls } from './toolCalling';
44
45
export interface EditCodePromptProps extends GenericBasePromptElementProps {
46
readonly promptContext: IEditStepBuildPromptContext;
47
readonly endpoint: IChatEndpoint;
48
readonly location: ChatLocation;
49
}
50
51
export class EditCodePrompt extends PromptElement<EditCodePromptProps> {
52
constructor(
53
props: EditCodePromptProps,
54
@IConfigurationService private readonly configurationService: IConfigurationService,
55
@IPromptPathRepresentationService private readonly promptPathRepresentationService: IPromptPathRepresentationService,
56
) {
57
super(props);
58
}
59
async render(state: void, sizing: PromptSizing) {
60
const tsExampleFilePath = '/Users/someone/proj01/example.ts';
61
62
const instructionsAfterHistory = modelPrefersInstructionsAfterHistory(this.props.endpoint.family);
63
const hasFilesInWorkingSet = this.props.promptContext.workingSet.length > 0;
64
const instructions = <InstructionMessage priority={900}>
65
{hasFilesInWorkingSet
66
? <>The user has a request for modifying one or more files.<br /></>
67
: <>If the user asks a question, then answer it.<br />
68
If you need to change existing files and it's not clear which files should be changed, then refuse and answer with "Please add the files to be modified to the working set{(this.configurationService.getConfig(ConfigKey.CodeSearchAgentEnabled) || this.configurationService.getConfig(ConfigKey.Advanced.CodeSearchAgentEnabled)) ? ', or use `#codebase` in your request to automatically discover working set files.' : ''}".<br />
69
The only exception is if you need to create new files. In that case, follow the following instructions.<br /></>}
70
1. Please come up with a solution that you first describe step-by-step.<br />
71
2. Group your changes by file. Use the file path as the header.<br />
72
3. For each file, give a short summary of what needs to be changed followed by a code block that contains the code changes.<br />
73
4. The code block should start with four backticks followed by the language.<br />
74
5. On the first line of the code block add a comment containing the filepath. This includes Markdown code blocks.<br />
75
6. Use a single code block per file that needs to be modified, even if there are multiple changes for a file.<br />
76
7. The user is very smart and can understand how to merge your code blocks into their files, you just need to provide minimal hints.<br />
77
8. Avoid repeating existing code, instead use comments to represent regions of unchanged code. The user prefers that you are as concise as possible. For example: <br />
78
<ExampleCodeBlock languageId='languageId' examplePath={'/path/to/file'} includeFilepath={true} minNumberOfBackticks={4}
79
code={
80
[
81
`// ${EXISTING_CODE_MARKER}`,
82
`{ changed code }`,
83
`// ${EXISTING_CODE_MARKER}`,
84
`{ changed code }`,
85
`// ${EXISTING_CODE_MARKER}`
86
].join('\n')
87
}
88
/><br />
89
90
<br />
91
<ResponseTranslationRules />
92
Here is an example of how you should format a code block belonging to the file example.ts in your response:<br />
93
<Tag name='example'>
94
### {this.promptPathRepresentationService.getExampleFilePath(tsExampleFilePath)}<br />
95
<br />
96
Add a new property 'age' and a new method 'getAge' to the class Person.<br />
97
<br />
98
<ExampleCodeBlock languageId='typescript' examplePath={tsExampleFilePath} includeFilepath={true} minNumberOfBackticks={4}
99
code={
100
[
101
`class Person {`,
102
` // ${EXISTING_CODE_MARKER}`,
103
` age: number;`,
104
` // ${EXISTING_CODE_MARKER}`,
105
` getAge() {`,
106
` return this.age;`,
107
` }`,
108
`}`,
109
].join('\n')
110
}
111
/><br />
112
</Tag>
113
</InstructionMessage>;
114
115
return (
116
<>
117
<SystemMessage priority={1000}>
118
You are an AI programming assistant.<br />
119
<CopilotIdentityRules />
120
<LegacySafetyRules />
121
</SystemMessage>
122
{instructionsAfterHistory ? undefined : instructions}
123
<EditCodeConversationHistory flexGrow={1} priority={700} workingSet={this.props.promptContext.workingSet} history={this.props.promptContext.history} promptInstructions={this.props.promptContext.promptInstructions} chatVariables={this.props.promptContext.chatVariables} />
124
{instructionsAfterHistory ? instructions : undefined}
125
<EditCodeUserMessage flexGrow={2} priority={900} {...this.props} />
126
<ChatToolCalls priority={899} flexGrow={3} promptContext={this.props.promptContext} toolCallRounds={this.props.promptContext.toolCallRounds} toolCallResults={this.props.promptContext.toolCallResults} />
127
</>
128
);
129
}
130
}
131
132
interface EditCodeReadonlyInstructionsProps extends BasePromptElementProps {
133
readonly chatVariables: ChatVariablesCollection;
134
readonly workingSet: IWorkingSet;
135
}
136
137
export class EditCodeReadonlyInstructions extends PromptElement<EditCodeReadonlyInstructionsProps> {
138
constructor(
139
props: EditCodeReadonlyInstructionsProps,
140
@IPromptPathRepresentationService private readonly promptPathRepresentationService: IPromptPathRepresentationService,
141
) {
142
super(props);
143
}
144
145
override render() {
146
const { readonlyUris } = this;
147
148
if (!readonlyUris.length) {
149
return <></>;
150
}
151
152
return <TextChunk>
153
{'<fileRestrictions>'}<br />
154
The following files are readonly. Making edits to any of these file paths is FORBIDDEN. If you cannot accomplish the task without editing the files, briefly explain why, but NEVER edit any of these files:<br />
155
{readonlyUris.map(uri => `\t- ${this.promptPathRepresentationService.getFilePath(uri)}`).join('\n')}<br />
156
{'</fileRestrictions>'}
157
</TextChunk>;
158
}
159
160
/**
161
* List of {@link Uri}s for all `readonly` variables, if any.
162
*/
163
private get readonlyUris(): Uri[] {
164
165
// get list of URIs for readonly variables inside the working set
166
const readonlyUris = [];
167
for (const entry of this.props.workingSet) {
168
if (entry.isMarkedReadonly) {
169
readonlyUris.push(entry.document.uri);
170
}
171
}
172
for (const variable of this.props.chatVariables) {
173
if (variable.isMarkedReadonly) {
174
if (isUri(variable.value)) {
175
readonlyUris.push(variable.value);
176
} else if (isLocation(variable.value)) {
177
readonlyUris.push(variable.value.uri);
178
}
179
}
180
}
181
return readonlyUris;
182
}
183
}
184
185
interface EditCodeConversationHistoryProps extends BasePromptElementProps {
186
readonly workingSet: IWorkingSet;
187
readonly promptInstructions: readonly TextDocumentSnapshot[];
188
readonly chatVariables: ChatVariablesCollection;
189
readonly history: readonly Turn[];
190
readonly priority: number;
191
}
192
193
class EditCodeConversationHistory extends PromptElement<EditCodeConversationHistoryProps> {
194
195
constructor(
196
props: EditCodeConversationHistoryProps,
197
@IPromptPathRepresentationService private readonly _promptPathRepresentationService: IPromptPathRepresentationService
198
) {
199
super(props);
200
}
201
202
203
override async render(state: void, sizing: PromptSizing) {
204
// Here we will keep track of which [file,version] pairs that are already in the prompt
205
const includedFilesAtVersions = new ResourceMap<number[]>();
206
207
// Populate with the current state
208
for (const entry of this.props.workingSet) {
209
includedFilesAtVersions.set(entry.document.uri, [entry.document.version]);
210
}
211
212
// Ditto for prompt instruction files
213
const includedPromptInstructions = new ResourceMap<number[]>();
214
for (const promptInstruction of this.props.promptInstructions) {
215
includedPromptInstructions.set(promptInstruction.uri, [promptInstruction.version]);
216
}
217
218
const history: (UserMessage | AssistantMessage)[] = [];
219
for (const turn of this.props.history) {
220
const editCodeStep = PreviousEditCodeStep.fromTurn(turn);
221
if (editCodeStep) {
222
history.push(this._renderUserMessageWithoutFiles(editCodeStep, includedFilesAtVersions, includedPromptInstructions));
223
history.push(this._renderAssistantMessageWithoutFileTags(editCodeStep.response));
224
}
225
}
226
227
return (<PrioritizedList priority={this.props.priority} descending={false}>{history}</PrioritizedList>);
228
}
229
230
private _renderAssistantMessageWithoutFileTags(message: string): AssistantMessage {
231
message = message.replace(/<\/?file>/g, '');
232
return (
233
<AssistantMessage>{message}</AssistantMessage>
234
);
235
}
236
237
private _renderUserMessageWithoutFiles(editCodeStep: PreviousEditCodeStep, includedFilesAtVersion: ResourceMap<number[]>, includedPromptInstructions: ResourceMap<number[]>): UserMessage {
238
const filesToRemove: Uri[] = [];
239
for (const entry of editCodeStep.workingSet) {
240
const versions = includedFilesAtVersion.get(entry.document.uri) ?? [];
241
const isAlreadyIncluded = versions.some(version => version === entry.document.version);
242
if (isAlreadyIncluded) {
243
filesToRemove.push(entry.document.uri);
244
} else {
245
versions.push(entry.document.version);
246
includedFilesAtVersion.set(entry.document.uri, versions);
247
}
248
}
249
250
const promptInstructionsToRemove: Uri[] = [];
251
for (const entry of editCodeStep.promptInstructions) {
252
const versions = includedPromptInstructions.get(entry.document.uri) ?? [];
253
const isAlreadyIncluded = versions.some(version => version === entry.document.version);
254
if (isAlreadyIncluded) {
255
promptInstructionsToRemove.push(entry.document.uri);
256
}
257
}
258
259
let userMessage = this._removePromptInstructionsFromPastUserMessage(editCodeStep.request, promptInstructionsToRemove);
260
userMessage = this._removeFilesFromPastUserMessage(userMessage, filesToRemove);
261
userMessage = this._removeReminders(userMessage);
262
return (
263
<UserMessage>{userMessage}</UserMessage>
264
);
265
}
266
267
private _removePromptInstructionsFromPastUserMessage(userMessage: string, shouldRemove: Uri[]) {
268
const interestingFilePaths = shouldRemove.map(uri => this._promptPathRepresentationService.getFilePath(uri));
269
return userMessage.replace(/<instructions>[\s\S]*?<\/instructions>/g, (match) => {
270
if (interestingFilePaths.some(path => match.includes(path))) {
271
return '';
272
}
273
return match;
274
});
275
}
276
277
private _removeFilesFromPastUserMessage(userMessage: string, shouldRemove: Uri[]) {
278
const interestingFilePaths = shouldRemove.map(uri => `${filepathCodeBlockMarker} ${this._promptPathRepresentationService.getFilePath(uri)}`);
279
return userMessage.replace(/<file(-selection)?>[\s\S]*?<\/file(-selection)?>/g, (match) => {
280
if (interestingFilePaths.some(path => match.includes(path))) {
281
return '';
282
}
283
return match;
284
});
285
}
286
287
private _removeReminders(userMessage: string) {
288
return userMessage.replace(/^<reminder>[\s\S]*?^<\/reminder>/gm, (match) => {
289
return '';
290
});
291
}
292
}
293
294
export class EditCodeUserMessage extends PromptElement<EditCodePromptProps> {
295
constructor(
296
props: EditCodePromptProps,
297
@IExperimentationService private readonly experimentationService: IExperimentationService,
298
@IConfigurationService private readonly _configurationService: IConfigurationService,
299
) {
300
super(props);
301
}
302
303
async render(state: void, sizing: PromptSizing) {
304
const { query, chatVariables, workingSet } = this.props.promptContext;
305
const useProjectLabels = this._configurationService.getExperimentBasedConfig(ConfigKey.Advanced.ProjectLabelsChat, this.experimentationService);
306
return (
307
<>
308
<UserMessage>
309
{useProjectLabels && <ProjectLabels flexGrow={1} priority={600} />}
310
<CustomInstructions flexGrow={6} priority={750} languageId={undefined} chatVariables={chatVariables} />
311
<NotebookFormat flexGrow={5} priority={810} chatVariables={workingSet} query={query} />
312
<ChatToolReferences flexGrow={4} priority={898} promptContext={this.props.promptContext} documentContext={this.props.documentContext} />
313
<ChatVariables flexGrow={3} priority={898} chatVariables={chatVariables} />
314
<WorkingSet flexGrow={3} flexReserve={sizing.tokenBudget * 0.8} priority={810} workingSet={workingSet} /><br />
315
<Tag name='reminder' flexGrow={2} priority={899} >
316
Avoid repeating existing code, instead use a line comment with `{EXISTING_CODE_MARKER}` to represent regions of unchanged code.<br />
317
The code block for each file being edited must start with a comment containing the filepath. This includes Markdown code blocks.<br />
318
For existing files, make sure the filepath exactly matches the filepath of the original file.<br />
319
<NotebookReminderInstructions chatVariables={chatVariables} query={query} />
320
<NewFilesLocationHint />
321
</Tag>
322
{query && <Tag name='prompt'><UserQuery flexGrow={7} priority={900} chatVariables={chatVariables} query={query} /></Tag>}
323
<EditCodeReadonlyInstructions chatVariables={chatVariables} workingSet={workingSet} />
324
</UserMessage>
325
</>
326
);
327
}
328
}
329
330
interface WorkingSetPromptProps extends BasePromptElementProps {
331
readonly workingSet: IWorkingSet;
332
}
333
334
export class WorkingSet extends PromptElement<WorkingSetPromptProps> {
335
public override render(state: void, sizing: PromptSizing) {
336
const { workingSet } = this.props;
337
return (
338
workingSet.length ?
339
<>
340
The user has provided the following files as input. Always make changes to these files unless the user asks to create a new file.<br />
341
Untitled files are files that are not yet named. Make changes to them like regular files.<br />
342
{workingSet.map((entry, index) => (
343
isTextDocumentWorkingSetEntry(entry) ?
344
<TextDocumentWorkingSetEntry entry={entry} flexGrow={index} /> :
345
<NotebookWorkingSetEntry entry={entry} flexGrow={index} />
346
))}
347
</> :
348
<></>
349
);
350
}
351
}
352
353
export class NewFilesLocationHint extends PromptElement {
354
constructor(
355
props: BasePromptElementProps,
356
@IWorkspaceService private readonly _workspaceService: IWorkspaceService,
357
@IPromptPathRepresentationService private readonly _promptPathRepresentationService: IPromptPathRepresentationService,
358
) {
359
super(props);
360
}
361
362
public override render(state: void, sizing: PromptSizing) {
363
const workspaceFolders = this._workspaceService.getWorkspaceFolders();
364
if (workspaceFolders.length === 1) {
365
return <>When suggesting to create new files, pick a location inside `{this._promptPathRepresentationService.getFilePath(workspaceFolders[0])}`.</>;
366
} else if (workspaceFolders.length > 0) {
367
return <>When suggesting to create new files, pick a location inside one of these root folders: {workspaceFolders.map(f => `${this._promptPathRepresentationService.getFilePath(f)}`).join(', ')}.</>;
368
} else {
369
const untitledRoot = Uri.from({ scheme: Schemas.untitled, authority: 'untitled' });
370
return <>When suggesting to create new files, pick a location inside `{this._promptPathRepresentationService.getFilePath(untitledRoot)}`.</>;
371
}
372
}
373
}
374
375
interface TextDocumentWorkingSetEntryPromptProps extends BasePromptElementProps {
376
readonly entry: ITextDocumentWorkingSetEntry;
377
}
378
379
class TextDocumentWorkingSetEntry extends PromptElement<TextDocumentWorkingSetEntryPromptProps> {
380
constructor(
381
props: TextDocumentWorkingSetEntryPromptProps,
382
@IIgnoreService private readonly _ignoreService: IIgnoreService,
383
@IInstantiationService private readonly instantiationService: IInstantiationService,
384
) {
385
super(props);
386
}
387
388
async render(state: void, sizing: PromptSizing) {
389
const { document, range: selection, state: workingSetEntryState } = this.props.entry;
390
391
const isIgnored = await this._ignoreService.isCopilotIgnored(document.uri);
392
if (isIgnored) {
393
return <ignoredFiles value={[document.uri]} />;
394
}
395
396
const s = this.instantiationService.createInstance(DocumentSummarizer);
397
const summarized = await s.summarizeDocument(document, undefined, selection, sizing.tokenBudget, {
398
costFnOverride: fileVariableCostFn,
399
});
400
401
const promptReferenceOptions = !summarized.isOriginal
402
? { status: { description: l10n.t('Part of this file was not sent to the model due to context window limitations. Try attaching specific selections from your file instead.'), kind: 2 } }
403
: undefined;
404
405
let userActionStateFragment = '';
406
if (workingSetEntryState === WorkingSetEntryState.Accepted) {
407
userActionStateFragment = 'I applied your suggestions for this file and accepted them. Here is the updated file:';
408
} else if (workingSetEntryState === WorkingSetEntryState.Rejected) {
409
userActionStateFragment = 'I considered your suggestions for this file but rejected them. Here is the file:';
410
} else if (workingSetEntryState === WorkingSetEntryState.Undecided) {
411
userActionStateFragment = 'I applied your suggestions for this file but haven\'t decided yet if I accept or reject them. Here is the updated file:';
412
}
413
414
return (
415
<CompositeElement priority={this.props.priority}>
416
<Chunk priority={2}>
417
<Tag name='file'>
418
{
419
userActionStateFragment && <>
420
<br />
421
&lt;status&gt;
422
{userActionStateFragment}
423
&lt;/status&gt;
424
<br />
425
</>
426
}
427
<CodeBlock includeFilepath={true} languageId={document.languageId} uri={document.uri} references={[new PromptReference(document.uri, undefined, promptReferenceOptions)]} code={summarized.text} />
428
</Tag>
429
</Chunk>
430
{!!selection && <FileSelection document={document} selection={selection} priority={1} />}
431
</CompositeElement>
432
);
433
}
434
}
435
436
interface NotebookWorkingSetEntryPromptProps extends BasePromptElementProps {
437
readonly entry: INotebookWorkingSetEntry;
438
}
439
440
class NotebookWorkingSetEntry extends PromptElement<NotebookWorkingSetEntryPromptProps> {
441
constructor(
442
props: NotebookWorkingSetEntryPromptProps,
443
@IIgnoreService private readonly _ignoreService: IIgnoreService,
444
@IInstantiationService private readonly instantiationService: IInstantiationService,
445
) {
446
super(props);
447
}
448
449
async render(state: void, sizing: PromptSizing) {
450
const { document, range: selection, state: workingSetEntryState } = this.props.entry;
451
452
const isIgnored = await this._ignoreService.isCopilotIgnored(document.uri);
453
if (isIgnored) {
454
return <ignoredFiles value={[document.uri]} />;
455
}
456
457
// TODO@rebornix ensure notebook is open
458
const s = this.instantiationService.createInstance(NotebookDocumentSummarizer);
459
const summarized = await s.summarizeDocument(document, undefined, selection, sizing.tokenBudget, {
460
costFnOverride: fileVariableCostFn,
461
});
462
463
const promptReferenceOptions = !summarized.isOriginal
464
? { status: { description: l10n.t('Part of this file was not sent to the model due to context window limitations. Try attaching specific selections from your file instead.'), kind: 2 } }
465
: undefined;
466
467
let userActionStateFragment = '';
468
if (workingSetEntryState === WorkingSetEntryState.Accepted) {
469
userActionStateFragment = 'I applied your suggestions for this file and accepted them. Here is the updated file:';
470
} else if (workingSetEntryState === WorkingSetEntryState.Rejected) {
471
userActionStateFragment = 'I considered your suggestions for this file but rejected them. Here is the file:';
472
} else if (workingSetEntryState === WorkingSetEntryState.Undecided) {
473
userActionStateFragment = 'I applied your suggestions for this file but haven\'t decided yet if I accept or reject them. Here is the updated file:';
474
}
475
// Kernel variables are useful only if we're in inline chat mode.
476
// This is the logic we used to have with inline chat for notebooks.
477
return (
478
<CompositeElement priority={this.props.priority}>
479
<Chunk priority={2}>
480
This is a notebook file: <br />
481
<Tag name='file'>
482
{
483
userActionStateFragment && <>
484
<br />
485
&lt;status&gt;
486
{userActionStateFragment}
487
&lt;/status&gt;
488
<br />
489
</>
490
}
491
<CodeBlock includeFilepath={true} languageId={document.languageId} uri={document.uri} references={[new PromptReference(document.uri, undefined, promptReferenceOptions)]} code={summarized.text} />
492
</Tag>
493
</Chunk>
494
{!!selection && <FileSelection document={document} selection={selection} priority={1} />}
495
</CompositeElement>
496
);
497
}
498
}
499
500
501
interface CurrentFileSelectionPromptProps extends BasePromptElementProps {
502
document: TextDocumentSnapshot | NotebookDocumentSnapshot | undefined;
503
selection: Range | undefined;
504
}
505
506
class FileSelection extends PromptElement<CurrentFileSelectionPromptProps> {
507
constructor(
508
props: CurrentFileSelectionPromptProps,
509
@IIgnoreService private readonly _ignoreService: IIgnoreService
510
) {
511
super(props);
512
}
513
514
async render(state: void, sizing: PromptSizing) {
515
const { document, selection } = this.props;
516
517
if (!document || !selection) {
518
return undefined;
519
}
520
521
const isIgnored = await this._ignoreService.isCopilotIgnored(document.uri);
522
if (isIgnored) {
523
return <ignoredFiles value={[document.uri]} />;
524
}
525
526
if (document.lineCount >= 4) {
527
const selectionLines: string[] = [];
528
const charactersInSelectionLines = () => selectionLines.reduce((acc, line) => acc + line.length, 0);
529
let selectionStartLine = Math.min(
530
document.lineCount - 1,
531
Math.max(0, selection.start.line));
532
let selectionEndLine = Math.min(document.lineCount - 1, selection.end.line);
533
if (selectionEndLine > selectionStartLine && selection.end.character === 0) {
534
selectionEndLine--;
535
}
536
if (selectionStartLine < selectionEndLine && selection.start.character === document.lineAt(selectionStartLine).text.length) {
537
selectionStartLine++;
538
}
539
for (let i = selectionStartLine; i <= selectionEndLine; i++) {
540
const line = document.lineAt(i);
541
selectionLines.push(line.text);
542
}
543
// render at least 4 lines as selected
544
let above = selectionStartLine - 1;
545
let below = selectionEndLine + 1;
546
while (selectionLines.length < 4 && charactersInSelectionLines() < 10) {
547
if (above >= 0) {
548
selectionLines.unshift(document.lineAt(above).text);
549
above--;
550
}
551
if (below < document.lineCount) {
552
selectionLines.push(document.lineAt(below).text);
553
below++;
554
}
555
}
556
557
// TODO@tags: adopt tags here once <Tag> fixes whitespace problems
558
return (
559
<Chunk>
560
&lt;file-selection&gt;
561
<CodeBlock includeFilepath={true} languageId={document.languageId} uri={document.uri} references={[new PromptReference(document.uri, undefined)]} code={selectionLines.join('\n')} shouldTrim={false} /><br />
562
&lt;/file-selection&gt;
563
</Chunk>
564
);
565
} else {
566
return undefined;
567
}
568
}
569
}
570
571