@@ -2,13 +2,14 @@ import * as vscode from 'vscode'
2
2
import { getActiveRegularEditor } from '@zardoy/vscode-utils'
3
3
import { getExtensionCommandId , getExtensionSetting , registerExtensionCommand , VSCodeQuickPickItem } from 'vscode-framework'
4
4
import { showQuickPick } from '@zardoy/vscode-utils/build/quickPick'
5
- import _ from 'lodash'
5
+ import _ , { partition } from 'lodash'
6
6
import { compact } from '@zardoy/utils'
7
7
import { defaultJsSupersetLangsWithVue } from '@zardoy/vscode-utils/build/langs'
8
8
import { offsetPosition } from '@zardoy/vscode-utils/build/position'
9
+ import { relative , join } from 'path-browserify'
9
10
import { RequestOptionsTypes , RequestResponseTypes } from '../typescript/src/ipcTypes'
10
11
import { sendCommand } from './sendCommand'
11
- import { tsRangeToVscode , tsRangeToVscodeSelection , tsTextChangesToVcodeTextEdits } from './util'
12
+ import { getTsLikePath , pickFileWithQuickPick , tsRangeToVscode , tsRangeToVscodeSelection , tsTextChangesToVcodeTextEdits } from './util'
12
13
13
14
export default ( ) => {
14
15
registerExtensionCommand ( 'removeFunctionArgumentsTypesInSelection' , async ( ) => {
@@ -221,49 +222,29 @@ export default () => {
221
222
await vscode . commands . executeCommand ( getExtensionCommandId ( 'goToNodeBySyntaxKind' ) , { filterWithSelection : true } )
222
223
} )
223
224
224
- async function sendTurnIntoArrayRequest < T = RequestResponseTypes [ 'turnArrayIntoObject' ] > (
225
- range : vscode . Range ,
226
- selectedKeyName ?: string ,
227
- document = vscode . window . activeTextEditor ! . document ,
228
- ) {
229
- return sendCommand < T , RequestOptionsTypes [ 'turnArrayIntoObject' ] > ( 'turnArrayIntoObject' , {
225
+ async function getPossibleTwoStepRefactorings ( range : vscode . Range , document = vscode . window . activeTextEditor ! . document ) {
226
+ return sendCommand < RequestResponseTypes [ 'getTwoStepCodeActions' ] , RequestOptionsTypes [ 'getTwoStepCodeActions' ] > ( 'getTwoStepCodeActions' , {
230
227
document,
231
228
position : range . start ,
232
229
inputOptions : {
233
230
range : [ document . offsetAt ( range . start ) , document . offsetAt ( range . end ) ] as [ number , number ] ,
234
- selectedKeyName,
235
231
} ,
236
232
} )
237
233
}
238
234
239
- registerExtensionCommand ( 'turnArrayIntoObjectRefactoring' as any , async ( _ , arg ?: RequestResponseTypes [ 'turnArrayIntoObject' ] ) => {
240
- if ( ! arg ) return
241
- const { keysCount, totalCount, totalObjectCount } = arg
242
- const selectedKey : string | false | undefined =
243
- // eslint-disable-next-line @typescript-eslint/dot-notation
244
- arg [ 'key' ] ||
245
- ( await showQuickPick (
246
- Object . entries ( keysCount ) . map ( ( [ key , count ] ) => {
247
- const isAllowed = count === totalObjectCount
248
- return { label : `${ isAllowed ? '$(check)' : '$(close)' } ${ key } ` , value : isAllowed ? key : false , description : `${ count } hits` }
249
- } ) ,
250
- {
251
- title : `Selected available key from ${ totalObjectCount } objects (${ totalCount } elements)` ,
235
+ async function getSecondStepRefactoringData ( range : vscode . Range , secondStepData ?: any , document = vscode . window . activeTextEditor ! . document ) {
236
+ return sendCommand < RequestResponseTypes [ 'twoStepCodeActionSecondStep' ] , RequestOptionsTypes [ 'twoStepCodeActionSecondStep' ] > (
237
+ 'twoStepCodeActionSecondStep' ,
238
+ {
239
+ document,
240
+ position : range . start ,
241
+ inputOptions : {
242
+ range : [ document . offsetAt ( range . start ) , document . offsetAt ( range . end ) ] as [ number , number ] ,
243
+ data : secondStepData ,
252
244
} ,
253
- ) )
254
- if ( selectedKey === undefined || selectedKey === '' ) return
255
- if ( selectedKey === false ) {
256
- void vscode . window . showWarningMessage ( "Can't use selected key as its not used in every object" )
257
- return
258
- }
259
-
260
- const editor = vscode . window . activeTextEditor !
261
- const edits = await sendTurnIntoArrayRequest < RequestResponseTypes [ 'turnArrayIntoObjectEdit' ] > ( editor . selection , selectedKey )
262
- if ( ! edits ) throw new Error ( 'Unknown error. Try debug.' )
263
- const edit = new vscode . WorkspaceEdit ( )
264
- edit . set ( editor . document . uri , tsTextChangesToVcodeTextEdits ( editor . document , edits ) )
265
- await vscode . workspace . applyEdit ( edit )
266
- } )
245
+ } ,
246
+ )
247
+ }
267
248
268
249
registerExtensionCommand ( 'acceptRenameWithParams' as any , async ( _ , { preview = false , comments = null , strings = null , alias = null } = { } ) => {
269
250
const editor = vscode . window . activeTextEditor
@@ -284,7 +265,90 @@ export default () => {
284
265
await vscode . commands . executeCommand ( preview ? 'acceptRenameInputWithPreview' : 'acceptRenameInput' )
285
266
} )
286
267
287
- // its actually a code action, but will be removed from there soon
268
+ // #region two-steps code actions
269
+ registerExtensionCommand ( 'applyRefactor' as any , async ( _ , arg ?: RequestResponseTypes [ 'getTwoStepCodeActions' ] ) => {
270
+ if ( ! arg ) return
271
+ let sendNextData : RequestOptionsTypes [ 'twoStepCodeActionSecondStep' ] [ 'data' ] | undefined
272
+ const { turnArrayIntoObject, moveToExistingFile } = arg
273
+ if ( turnArrayIntoObject ) {
274
+ const { keysCount, totalCount, totalObjectCount } = turnArrayIntoObject
275
+ const selectedKey = await showQuickPick (
276
+ Object . entries ( keysCount ) . map ( ( [ key , count ] ) => {
277
+ const isAllowed = count === totalObjectCount
278
+ return { label : `${ isAllowed ? '$(check)' : '$(close)' } ${ key } ` , value : isAllowed ? key : false , description : `${ count } hits` }
279
+ } ) ,
280
+ {
281
+ title : `Selected available key from ${ totalObjectCount } objects (${ totalCount } elements)` ,
282
+ } ,
283
+ )
284
+ if ( selectedKey === undefined || selectedKey === '' ) return
285
+ if ( selectedKey === false ) {
286
+ void vscode . window . showWarningMessage ( "Can't use selected key as its not used in object of every element" )
287
+ return
288
+ }
289
+
290
+ sendNextData = {
291
+ name : 'turnArrayIntoObject' ,
292
+ selectedKeyName : selectedKey as string ,
293
+ }
294
+ }
295
+
296
+ if ( moveToExistingFile ) {
297
+ sendNextData = {
298
+ name : 'moveToExistingFile' ,
299
+ }
300
+ }
301
+
302
+ if ( ! sendNextData ) return
303
+ const editor = vscode . window . activeTextEditor !
304
+ const nextResponse = await getSecondStepRefactoringData ( editor . selection , sendNextData )
305
+ if ( ! nextResponse ) throw new Error ( 'No code action data. Try debug.' )
306
+ const edit = new vscode . WorkspaceEdit ( )
307
+ let mainChanges = 'edits' in nextResponse && nextResponse . edits
308
+ if ( moveToExistingFile && 'fileNames' in nextResponse ) {
309
+ const { fileNames, fileEdits } = nextResponse
310
+ const selectedFilePath = await pickFileWithQuickPick ( fileNames )
311
+ if ( ! selectedFilePath ) return
312
+ const document = await vscode . workspace . openTextDocument ( vscode . Uri . file ( selectedFilePath ) )
313
+ const outline = await vscode . commands . executeCommand ( 'vscode.executeDocumentSymbolProvider' , document . uri )
314
+ const currentEditorPath = getTsLikePath ( vscode . window . activeTextEditor ! . document . uri )
315
+ // currently ignoring other files due to https://github.com/microsoft/TypeScript/issues/32344
316
+ // TODO-high it ignores any updates in https://github.com/microsoft/TypeScript/blob/20182cf8485ca5cf360d9396ad25d939b848a0ec/src/services/refactors/moveToNewFile.ts#L290
317
+ const currentFileEdits = [ ...fileEdits . find ( fileEdit => fileEdit . fileName === currentEditorPath ) ! . textChanges ]
318
+ const textChangeIndexToPatch = currentFileEdits . findIndex ( currentFileEdit => currentFileEdit . newText . trim ( ) )
319
+ const { newText : updateImportText } = currentFileEdits [ textChangeIndexToPatch ] !
320
+ // TODO-mid use native path resolver (ext, index, alias)
321
+ let newRelativePath = relative ( join ( currentEditorPath , '..' ) , selectedFilePath )
322
+ if ( ! newRelativePath . startsWith ( './' ) && ! newRelativePath . startsWith ( '../' ) ) newRelativePath = `./${ newRelativePath } `
323
+ currentFileEdits [ textChangeIndexToPatch ] ! . newText = updateImportText . replace ( / ( [ ' " ] ) .+ ( [ ' " ] ) / , ( _m , g1 ) => `${ g1 } ${ newRelativePath } ${ g1 } ` )
324
+ mainChanges = currentFileEdits
325
+ const newFileText = fileEdits . find ( fileEdit => fileEdit . isNewFile ) ! . textChanges [ 0 ] ! . newText
326
+ const [ importLines , otherLines ] = partition ( newFileText . split ( '\n' ) , line => line . startsWith ( 'import ' ) )
327
+ const startPos = new vscode . Position ( 0 , 0 )
328
+ const newFileNodes = await sendCommand < RequestResponseTypes [ 'filterBySyntaxKind' ] > ( 'filterBySyntaxKind' , {
329
+ position : startPos ,
330
+ document,
331
+ } )
332
+ const lastImportDeclaration = newFileNodes ?. nodesByKind . ImportDeclaration ?. at ( - 1 )
333
+ const lastImportEnd = lastImportDeclaration ? tsRangeToVscode ( document , lastImportDeclaration . range ) . end : startPos
334
+ edit . set ( vscode . Uri . file ( selectedFilePath ) , [
335
+ {
336
+ range : new vscode . Range ( startPos , startPos ) ,
337
+ newText : [ ...importLines , '\n' ] . join ( '\n' ) ,
338
+ } ,
339
+ {
340
+ range : new vscode . Range ( lastImportEnd , lastImportEnd ) ,
341
+ newText : [ '\n' , ...otherLines ] . join ( '\n' ) ,
342
+ } ,
343
+ ] )
344
+ }
345
+
346
+ if ( ! mainChanges ) return
347
+ edit . set ( editor . document . uri , tsTextChangesToVcodeTextEdits ( editor . document , mainChanges ) )
348
+ await vscode . workspace . applyEdit ( edit )
349
+ } )
350
+
351
+ // most probably will be moved to ts-code-actions extension
288
352
vscode . languages . registerCodeActionsProvider ( defaultJsSupersetLangsWithVue , {
289
353
async provideCodeActions ( document , range , context , token ) {
290
354
if ( document !== vscode . window . activeTextEditor ?. document || ! getExtensionSetting ( 'enablePlugin' ) ) {
@@ -301,22 +365,34 @@ export default () => {
301
365
}
302
366
303
367
if ( context . triggerKind !== vscode . CodeActionTriggerKind . Invoke ) return
304
- const result = await sendTurnIntoArrayRequest ( range )
368
+ const result = await getPossibleTwoStepRefactorings ( range )
305
369
if ( ! result ) return
306
- const { keysCount, totalCount, totalObjectCount } = result
307
- return [
308
- {
309
- title : `Turn Array Into Object (${ totalCount } elements)` ,
310
- command : getExtensionCommandId ( 'turnArrayIntoObjectRefactoring' as any ) ,
311
- arguments : [
312
- {
313
- keysCount,
314
- totalCount,
315
- totalObjectCount,
316
- } satisfies RequestResponseTypes [ 'turnArrayIntoObject' ] ,
317
- ] ,
318
- } ,
319
- ]
370
+ const { turnArrayIntoObject, moveToExistingFile } = result
371
+ const codeActions : vscode . CodeAction [ ] = [ ]
372
+ const getCommand = ( arg ) : vscode . Command | undefined => ( {
373
+ title : '' ,
374
+ command : getExtensionCommandId ( 'applyRefactor' as any ) ,
375
+ arguments : [ arg ] ,
376
+ } )
377
+
378
+ if ( turnArrayIntoObject ) {
379
+ codeActions . push ( {
380
+ title : `Turn array into object (${ turnArrayIntoObject . totalCount } elements)` ,
381
+ command : getCommand ( { turnArrayIntoObject } ) ,
382
+ kind : vscode . CodeActionKind . RefactorRewrite ,
383
+ } )
384
+ }
385
+
386
+ if ( moveToExistingFile ) {
387
+ codeActions . push ( {
388
+ title : `Move to existing file` ,
389
+ command : getCommand ( { moveToExistingFile } ) ,
390
+ kind : vscode . CodeActionKind . Refactor . append ( 'move' ) ,
391
+ } )
392
+ }
393
+
394
+ return codeActions
320
395
} ,
321
396
} )
397
+ // #endregion
322
398
}
0 commit comments