@@ -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 ,
@@ -70,6 +75,29 @@ export interface TypeScriptServiceOptions {
70
75
71
76
export type TypeScriptServiceFactory = ( client : LanguageClient , options ?: TypeScriptServiceOptions ) => TypeScriptService ;
72
77
78
+ // defaults from https://github.com/Microsoft/vscode/blob/master/tsfmt.json
79
+ // A formattingProvider could be implemented to read editorconfig etc.
80
+ const formatSettings : ts . FormatCodeSettings = {
81
+ indentSize : 4 ,
82
+ tabSize : 4 ,
83
+ newLineCharacter : '\n' ,
84
+ convertTabsToSpaces : false ,
85
+ indentStyle : ts . IndentStyle . Smart ,
86
+ insertSpaceAfterCommaDelimiter : true ,
87
+ insertSpaceAfterSemicolonInForStatements : true ,
88
+ insertSpaceBeforeAndAfterBinaryOperators : true ,
89
+ insertSpaceAfterKeywordsInControlFlowStatements : true ,
90
+ insertSpaceAfterFunctionKeywordForAnonymousFunctions : false ,
91
+ insertSpaceBeforeFunctionParenthesis : false ,
92
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis : false ,
93
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets : false ,
94
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces : true ,
95
+ insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces : false ,
96
+ insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces : false ,
97
+ placeOpenBraceOnNewLineForFunctions : false ,
98
+ placeOpenBraceOnNewLineForControlBlocks : false
99
+ } ;
100
+
73
101
/**
74
102
* Handles incoming requests and return responses. There is a one-to-one-to-one
75
103
* correspondence between TCP connection, TypeScriptService instance, and
@@ -211,6 +239,11 @@ export class TypeScriptService {
211
239
resolveProvider : false ,
212
240
triggerCharacters : [ '.' ]
213
241
} ,
242
+ codeActionProvider : true ,
243
+ renameProvider : true ,
244
+ executeCommandProvider : {
245
+ commands : [ ]
246
+ } ,
214
247
xpackagesProvider : true
215
248
}
216
249
} ;
@@ -990,6 +1023,94 @@ export class TypeScriptService {
990
1023
. map ( signatureHelp => ( { op : 'add' , path : '' , value : signatureHelp } ) as AddPatch ) ;
991
1024
}
992
1025
1026
+ textDocumentCodeAction ( params : CodeActionParams , span = new Span ( ) ) : Observable < OpPatch > {
1027
+ return this . projectManager . ensureReferencedFiles ( params . textDocument . uri , undefined , undefined , span )
1028
+ . toArray ( )
1029
+ . map ( ( ) : Command [ ] => {
1030
+ const filePath = uri2path ( params . textDocument . uri ) ;
1031
+ const configuration = this . projectManager . getConfiguration ( filePath ) ;
1032
+ configuration . ensureBasicFiles ( span ) ;
1033
+
1034
+ const sourceFile = this . _getSourceFile ( configuration , filePath , span ) ;
1035
+ if ( ! sourceFile ) {
1036
+ throw new Error ( `expected source file ${ filePath } to exist in configuration` ) ;
1037
+ }
1038
+
1039
+ const start : number = ts . getPositionOfLineAndCharacter ( sourceFile , params . range . start . line , params . range . start . character ) ;
1040
+ const end : number = ts . getPositionOfLineAndCharacter ( sourceFile , params . range . end . line , params . range . end . character ) ;
1041
+ const errorCodes : number [ ] = [ ] ;
1042
+ for ( const diagnostic of params . context . diagnostics ) {
1043
+ if ( typeof ( diagnostic . code ) === 'number' ) {
1044
+ errorCodes . push ( diagnostic . code ) ;
1045
+ }
1046
+ }
1047
+
1048
+ const fixes : ts . CodeAction [ ] = configuration . getService ( ) . getCodeFixesAtPosition ( filePath , start , end , errorCodes , formatSettings ) ;
1049
+ if ( ! fixes ) {
1050
+ return [ ] ;
1051
+ }
1052
+
1053
+ return fixes . map ( fix => Command . create ( fix . description , 'codeFix' , ...fix . changes ) ) ;
1054
+ } )
1055
+ . map ( command => ( { op : 'add' , path : '' , value : command } ) as AddPatch ) ;
1056
+ }
1057
+
1058
+ workspaceExecuteCommand ( params : ExecuteCommandParams , span = new Span ( ) ) : Observable < OpPatch > {
1059
+ switch ( params . command ) {
1060
+ case 'codeFix' :
1061
+ if ( params . arguments && params . arguments . length === 1 ) {
1062
+ return Observable
1063
+ . fromPromise ( this . executeCodeFixCommand ( params . arguments , span ) )
1064
+ . map ( result => ( { op : 'add' , path : '' , value : result } ) as OpPatch ) ;
1065
+ } else {
1066
+ throw new Error ( `Command ${ params . command } requires arguments` ) ;
1067
+ }
1068
+ default :
1069
+ throw new Error ( `Unknown command ${ params . command } ` ) ;
1070
+ }
1071
+ }
1072
+
1073
+ convertTextChange ( textChange : ts . TextChange , sourceFile : ts . SourceFile ) : TextEdit {
1074
+ const start = ts . getLineAndCharacterOfPosition ( sourceFile , textChange . span . start ) ;
1075
+ if ( textChange . span . length ) {
1076
+ const end = ts . getLineAndCharacterOfPosition ( sourceFile , textChange . span . start + textChange . span . length ) ;
1077
+ const range = LSRange . create ( start , end ) ;
1078
+ if ( textChange . newText ) {
1079
+ return TextEdit . replace ( range , textChange . newText ) ;
1080
+ } else {
1081
+ return TextEdit . del ( range ) ;
1082
+ }
1083
+ } else {
1084
+ return TextEdit . insert ( start , textChange . newText ) ;
1085
+ }
1086
+ }
1087
+
1088
+ async executeCodeFixCommand ( fileTextChanges : ts . FileTextChanges [ ] , span = new Span ( ) ) : Promise < void > {
1089
+ if ( fileTextChanges . length === 0 ) {
1090
+ throw new Error ( 'No changes supplied for code fix command' ) ;
1091
+ }
1092
+
1093
+ await this . projectManager . ensureOwnFiles ( span ) ;
1094
+ const configuration = this . projectManager . getConfiguration ( fileTextChanges [ 0 ] . fileName ) ;
1095
+ configuration . ensureBasicFiles ( span ) ;
1096
+ const fileToEdits : { [ uri : string ] : TextEdit [ ] } = { } ;
1097
+ for ( const change of fileTextChanges ) {
1098
+ const sourceFile = this . _getSourceFile ( configuration , change . fileName , span ) ;
1099
+ if ( ! sourceFile ) {
1100
+ throw new Error ( `expected source file ${ change . fileName } to exist in configuration` ) ;
1101
+ }
1102
+ fileToEdits [ path2uri ( this . root , change . fileName ) ] =
1103
+ change . textChanges . map ( tc => this . convertTextChange ( tc , sourceFile ) ) ;
1104
+ }
1105
+
1106
+ const params : ApplyWorkspaceEditParams = {
1107
+ edit : {
1108
+ changes : fileToEdits
1109
+ }
1110
+ } ;
1111
+ return this . client . workspaceApplyEdit ( params ) . then ( r => undefined ) ;
1112
+ }
1113
+
993
1114
/**
994
1115
* The rename request is sent from the client to the server to perform a workspace-wide rename of a symbol.
995
1116
*
0 commit comments