@@ -32,6 +32,8 @@ import { ICodeMapperCodeBlock, ICodeMapperRequest, ICodeMapperResponse, ICodeMap
32
32
import { ChatUserAction , IChatService } from '../../common/chatService.js' ;
33
33
import { isResponseVM } from '../../common/chatViewModel.js' ;
34
34
import { ICodeBlockActionContext } from '../codeBlockPart.js' ;
35
+ import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js' ;
36
+ import { ILabelService } from '../../../../../platform/label/common/label.js' ;
35
37
36
38
export class InsertCodeBlockOperation {
37
39
constructor (
@@ -104,48 +106,49 @@ export class ApplyCodeBlockOperation {
104
106
constructor (
105
107
@IEditorService private readonly editorService : IEditorService ,
106
108
@ITextFileService private readonly textFileService : ITextFileService ,
107
- @ICodeEditorService private readonly codeEditorService : ICodeEditorService ,
108
109
@IChatService private readonly chatService : IChatService ,
109
110
@ILanguageService private readonly languageService : ILanguageService ,
110
111
@IFileService private readonly fileService : IFileService ,
111
112
@IDialogService private readonly dialogService : IDialogService ,
112
113
@ILogService private readonly logService : ILogService ,
113
114
@ICodeMapperService private readonly codeMapperService : ICodeMapperService ,
114
- @IProgressService private readonly progressService : IProgressService
115
+ @IProgressService private readonly progressService : IProgressService ,
116
+ @IQuickInputService private readonly quickInputService : IQuickInputService ,
117
+ @ILabelService private readonly labelService : ILabelService ,
115
118
) {
116
119
}
117
120
118
121
public async run ( context : ICodeBlockActionContext ) : Promise < void > {
119
122
let activeEditorControl = getEditableActiveCodeEditor ( this . editorService ) ;
120
123
121
- if ( context . codemapperUri && ! isEqual ( activeEditorControl ?. getModel ( ) . uri , context . codemapperUri ) ) {
122
- // If the code block is from a code mapper, first reveal the target file
123
- try {
124
- // If the file doesn't exist yet, create it
125
- if ( ! ( await this . fileService . exists ( context . codemapperUri ) ) ) {
126
- // TODO: try to find the file in the workspace
124
+ const codemapperUri = await this . evaluateURIToUse ( context . codemapperUri , activeEditorControl ) ;
125
+ if ( ! codemapperUri ) {
126
+ return ;
127
+ }
127
128
128
- await this . fileService . writeFile ( context . codemapperUri , VSBuffer . fromString ( '' ) ) ;
129
- }
130
- await this . editorService . openEditor ( { resource : context . codemapperUri } ) ;
129
+ if ( codemapperUri && ! isEqual ( activeEditorControl ?. getModel ( ) . uri , codemapperUri ) ) {
130
+ // reveal the target file
131
+ try {
132
+ await this . editorService . openEditor ( { resource : codemapperUri } ) ;
131
133
132
134
activeEditorControl = getEditableActiveCodeEditor ( this . editorService ) ;
133
135
if ( activeEditorControl ) {
134
136
this . tryToRevealCodeBlock ( activeEditorControl , context . code ) ;
135
137
}
136
138
} catch ( e ) {
137
- this . logService . info ( '[ApplyCodeBlockOperation] error opening code mapper file' , context . codemapperUri , e ) ;
139
+ this . logService . info ( '[ApplyCodeBlockOperation] error opening code mapper file' , codemapperUri , e ) ;
140
+ return ;
138
141
}
139
142
}
140
143
141
144
let result : IComputeEditsResult | undefined = undefined ;
142
145
143
146
if ( activeEditorControl ) {
144
- result = await this . handleTextEditor ( activeEditorControl , context ) ;
147
+ result = await this . handleTextEditor ( activeEditorControl , context . code ) ;
145
148
} else {
146
149
const activeNotebookEditor = getActiveNotebookEditor ( this . editorService ) ;
147
150
if ( activeNotebookEditor ) {
148
- result = await this . handleNotebookEditor ( activeNotebookEditor , context ) ;
151
+ result = await this . handleNotebookEditor ( activeNotebookEditor , context . code ) ;
149
152
} else {
150
153
this . notify ( localize ( 'applyCodeBlock.noActiveEditor' , "To apply this code block, open a code or notebook editor." ) ) ;
151
154
}
@@ -159,57 +162,98 @@ export class ApplyCodeBlockOperation {
159
162
} ) ;
160
163
}
161
164
162
- private async handleNotebookEditor ( notebookEditor : IActiveNotebookEditor , codeBlockContext : ICodeBlockActionContext ) : Promise < IComputeEditsResult | undefined > {
165
+ private async evaluateURIToUse ( resource : URI | undefined , activeEditorControl : IActiveCodeEditor | undefined ) : Promise < URI | undefined > {
166
+ if ( resource && await this . fileService . exists ( resource ) ) {
167
+ return resource ;
168
+ }
169
+
170
+ const activeEditorOption = activeEditorControl ?. getModel ( ) . uri ? { label : localize ( 'activeEditor' , "Active editor '{0}'" , this . labelService . getUriLabel ( activeEditorControl . getModel ( ) . uri , { relative : true } ) ) , id : 'activeEditor' } : undefined ;
171
+ const untitledEditorOption = { label : localize ( 'newUntitledFile' , "New untitled editor" ) , id : 'newUntitledFile' } ;
172
+
173
+ const options = [ ] ;
174
+ if ( resource ) {
175
+ // code block had an URI, but it doesn't exist
176
+ options . push ( { label : localize ( 'createFile' , "New file '{0}'" , this . labelService . getUriLabel ( resource , { relative : true } ) ) , id : 'createFile' } ) ;
177
+ options . push ( untitledEditorOption ) ;
178
+ if ( activeEditorOption ) {
179
+ options . push ( activeEditorOption ) ;
180
+ }
181
+ } else {
182
+ // code block had no URI
183
+ if ( activeEditorOption ) {
184
+ options . push ( activeEditorOption ) ;
185
+ }
186
+ options . push ( untitledEditorOption ) ;
187
+ }
188
+
189
+ const selected = options . length > 1 ? await this . quickInputService . pick ( options , { placeHolder : localize ( 'selectOption' , "Select where to apply the code block" ) } ) : options [ 0 ] ;
190
+ if ( selected ) {
191
+ switch ( selected . id ) {
192
+ case 'createFile' :
193
+ if ( resource ) {
194
+ try {
195
+ await this . fileService . writeFile ( resource , VSBuffer . fromString ( '' ) ) ;
196
+ } catch ( error ) {
197
+ this . notify ( localize ( 'applyCodeBlock.fileWriteError' , "Failed to create file: {0}" , error . message ) ) ;
198
+ return URI . from ( { scheme : 'untitled' , path : resource . path } ) ;
199
+ }
200
+ }
201
+ return resource ;
202
+ case 'newUntitledFile' :
203
+ return URI . from ( { scheme : 'untitled' , path : resource ? resource . path : 'Untitled-1' } ) ;
204
+ case 'activeEditor' :
205
+ return activeEditorControl ?. getModel ( ) . uri ;
206
+ }
207
+ }
208
+ return undefined ;
209
+ }
210
+
211
+ private async handleNotebookEditor ( notebookEditor : IActiveNotebookEditor , code : string ) : Promise < IComputeEditsResult | undefined > {
163
212
if ( notebookEditor . isReadOnly ) {
164
213
this . notify ( localize ( 'applyCodeBlock.readonlyNotebook' , "Cannot apply code block to read-only notebook editor." ) ) ;
165
214
return undefined ;
166
215
}
167
216
const focusRange = notebookEditor . getFocus ( ) ;
168
217
const next = Math . max ( focusRange . end - 1 , 0 ) ;
169
- insertCell ( this . languageService , notebookEditor , next , CellKind . Code , 'below' , codeBlockContext . code , true ) ;
218
+ insertCell ( this . languageService , notebookEditor , next , CellKind . Code , 'below' , code , true ) ;
170
219
return undefined ;
171
220
}
172
221
173
- private async handleTextEditor ( codeEditor : IActiveCodeEditor , codeBlockContext : ICodeBlockActionContext ) : Promise < IComputeEditsResult | undefined > {
222
+ private async handleTextEditor ( codeEditor : IActiveCodeEditor , code : string ) : Promise < IComputeEditsResult | undefined > {
174
223
const activeModel = codeEditor . getModel ( ) ;
175
224
if ( isReadOnly ( activeModel , this . textFileService ) ) {
176
225
this . notify ( localize ( 'applyCodeBlock.readonly' , "Cannot apply code block to read-only file." ) ) ;
177
226
return undefined ;
178
227
}
179
228
180
- const resource = codeBlockContext . codemapperUri ?? activeModel . uri ;
181
- const codeBlock = { code : codeBlockContext . code , resource, markdownBeforeBlock : undefined } ;
229
+ const codeBlock = { code, resource : activeModel . uri , markdownBeforeBlock : undefined } ;
182
230
183
231
const codeMapper = this . codeMapperService . providers [ 0 ] ?. displayName ;
184
232
if ( ! codeMapper ) {
185
233
this . notify ( localize ( 'applyCodeBlock.noCodeMapper' , "No code mapper available." ) ) ;
186
234
return undefined ;
187
235
}
188
236
let editsProposed = false ;
189
-
190
- const editorToApply = await this . codeEditorService . openCodeEditor ( { resource } , codeEditor ) ;
191
- if ( editorToApply && editorToApply . hasModel ( ) ) {
192
-
193
- const cancellationTokenSource = new CancellationTokenSource ( ) ;
194
- try {
195
- const iterable = await this . progressService . withProgress < AsyncIterable < TextEdit [ ] > > (
196
- { location : ProgressLocation . Notification , delay : 500 , sticky : true , cancellable : true } ,
197
- async progress => {
198
- progress . report ( { message : localize ( 'applyCodeBlock.progress' , "Applying code block using {0}..." , codeMapper ) } ) ;
199
- const editsIterable = this . getEdits ( codeBlock , cancellationTokenSource . token ) ;
200
- return await this . waitForFirstElement ( editsIterable ) ;
201
- } ,
202
- ( ) => cancellationTokenSource . cancel ( )
203
- ) ;
204
- editsProposed = await this . applyWithInlinePreview ( iterable , editorToApply , cancellationTokenSource ) ;
205
- } catch ( e ) {
206
- if ( ! isCancellationError ( e ) ) {
207
- this . notify ( localize ( 'applyCodeBlock.error' , "Failed to apply code block: {0}" , e . message ) ) ;
208
- }
209
- } finally {
210
- cancellationTokenSource . dispose ( ) ;
237
+ const cancellationTokenSource = new CancellationTokenSource ( ) ;
238
+ try {
239
+ const iterable = await this . progressService . withProgress < AsyncIterable < TextEdit [ ] > > (
240
+ { location : ProgressLocation . Notification , delay : 500 , sticky : true , cancellable : true } ,
241
+ async progress => {
242
+ progress . report ( { message : localize ( 'applyCodeBlock.progress' , "Applying code block using {0}..." , codeMapper ) } ) ;
243
+ const editsIterable = this . getEdits ( codeBlock , cancellationTokenSource . token ) ;
244
+ return await this . waitForFirstElement ( editsIterable ) ;
245
+ } ,
246
+ ( ) => cancellationTokenSource . cancel ( )
247
+ ) ;
248
+ editsProposed = await this . applyWithInlinePreview ( iterable , codeEditor , cancellationTokenSource ) ;
249
+ } catch ( e ) {
250
+ if ( ! isCancellationError ( e ) ) {
251
+ this . notify ( localize ( 'applyCodeBlock.error' , "Failed to apply code block: {0}" , e . message ) ) ;
211
252
}
253
+ } finally {
254
+ cancellationTokenSource . dispose ( ) ;
212
255
}
256
+
213
257
return {
214
258
editsProposed,
215
259
codeMapper
0 commit comments