@@ -6,6 +6,9 @@ import { Span } from 'opentracing';
6
6
import * as path from 'path' ;
7
7
import * as ts from 'typescript' ;
8
8
import {
9
+ ApplyWorkspaceEditParams ,
10
+ CodeActionParams ,
11
+ Command ,
9
12
CompletionItem ,
10
13
CompletionItemKind ,
11
14
CompletionList ,
@@ -14,10 +17,12 @@ import {
14
17
DidOpenTextDocumentParams ,
15
18
DidSaveTextDocumentParams ,
16
19
DocumentSymbolParams ,
20
+ ExecuteCommandParams ,
17
21
Hover ,
18
22
Location ,
19
23
MarkedString ,
20
24
ParameterInformation ,
25
+ Range as LSRange ,
21
26
ReferenceParams ,
22
27
RenameParams ,
23
28
SignatureHelp ,
@@ -69,6 +74,29 @@ export interface TypeScriptServiceOptions {
69
74
70
75
export type TypeScriptServiceFactory = ( client : LanguageClient , options ?: TypeScriptServiceOptions ) => TypeScriptService ;
71
76
77
+ // defaults from https://github.com/Microsoft/vscode/blob/master/tsfmt.json
78
+ // A formattingProvider could be implemented to read editorconfig etc.
79
+ const formatSettings : ts . FormatCodeSettings = {
80
+ indentSize : 4 ,
81
+ tabSize : 4 ,
82
+ newLineCharacter : '\n' ,
83
+ convertTabsToSpaces : false ,
84
+ indentStyle : ts . IndentStyle . Smart ,
85
+ insertSpaceAfterCommaDelimiter : true ,
86
+ insertSpaceAfterSemicolonInForStatements : true ,
87
+ insertSpaceBeforeAndAfterBinaryOperators : true ,
88
+ insertSpaceAfterKeywordsInControlFlowStatements : true ,
89
+ insertSpaceAfterFunctionKeywordForAnonymousFunctions : false ,
90
+ insertSpaceBeforeFunctionParenthesis : false ,
91
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis : false ,
92
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets : false ,
93
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces : true ,
94
+ insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces : false ,
95
+ insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces : false ,
96
+ placeOpenBraceOnNewLineForFunctions : false ,
97
+ placeOpenBraceOnNewLineForControlBlocks : false
98
+ } ;
99
+
72
100
/**
73
101
* Handles incoming requests and return responses. There is a one-to-one-to-one
74
102
* correspondence between TCP connection, TypeScriptService instance, and
@@ -210,6 +238,11 @@ export class TypeScriptService {
210
238
resolveProvider : false ,
211
239
triggerCharacters : [ '.' ]
212
240
} ,
241
+ codeActionProvider : true ,
242
+ renameProvider : true ,
243
+ executeCommandProvider : {
244
+ commands : [ ]
245
+ } ,
213
246
xpackagesProvider : true
214
247
}
215
248
} ;
@@ -1008,6 +1041,94 @@ export class TypeScriptService {
1008
1041
. map ( signatureHelp => ( { op : 'add' , path : '' , value : signatureHelp } ) as AddPatch ) ;
1009
1042
}
1010
1043
1044
+ textDocumentCodeAction ( params : CodeActionParams , span = new Span ( ) ) : Observable < OpPatch > {
1045
+ return this . projectManager . ensureReferencedFiles ( params . textDocument . uri , undefined , undefined , span )
1046
+ . toArray ( )
1047
+ . map ( ( ) : Command [ ] => {
1048
+ const filePath = uri2path ( params . textDocument . uri ) ;
1049
+ const configuration = this . projectManager . getConfiguration ( filePath ) ;
1050
+ configuration . ensureBasicFiles ( span ) ;
1051
+
1052
+ const sourceFile = this . _getSourceFile ( configuration , filePath , span ) ;
1053
+ if ( ! sourceFile ) {
1054
+ throw new Error ( `expected source file ${ filePath } to exist in configuration` ) ;
1055
+ }
1056
+
1057
+ const start : number = ts . getPositionOfLineAndCharacter ( sourceFile , params . range . start . line , params . range . start . character ) ;
1058
+ const end : number = ts . getPositionOfLineAndCharacter ( sourceFile , params . range . end . line , params . range . end . character ) ;
1059
+ const errorCodes : number [ ] = [ ] ;
1060
+ for ( const diagnostic of params . context . diagnostics ) {
1061
+ if ( typeof ( diagnostic . code ) === 'number' ) {
1062
+ errorCodes . push ( diagnostic . code ) ;
1063
+ }
1064
+ }
1065
+
1066
+ const fixes : ts . CodeAction [ ] = configuration . getService ( ) . getCodeFixesAtPosition ( filePath , start , end , errorCodes , formatSettings ) ;
1067
+ if ( ! fixes ) {
1068
+ return [ ] ;
1069
+ }
1070
+
1071
+ return fixes . map ( fix => Command . create ( fix . description , 'codeFix' , ...fix . changes ) ) ;
1072
+ } )
1073
+ . map ( command => ( { op : 'add' , path : '' , value : command } ) as AddPatch ) ;
1074
+ }
1075
+
1076
+ workspaceExecuteCommand ( params : ExecuteCommandParams , span = new Span ( ) ) : Observable < OpPatch > {
1077
+ switch ( params . command ) {
1078
+ case 'codeFix' :
1079
+ if ( params . arguments && params . arguments . length === 1 ) {
1080
+ return Observable
1081
+ . fromPromise ( this . executeCodeFixCommand ( params . arguments , span ) )
1082
+ . map ( result => ( { op : 'add' , path : '' , value : result } ) as OpPatch ) ;
1083
+ } else {
1084
+ throw new Error ( `Command ${ params . command } requires arguments` ) ;
1085
+ }
1086
+ default :
1087
+ throw new Error ( `Unknown command ${ params . command } ` ) ;
1088
+ }
1089
+ }
1090
+
1091
+ convertTextChange ( textChange : ts . TextChange , sourceFile : ts . SourceFile ) : TextEdit {
1092
+ const start = ts . getLineAndCharacterOfPosition ( sourceFile , textChange . span . start ) ;
1093
+ if ( textChange . span . length ) {
1094
+ const end = ts . getLineAndCharacterOfPosition ( sourceFile , textChange . span . start + textChange . span . length ) ;
1095
+ const range = LSRange . create ( start , end ) ;
1096
+ if ( textChange . newText ) {
1097
+ return TextEdit . replace ( range , textChange . newText ) ;
1098
+ } else {
1099
+ return TextEdit . del ( range ) ;
1100
+ }
1101
+ } else {
1102
+ return TextEdit . insert ( start , textChange . newText ) ;
1103
+ }
1104
+ }
1105
+
1106
+ async executeCodeFixCommand ( fileTextChanges : ts . FileTextChanges [ ] , span = new Span ( ) ) : Promise < void > {
1107
+ if ( fileTextChanges . length === 0 ) {
1108
+ throw new Error ( 'No changes supplied for code fix command' ) ;
1109
+ }
1110
+
1111
+ await this . projectManager . ensureOwnFiles ( span ) ;
1112
+ const configuration = this . projectManager . getConfiguration ( fileTextChanges [ 0 ] . fileName ) ;
1113
+ configuration . ensureBasicFiles ( span ) ;
1114
+ const fileToEdits : { [ uri : string ] : TextEdit [ ] } = { } ;
1115
+ for ( const change of fileTextChanges ) {
1116
+ const sourceFile = this . _getSourceFile ( configuration , change . fileName , span ) ;
1117
+ if ( ! sourceFile ) {
1118
+ throw new Error ( `expected source file ${ change . fileName } to exist in configuration` ) ;
1119
+ }
1120
+ fileToEdits [ path2uri ( this . root , change . fileName ) ] =
1121
+ change . textChanges . map ( tc => this . convertTextChange ( tc , sourceFile ) ) ;
1122
+ }
1123
+
1124
+ const params : ApplyWorkspaceEditParams = {
1125
+ edit : {
1126
+ changes : fileToEdits
1127
+ }
1128
+ } ;
1129
+ return this . client . workspaceApplyEdit ( params ) . then ( r => undefined ) ;
1130
+ }
1131
+
1011
1132
/**
1012
1133
* The rename request is sent from the client to the server to perform a workspace-wide rename of a symbol.
1013
1134
*
0 commit comments