@@ -13,65 +13,70 @@ import * as jsYaml from 'js-yaml';
13
13
import { Task } from '../tekton' ;
14
14
import { telemetryLogError } from '../telemetry' ;
15
15
import { ContextType } from '../context-type' ;
16
+ import * as _ from 'lodash' ;
16
17
17
18
interface ProviderMetadata {
18
19
getProviderMetadata ( ) : vscode . CodeActionProviderMetadata ;
19
20
}
20
21
21
- interface TaskInlineAction extends vscode . CodeAction {
22
+ const INLINE_TASK = vscode . CodeActionKind . RefactorInline . append ( 'TektonTask' ) ;
23
+ const EXTRACT_TASK = vscode . CodeActionKind . RefactorExtract . append ( 'TektonTask' ) ;
24
+
25
+ interface InlineTaskAction extends vscode . CodeAction {
22
26
taskRefStartPosition ?: vscode . Position ;
23
27
taskRefEndPosition ?: vscode . Position ;
24
28
taskRefName ?: string ;
25
29
taskKind ?: string ;
26
30
documentUri ?: vscode . Uri ;
27
31
}
28
32
33
+ function isTaskInlineAction ( action : vscode . CodeAction ) : action is InlineTaskAction {
34
+ return action . kind . contains ( INLINE_TASK ) ;
35
+ }
36
+
37
+ interface ExtractTaskAction extends vscode . CodeAction {
38
+ documentUri ?: vscode . Uri ;
39
+ taskSpecText ?: string ;
40
+ taskSpecStartPosition ?: vscode . Position ;
41
+ taskSpecEndPosition ?: vscode . Position ;
42
+ }
43
+
29
44
class PipelineCodeActionProvider implements vscode . CodeActionProvider {
30
45
provideCodeActions ( document : vscode . TextDocument , range : vscode . Range | vscode . Selection ) : vscode . ProviderResult < vscode . CodeAction [ ] > {
31
46
const result = [ ] ;
32
47
const tknDocs = yamlLocator . getTknDocuments ( document ) ;
33
48
for ( const tknDoc of tknDocs ) {
34
49
const selectedElement = this . findTask ( tknDoc , range . start ) ;
35
50
if ( selectedElement ) {
36
- const taskRefName = selectedElement . taskRef ?. name . value
37
- if ( ! taskRefName ) {
38
- continue ;
51
+
52
+ const inlineAction = this . getInlineAction ( selectedElement , document ) ;
53
+ if ( inlineAction ) {
54
+ result . push ( inlineAction ) ;
55
+ }
56
+
57
+ const extractAction = this . getExtractTaskAction ( selectedElement , document ) ;
58
+ if ( extractAction ) {
59
+ result . push ( extractAction ) ;
39
60
}
40
- const action : TaskInlineAction = new vscode . CodeAction ( `Inline '${ taskRefName } ' Task spec` , vscode . CodeActionKind . RefactorInline . append ( 'TektonTask' ) ) ;
41
- const startPos = document . positionAt ( selectedElement . taskRef ?. keyNode ?. startPosition ) ;
42
- const endPos = document . positionAt ( selectedElement . taskRef ?. endPosition ) ;
43
- action . taskRefStartPosition = startPos ;
44
- action . taskRefEndPosition = endPos ;
45
- action . taskRefName = taskRefName ;
46
- action . taskKind = selectedElement . taskRef ?. kind . value ;
47
- action . documentUri = document . uri ;
48
- result . push ( action ) ;
49
61
}
50
62
}
51
63
52
64
return result ;
53
65
}
54
- resolveCodeAction ?( codeAction : TaskInlineAction ) : Thenable < vscode . CodeAction > {
55
- return vscode . window . withProgress ( { location : vscode . ProgressLocation . Notification , cancellable : false , title : `Loading '${ codeAction . taskRefName } ' Task...` } , async ( ) : Promise < vscode . CodeAction > => {
56
- const uri = tektonFSUri ( codeAction . taskKind === TektonYamlType . ClusterTask ? ContextType . CLUSTERTASK : ContextType . TASK , codeAction . taskRefName , 'yaml' ) ;
57
- try {
58
- const taskDoc = await tektonVfsProvider . loadTektonDocument ( uri , false ) ;
59
- codeAction . edit = new vscode . WorkspaceEdit ( ) ;
60
- codeAction . edit . replace ( codeAction . documentUri ,
61
- new vscode . Range ( codeAction . taskRefStartPosition , codeAction . taskRefEndPosition ) ,
62
- this . extractTaskDef ( taskDoc , codeAction . taskRefStartPosition . character , codeAction . taskRefEndPosition . character ) ) ;
63
- } catch ( err ) {
64
- vscode . window . showErrorMessage ( 'Cannot get Tekton Task definition: ' + err . toString ( ) ) ;
65
- telemetryLogError ( 'resolveCodeAction' , `Cannot get '${ codeAction . taskRefName } ' Task definition` ) ;
66
- }
67
- return codeAction ;
68
- } ) ;
66
+ resolveCodeAction ?( codeAction : vscode . CodeAction ) : Thenable < vscode . CodeAction > {
67
+ if ( isTaskInlineAction ( codeAction ) ) {
68
+ return this . resolveInlineAction ( codeAction ) ;
69
+ }
70
+
71
+ if ( codeAction . kind . contains ( EXTRACT_TASK ) ) {
72
+ return this . resolveExtractTaskAction ( codeAction ) ;
73
+ }
69
74
70
75
}
71
76
72
77
getProviderMetadata ( ) : vscode . CodeActionProviderMetadata {
73
78
return {
74
- providedCodeActionKinds : [ vscode . CodeActionKind . RefactorInline . append ( 'TektonTask' ) ] ,
79
+ providedCodeActionKinds : [ INLINE_TASK , EXTRACT_TASK ] ,
75
80
}
76
81
}
77
82
@@ -97,6 +102,137 @@ class PipelineCodeActionProvider implements vscode.CodeActionProvider {
97
102
98
103
}
99
104
105
+ private getInlineAction ( selectedElement : PipelineTask , document : vscode . TextDocument ) : InlineTaskAction | undefined {
106
+ const taskRefName = selectedElement . taskRef ?. name . value
107
+ if ( ! taskRefName ) {
108
+ return ;
109
+ }
110
+ const action : InlineTaskAction = new vscode . CodeAction ( `Inline '${ taskRefName } ' Task spec` , INLINE_TASK ) ;
111
+ const startPos = document . positionAt ( selectedElement . taskRef ?. keyNode ?. startPosition ) ;
112
+ const endPos = document . positionAt ( selectedElement . taskRef ?. endPosition ) ;
113
+ action . taskRefStartPosition = startPos ;
114
+ action . taskRefEndPosition = endPos ;
115
+ action . taskRefName = taskRefName ;
116
+ action . taskKind = selectedElement . taskRef ?. kind . value ;
117
+ action . documentUri = document . uri ;
118
+
119
+ return action ;
120
+ }
121
+
122
+ private async resolveInlineAction ( codeAction : InlineTaskAction ) : Promise < InlineTaskAction > {
123
+ return vscode . window . withProgress ( { location : vscode . ProgressLocation . Notification , cancellable : false , title : `Loading '${ codeAction . taskRefName } ' Task...` } , async ( ) : Promise < vscode . CodeAction > => {
124
+ const uri = tektonFSUri ( codeAction . taskKind === TektonYamlType . ClusterTask ? ContextType . CLUSTERTASK : ContextType . TASK , codeAction . taskRefName , 'yaml' ) ;
125
+ try {
126
+ const taskDoc = await tektonVfsProvider . loadTektonDocument ( uri , false ) ;
127
+ codeAction . edit = new vscode . WorkspaceEdit ( ) ;
128
+ codeAction . edit . replace ( codeAction . documentUri ,
129
+ new vscode . Range ( codeAction . taskRefStartPosition , codeAction . taskRefEndPosition ) ,
130
+ this . extractTaskDef ( taskDoc , codeAction . taskRefStartPosition . character , codeAction . taskRefEndPosition . character ) ) ;
131
+ } catch ( err ) {
132
+ vscode . window . showErrorMessage ( 'Cannot get Tekton Task definition: ' + err . toString ( ) ) ;
133
+ telemetryLogError ( 'resolveCodeAction' , `Cannot get '${ codeAction . taskRefName } ' Task definition` ) ;
134
+ }
135
+ return codeAction ;
136
+ } ) ;
137
+ }
138
+
139
+ private getExtractTaskAction ( selectedElement : PipelineTask , document : vscode . TextDocument ) : ExtractTaskAction | undefined {
140
+ const taskSpec = selectedElement . taskSpec ;
141
+ if ( ! taskSpec ) {
142
+ return ;
143
+ }
144
+ const startPos = document . positionAt ( taskSpec . keyNode ?. startPosition ) ;
145
+ let taskSpecStartPos = document . positionAt ( taskSpec . startPosition ) ;
146
+ // start replace from stat of the line
147
+ taskSpecStartPos = document . lineAt ( taskSpecStartPos . line ) . range . start ;
148
+ let endPos = document . positionAt ( taskSpec . endPosition ) ;
149
+
150
+ // if last line is contains only spaces then replace til previous line
151
+ const lastLine = document . getText ( new vscode . Range ( endPos . line , 0 , endPos . line , endPos . character ) ) ;
152
+ if ( lastLine . trim ( ) . length === 0 ) {
153
+ endPos = document . lineAt ( endPos . line - 1 ) . range . end ;
154
+ }
155
+
156
+ const action : ExtractTaskAction = new vscode . CodeAction ( `Extract '${ selectedElement . name . value } ' Task spec` , EXTRACT_TASK ) ;
157
+ action . documentUri = document . uri ;
158
+ action . taskSpecStartPosition = startPos ;
159
+ action . taskSpecEndPosition = endPos ;
160
+ action . taskSpecText = document . getText ( new vscode . Range ( taskSpecStartPos , endPos ) ) ;
161
+
162
+ return action ;
163
+ }
164
+
165
+ private async resolveExtractTaskAction ( action : ExtractTaskAction ) : Promise < vscode . CodeAction > {
166
+ const name = await vscode . window . showInputBox ( { ignoreFocusOut : true , prompt : 'Provide Task Name' } ) ;
167
+ const type = await vscode . window . showQuickPick ( [ 'Task' , 'ClusterTask' ] , { placeHolder : 'Select Task Type:' , canPickMany : false , ignoreFocusOut : true } ) ;
168
+ if ( ! type || ! name ) {
169
+ return ;
170
+ }
171
+ return vscode . window . withProgress ( { location : vscode . ProgressLocation . Notification , cancellable : false , title : 'Extracting Task...' } , async ( ) : Promise < vscode . CodeAction > => {
172
+ try {
173
+ const virtDoc = this . getDocForExtractedTask ( name , type , action . taskSpecText ) ;
174
+ const saveError = await tektonVfsProvider . saveTektonDocument ( virtDoc ) ;
175
+ if ( saveError ) {
176
+ console . error ( saveError ) ;
177
+ throw new Error ( saveError ) ;
178
+ }
179
+ const newUri = tektonFSUri ( type , name , 'yaml' ) ;
180
+ await vscode . commands . executeCommand ( 'vscode.open' , newUri ) ;
181
+ const indentation = ' ' . repeat ( action . taskSpecStartPosition . character ) ;
182
+ action . edit = new vscode . WorkspaceEdit ( ) ;
183
+ action . edit . replace ( action . documentUri , new vscode . Range ( action . taskSpecStartPosition , action . taskSpecEndPosition ) ,
184
+ `taskRef:
185
+ ${ indentation } name: ${ name }
186
+ ${ indentation } kind: ${ type } ` ) ;
187
+ } catch ( err ) {
188
+ console . error ( err ) ;
189
+ }
190
+
191
+ return action ;
192
+ } ) ;
193
+ }
194
+
195
+ private getDocForExtractedTask ( name : string , type : string , content : string ) : VirtualDocument {
196
+
197
+ const lines = content . split ( '\n' ) ;
198
+ const firstLine = lines [ 0 ] . trimLeft ( ) ;
199
+ const indentation = lines [ 0 ] . length - firstLine . length ;
200
+ lines [ 0 ] = firstLine ;
201
+ for ( let i = 1 ; i < lines . length ; i ++ ) {
202
+ lines [ i ] = lines [ i ] . slice ( indentation ) ;
203
+ }
204
+ content = lines . join ( '\n' ) ;
205
+
206
+ const taskPart = jsYaml . load ( content ) ;
207
+ let metadataPart : { } = undefined ;
208
+ if ( taskPart . metadata ) {
209
+ metadataPart = taskPart . metadata ;
210
+ delete taskPart [ 'metadata' ] ;
211
+ }
212
+
213
+ let metadataPartStr : string = undefined ;
214
+ if ( metadataPart && ! _ . isEmpty ( metadataPart ) ) {
215
+ metadataPartStr = jsYaml . dump ( metadataPart , { indent : 2 } ) ;
216
+ metadataPartStr = metadataPartStr . trimRight ( ) . split ( '\n' ) . map ( it => ' ' + it ) . join ( '\n' ) ;
217
+ }
218
+ let specContent = jsYaml . dump ( taskPart , { indent : 2 , noArrayIndent : false } ) ;
219
+ specContent = specContent . trimRight ( ) . split ( '\n' ) . map ( it => ' ' + it ) . join ( '\n' ) ;
220
+ return {
221
+ version : 1 ,
222
+ uri : vscode . Uri . file ( `file:///extracted/task/${ name } .yaml` ) ,
223
+ getText : ( ) => {
224
+ return `apiVersion: tekton.dev/v1beta1
225
+ kind: ${ type }
226
+ metadata:
227
+ name: ${ name } \n${ metadataPartStr ? metadataPartStr : '' }
228
+ spec:
229
+ ${ specContent }
230
+ ` ;
231
+ }
232
+ }
233
+
234
+ }
235
+
100
236
private extractTaskDef ( taskDoc : VirtualDocument , startPos : number , endPos ) : string {
101
237
const task : Task = jsYaml . safeLoad ( taskDoc . getText ( ) ) as Task ;
102
238
if ( ! task ) {
0 commit comments