1
1
//! LSP server main loop for dispatching requests, notifications and handling responses.
2
2
3
3
use crossbeam_channel:: Sender ;
4
+ use lsp_types:: request:: Request ;
4
5
use std:: collections:: HashSet ;
5
6
6
7
use crate :: dispatch:: routers:: { NotificationRouter , RequestRouter } ;
7
8
use crate :: memory:: Memory ;
9
+ use crate :: utils;
10
+ use handlers:: request:: CreateProjectResponse ;
8
11
9
12
mod actions;
10
13
mod handlers;
@@ -41,8 +44,8 @@ pub fn main_loop(
41
44
// Handles all other notifications using the dispatcher.
42
45
dispatcher. handle_notification ( not) ?;
43
46
}
44
- // We don't currently initiate any requests, so all responses are ignored .
45
- lsp_server:: Message :: Response ( _ ) => ( ) ,
47
+ // Handles responses to requests initiated by the server (e.g workspace edits) .
48
+ lsp_server:: Message :: Response ( resp ) => dispatcher . handle_response ( resp ) ? ,
46
49
}
47
50
}
48
51
@@ -56,6 +59,9 @@ struct Dispatcher<'a> {
56
59
memory : Memory ,
57
60
}
58
61
62
+ const INITIALIZE_PROJECT_ID_PREFIX : & str = "initialize-project::" ;
63
+ const SHOW_DOCUMENT_ID_PREFIX : & str = "show-document::" ;
64
+
59
65
impl < ' a > Dispatcher < ' a > {
60
66
/// Creates a dispatcher for an LSP server connection.
61
67
fn new (
@@ -72,6 +78,7 @@ impl<'a> Dispatcher<'a> {
72
78
/// Handles LSP requests and sends responses (if any) as appropriate.
73
79
fn handle_request ( & mut self , req : lsp_server:: Request ) -> anyhow:: Result < ( ) > {
74
80
// Computes request response (if any).
81
+ let is_execute_command = req. method == lsp_types:: request:: ExecuteCommand :: METHOD ;
75
82
let mut router = RequestRouter :: new ( req, & mut self . memory , & self . client_capabilities ) ;
76
83
let result = router
77
84
. process :: < lsp_types:: request:: Completion > ( handlers:: request:: handle_completion)
@@ -81,11 +88,71 @@ impl<'a> Dispatcher<'a> {
81
88
. process :: < lsp_types:: request:: SignatureHelpRequest > (
82
89
handlers:: request:: handle_signature_help,
83
90
)
91
+ . process :: < lsp_types:: request:: ExecuteCommand > (
92
+ handlers:: request:: handle_execute_command,
93
+ )
84
94
. finish ( ) ;
85
95
86
96
// Sends response (if any).
87
- if let Some ( resp) = result {
88
- self . send ( resp. into ( ) ) ?;
97
+ if let Some ( mut resp) = result {
98
+ // Intercept non-empty `createProject` responses and create the project as a workspace edit.
99
+ if let Some ( project) = is_execute_command
100
+ . then_some ( resp. result . as_ref ( ) )
101
+ . flatten ( )
102
+ . and_then ( |value| {
103
+ serde_json:: from_value :: < CreateProjectResponse > ( value. clone ( ) ) . ok ( )
104
+ } )
105
+ {
106
+ // Return an empty response.
107
+ resp. result = Some ( serde_json:: Value :: Null ) ;
108
+ self . send ( resp. into ( ) ) ?;
109
+
110
+ // Create project files using a workspace edit.
111
+ let doc_changes = project
112
+ . files
113
+ . iter ( )
114
+ . flat_map ( |( uri, content) | {
115
+ vec ! [
116
+ lsp_types:: DocumentChangeOperation :: Op ( lsp_types:: ResourceOp :: Create (
117
+ lsp_types:: CreateFile {
118
+ uri: uri. clone( ) ,
119
+ options: None ,
120
+ annotation_id: None ,
121
+ } ,
122
+ ) ) ,
123
+ lsp_types:: DocumentChangeOperation :: Edit ( lsp_types:: TextDocumentEdit {
124
+ text_document: lsp_types:: OptionalVersionedTextDocumentIdentifier {
125
+ uri: uri. clone( ) ,
126
+ version: None ,
127
+ } ,
128
+ edits: vec![ lsp_types:: OneOf :: Left ( lsp_types:: TextEdit {
129
+ range: lsp_types:: Range :: default ( ) ,
130
+ new_text: content. to_owned( ) ,
131
+ } ) ] ,
132
+ } ) ,
133
+ ]
134
+ } )
135
+ . collect ( ) ;
136
+ let params = lsp_types:: ApplyWorkspaceEditParams {
137
+ label : Some ( "new ink! project" . to_string ( ) ) ,
138
+ edit : lsp_types:: WorkspaceEdit {
139
+ document_changes : Some ( lsp_types:: DocumentChanges :: Operations ( doc_changes) ) ,
140
+ ..Default :: default ( )
141
+ } ,
142
+ } ;
143
+ let req = lsp_server:: Request :: new (
144
+ lsp_server:: RequestId :: from ( format ! (
145
+ "{INITIALIZE_PROJECT_ID_PREFIX}{}" ,
146
+ project. uri
147
+ ) ) ,
148
+ lsp_types:: request:: ApplyWorkspaceEdit :: METHOD . to_string ( ) ,
149
+ params,
150
+ ) ;
151
+ self . send ( req. into ( ) ) ?;
152
+ } else {
153
+ // Otherwise return response.
154
+ self . send ( resp. into ( ) ) ?;
155
+ }
89
156
}
90
157
91
158
// Process memory changes (if any) made by request handlers.
@@ -116,6 +183,39 @@ impl<'a> Dispatcher<'a> {
116
183
Ok ( ( ) )
117
184
}
118
185
186
+ /// Handles LSP responses.
187
+ fn handle_response ( & mut self , resp : lsp_server:: Response ) -> anyhow:: Result < ( ) > {
188
+ // Open `lib.rs` after project initialization.
189
+ if let Some ( resp_id) = utils:: request_id_as_str ( resp. id ) {
190
+ if resp_id. starts_with ( INITIALIZE_PROJECT_ID_PREFIX ) {
191
+ if let Some ( project_uri) = resp_id
192
+ . strip_prefix ( INITIALIZE_PROJECT_ID_PREFIX )
193
+ . and_then ( |suffix| lsp_types:: Url :: parse ( suffix) . ok ( ) )
194
+ {
195
+ if let Ok ( lib_uri) = project_uri. join ( "lib.rs" ) {
196
+ let params = lsp_types:: ShowDocumentParams {
197
+ uri : lib_uri. clone ( ) ,
198
+ external : None ,
199
+ take_focus : Some ( true ) ,
200
+ selection : None ,
201
+ } ;
202
+ let req = lsp_server:: Request :: new (
203
+ lsp_server:: RequestId :: from ( format ! (
204
+ "{SHOW_DOCUMENT_ID_PREFIX}{}" ,
205
+ lib_uri
206
+ ) ) ,
207
+ lsp_types:: request:: ShowDocument :: METHOD . to_string ( ) ,
208
+ params,
209
+ ) ;
210
+ self . send ( req. into ( ) ) ?;
211
+ }
212
+ }
213
+ }
214
+ }
215
+
216
+ Ok ( ( ) )
217
+ }
218
+
119
219
/// Processes changes to state and triggers appropriate actions (if any).
120
220
fn process_changes ( & mut self ) -> anyhow:: Result < ( ) > {
121
221
// Retrieves document changes (if any).
@@ -216,7 +316,6 @@ mod tests {
216
316
217
317
// Verifies that LSP requests (from client to server) get appropriate LSP responses (from server to client).
218
318
// Creates LSP completion request.
219
- use lsp_types:: request:: Request ;
220
319
let completion_request_id = lsp_server:: RequestId :: from ( 1 ) ;
221
320
let completion_request = lsp_server:: Request {
222
321
id : completion_request_id. clone ( ) ,
0 commit comments