@@ -19,6 +19,7 @@ use super::commands::CommandExecutor;
1919use super :: component_factory:: { ComponentFactory , Config } ;
2020use super :: queries:: QueryExecutor ;
2121use super :: { InMemoryDocumentDatabase , LSPClient } ;
22+ use crate :: infra:: parse_compose_file;
2223
2324pub struct LSPServer < C > {
2425 command_executor : CommandExecutor < C > ,
@@ -83,6 +84,94 @@ impl TryFrom<&str> for SupportedCommands {
8384 }
8485}
8586
87+ struct CommandInfo {
88+ title : String ,
89+ command : String ,
90+ arguments : Option < Vec < Value > > ,
91+ range : Range ,
92+ }
93+
94+ impl < C > LSPServer < C >
95+ where
96+ C : LSPClient + Send + Sync + ' static ,
97+ {
98+ fn generate_commands_for_uri (
99+ & self ,
100+ uri : & tower_lsp:: lsp_types:: Url ,
101+ content : & str ,
102+ ) -> Vec < CommandInfo > {
103+ let file_uri = uri. as_str ( ) ;
104+
105+ if file_uri. contains ( "docker-compose.yml" )
106+ || file_uri. contains ( "compose.yml" )
107+ || file_uri. contains ( "docker-compose.yaml" )
108+ || file_uri. contains ( "compose.yaml" )
109+ {
110+ self . generate_compose_commands ( uri, content)
111+ } else {
112+ self . generate_dockerfile_commands ( uri, content)
113+ }
114+ }
115+
116+ fn generate_compose_commands (
117+ & self ,
118+ uri : & tower_lsp:: lsp_types:: Url ,
119+ content : & str ,
120+ ) -> Vec < CommandInfo > {
121+ let mut commands = vec ! [ ] ;
122+ if let Ok ( instructions) = parse_compose_file ( content) {
123+ for instruction in instructions {
124+ commands. push ( CommandInfo {
125+ title : "Scan base image" . to_string ( ) ,
126+ command : SupportedCommands :: ExecuteBaseImageScan . to_string ( ) ,
127+ arguments : Some ( vec ! [ json!( uri) , json!( instruction. range. start. line) ] ) ,
128+ range : instruction. range ,
129+ } ) ;
130+ }
131+ }
132+ commands
133+ }
134+
135+ fn generate_dockerfile_commands (
136+ & self ,
137+ uri : & tower_lsp:: lsp_types:: Url ,
138+ content : & str ,
139+ ) -> Vec < CommandInfo > {
140+ let mut commands = vec ! [ ] ;
141+ if let Some ( last_line_starting_with_from_statement) = content
142+ . lines ( )
143+ . enumerate ( )
144+ . filter ( |( _, line) | line. trim_start ( ) . starts_with ( "FROM " ) )
145+ . map ( |( line_num, _) | line_num)
146+ . last ( )
147+ {
148+ let range = Range :: new (
149+ Position :: new ( last_line_starting_with_from_statement as u32 , 0 ) ,
150+ Position :: new ( last_line_starting_with_from_statement as u32 , 0 ) ,
151+ ) ;
152+ commands. push ( CommandInfo {
153+ title : "Build and scan" . to_string ( ) ,
154+ command : SupportedCommands :: ExecuteBuildAndScan . to_string ( ) ,
155+ arguments : Some ( vec ! [
156+ json!( uri) ,
157+ json!( last_line_starting_with_from_statement) ,
158+ ] ) ,
159+ range,
160+ } ) ;
161+ commands. push ( CommandInfo {
162+ title : "Scan base image" . to_string ( ) ,
163+ command : SupportedCommands :: ExecuteBaseImageScan . to_string ( ) ,
164+ arguments : Some ( vec ! [
165+ json!( uri) ,
166+ json!( last_line_starting_with_from_statement) ,
167+ ] ) ,
168+ range,
169+ } ) ;
170+ }
171+ commands
172+ }
173+ }
174+
86175#[ async_trait:: async_trait]
87176impl < C > LanguageServer for LSPServer < C >
88177where
@@ -152,8 +241,6 @@ where
152241 }
153242
154243 async fn code_action ( & self , params : CodeActionParams ) -> Result < Option < CodeActionResponse > > {
155- let mut code_actions = vec ! [ ] ;
156-
157244 let Some ( content) = self
158245 . query_executor
159246 . get_document_text ( params. text_document . uri . as_str ( ) )
@@ -165,48 +252,23 @@ where
165252 ) ) ) ;
166253 } ;
167254
168- let Some ( last_line_starting_with_from_statement) = content
169- . lines ( )
170- . enumerate ( )
171- . filter ( |( _, line) | line. trim_start ( ) . starts_with ( "FROM " ) )
172- . map ( |( line_num, _) | line_num)
173- . last ( )
174- else {
175- return Ok ( None ) ;
176- } ;
177-
178- let Ok ( line_selected_as_usize) = usize:: try_from ( params. range . start . line ) else {
179- return Err ( Error :: internal_error ( ) . with_message ( format ! (
180- "unable to parse u32 as usize: {}" ,
181- params. range. start. line
182- ) ) ) ;
183- } ;
184-
185- if last_line_starting_with_from_statement == line_selected_as_usize {
186- code_actions. push ( CodeActionOrCommand :: Command ( Command {
187- title : "Build and scan" . to_string ( ) ,
188- command : SupportedCommands :: ExecuteBuildAndScan . to_string ( ) ,
189- arguments : Some ( vec ! [
190- json!( params. text_document. uri) ,
191- json!( line_selected_as_usize) ,
192- ] ) ,
193- } ) ) ;
194- code_actions. push ( CodeActionOrCommand :: Command ( Command {
195- title : "Scan base image" . to_string ( ) ,
196- command : SupportedCommands :: ExecuteBaseImageScan . to_string ( ) ,
197- arguments : Some ( vec ! [
198- json!( params. text_document. uri) ,
199- json!( line_selected_as_usize) ,
200- ] ) ,
201- } ) ) ;
202- }
255+ let commands = self . generate_commands_for_uri ( & params. text_document . uri , & content) ;
256+ let code_actions: Vec < CodeActionOrCommand > = commands
257+ . into_iter ( )
258+ . filter ( |cmd| cmd. range . start . line == params. range . start . line )
259+ . map ( |cmd| {
260+ CodeActionOrCommand :: Command ( Command {
261+ title : cmd. title ,
262+ command : cmd. command ,
263+ arguments : cmd. arguments ,
264+ } )
265+ } )
266+ . collect ( ) ;
203267
204268 Ok ( Some ( code_actions) )
205269 }
206270
207271 async fn code_lens ( & self , params : CodeLensParams ) -> Result < Option < Vec < CodeLens > > > {
208- let mut code_lens = vec ! [ ] ;
209-
210272 let Some ( content) = self
211273 . query_executor
212274 . get_document_text ( params. text_document . uri . as_str ( ) )
@@ -218,48 +280,21 @@ where
218280 ) ) ) ;
219281 } ;
220282
221- let Some ( last_line_starting_with_from_statement) = content
222- . lines ( )
223- . enumerate ( )
224- . filter ( |( _, line) | line. trim_start ( ) . starts_with ( "FROM " ) )
225- . map ( |( line_num, _) | line_num)
226- . last ( )
227- else {
228- return Ok ( None ) ;
229- } ;
230-
231- code_lens. push ( CodeLens {
232- range : Range :: new (
233- Position :: new ( last_line_starting_with_from_statement as u32 , 0 ) ,
234- Position :: new ( last_line_starting_with_from_statement as u32 , 0 ) ,
235- ) ,
236- command : Some ( Command {
237- title : "Build and scan" . to_string ( ) ,
238- command : SupportedCommands :: ExecuteBuildAndScan . to_string ( ) ,
239- arguments : Some ( vec ! [
240- json!( params. text_document. uri) ,
241- json!( last_line_starting_with_from_statement) ,
242- ] ) ,
243- } ) ,
244- data : None ,
245- } ) ;
246- code_lens. push ( CodeLens {
247- range : Range :: new (
248- Position :: new ( last_line_starting_with_from_statement as u32 , 0 ) ,
249- Position :: new ( last_line_starting_with_from_statement as u32 , 0 ) ,
250- ) ,
251- command : Some ( Command {
252- title : "Scan base image" . to_string ( ) ,
253- command : SupportedCommands :: ExecuteBaseImageScan . to_string ( ) ,
254- arguments : Some ( vec ! [
255- json!( params. text_document. uri) ,
256- json!( last_line_starting_with_from_statement) ,
257- ] ) ,
258- } ) ,
259- data : None ,
260- } ) ;
283+ let commands = self . generate_commands_for_uri ( & params. text_document . uri , & content) ;
284+ let code_lenses = commands
285+ . into_iter ( )
286+ . map ( |cmd| CodeLens {
287+ range : cmd. range ,
288+ command : Some ( Command {
289+ title : cmd. title ,
290+ command : cmd. command ,
291+ arguments : cmd. arguments ,
292+ } ) ,
293+ data : None ,
294+ } )
295+ . collect ( ) ;
261296
262- Ok ( Some ( code_lens ) )
297+ Ok ( Some ( code_lenses ) )
263298 }
264299
265300 async fn execute_command ( & self , params : ExecuteCommandParams ) -> Result < Option < Value > > {
0 commit comments