@@ -2,13 +2,14 @@ import * as vscode from 'vscode'
22import { getActiveRegularEditor } from '@zardoy/vscode-utils'
33import { getExtensionCommandId , getExtensionSetting , registerExtensionCommand , VSCodeQuickPickItem } from 'vscode-framework'
44import { showQuickPick } from '@zardoy/vscode-utils/build/quickPick'
5- import _ from 'lodash'
5+ import _ , { partition } from 'lodash'
66import { compact } from '@zardoy/utils'
77import { defaultJsSupersetLangsWithVue } from '@zardoy/vscode-utils/build/langs'
88import { offsetPosition } from '@zardoy/vscode-utils/build/position'
9+ import { relative , join } from 'path-browserify'
910import { RequestOptionsTypes , RequestResponseTypes } from '../typescript/src/ipcTypes'
1011import { sendCommand } from './sendCommand'
11- import { tsRangeToVscode , tsRangeToVscodeSelection , tsTextChangesToVcodeTextEdits } from './util'
12+ import { getTsLikePath , pickFileWithQuickPick , tsRangeToVscode , tsRangeToVscodeSelection , tsTextChangesToVcodeTextEdits } from './util'
1213
1314export default ( ) => {
1415 registerExtensionCommand ( 'removeFunctionArgumentsTypesInSelection' , async ( ) => {
@@ -221,49 +222,29 @@ export default () => {
221222 await vscode . commands . executeCommand ( getExtensionCommandId ( 'goToNodeBySyntaxKind' ) , { filterWithSelection : true } )
222223 } )
223224
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' , {
230227 document,
231228 position : range . start ,
232229 inputOptions : {
233230 range : [ document . offsetAt ( range . start ) , document . offsetAt ( range . end ) ] as [ number , number ] ,
234- selectedKeyName,
235231 } ,
236232 } )
237233 }
238234
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 ,
252244 } ,
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+ }
267248
268249 registerExtensionCommand ( 'acceptRenameWithParams' as any , async ( _ , { preview = false , comments = null , strings = null , alias = null } = { } ) => {
269250 const editor = vscode . window . activeTextEditor
@@ -284,7 +265,90 @@ export default () => {
284265 await vscode . commands . executeCommand ( preview ? 'acceptRenameInputWithPreview' : 'acceptRenameInput' )
285266 } )
286267
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
288352 vscode . languages . registerCodeActionsProvider ( defaultJsSupersetLangsWithVue , {
289353 async provideCodeActions ( document , range , context , token ) {
290354 if ( document !== vscode . window . activeTextEditor ?. document || ! getExtensionSetting ( 'enablePlugin' ) ) {
@@ -301,22 +365,34 @@ export default () => {
301365 }
302366
303367 if ( context . triggerKind !== vscode . CodeActionTriggerKind . Invoke ) return
304- const result = await sendTurnIntoArrayRequest ( range )
368+ const result = await getPossibleTwoStepRefactorings ( range )
305369 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
320395 } ,
321396 } )
397+ // #endregion
322398}
0 commit comments