1010 ChangeAnnotation ,
1111 CodeAction ,
1212 CodeActionContext ,
13+ CodeActionDisabledType ,
1314 CodeActionKind ,
1415 CodeActionTriggerKind ,
1516 Command ,
@@ -126,34 +127,68 @@ async def collect(
126127 async def code_action_create_keyword (
127128 self , document : TextDocument , range : Range , context : CodeActionContext
128129 ) -> Optional [List [Union [Command , CodeAction ]]]:
130+ from robot .parsing .model .statements import (
131+ Fixture ,
132+ KeywordCall ,
133+ Template ,
134+ TestTemplate ,
135+ )
136+
129137 result : List [Union [Command , CodeAction ]] = []
130138
131139 if (context .only and CodeActionKind .QUICK_FIX in context .only ) or context .trigger_kind in [
132140 CodeActionTriggerKind .INVOKED ,
133141 CodeActionTriggerKind .AUTOMATIC ,
134142 ]:
143+ model = await self .parent .documents_cache .get_model (document , False )
144+ namespace = await self .parent .documents_cache .get_namespace (document )
145+
135146 for diagnostic in (
136147 d
137148 for d in context .diagnostics
138149 if d .source == DIAGNOSTICS_SOURCE_NAME and d .code == Error .KEYWORD_NOT_FOUND
139150 ):
140- text = document .get_lines ()[diagnostic .range .start .line ][
141- diagnostic .range .start .character : diagnostic .range .end .character
142- ]
143- if not text :
144- continue
145- result .append (
146- CodeAction (
147- f"Create Keyword `{ text } `" ,
148- kind = CodeActionKind .QUICK_FIX ,
149- command = Command (
150- self .parent .commands .get_command_name (self .create_keyword_command ),
151- self .parent .commands .get_command_name (self .create_keyword_command ),
152- [document .document_uri , diagnostic .range ],
153- ),
154- diagnostics = [diagnostic ],
151+ disabled = None
152+ node = await get_node_at_position (model , diagnostic .range .start )
153+
154+ if isinstance (node , (KeywordCall , Fixture , TestTemplate , Template )):
155+ tokens = get_tokens_at_position (node , diagnostic .range .start )
156+ if not tokens :
157+ continue
158+
159+ keyword_token = tokens [- 1 ]
160+
161+ bdd_token , token = self .split_bdd_prefix (namespace , keyword_token )
162+ if bdd_token is not None and token is not None :
163+ keyword_token = token
164+
165+ lib_entry , kw_namespace = await self .get_namespace_info_from_keyword (namespace , keyword_token )
166+
167+ if lib_entry is not None and lib_entry .library_doc .type == "LIBRARY" :
168+ disabled = CodeActionDisabledType ("Keyword is from a library" )
169+
170+ text = keyword_token .value
171+
172+ if lib_entry and kw_namespace :
173+ text = text [len (kw_namespace ) + 1 :].strip ()
174+
175+ if not text :
176+ continue
177+
178+ result .append (
179+ CodeAction (
180+ f"Create Keyword `{ text } `" ,
181+ kind = CodeActionKind .QUICK_FIX ,
182+ command = Command (
183+ self .parent .commands .get_command_name (self .create_keyword_command ),
184+ self .parent .commands .get_command_name (self .create_keyword_command ),
185+ [document .document_uri , diagnostic .range ],
186+ ),
187+ diagnostics = [diagnostic ],
188+ disabled = disabled ,
189+ is_preferred = True ,
190+ )
155191 )
156- )
157192
158193 return result if result else None
159194
@@ -187,9 +222,20 @@ async def create_keyword_command(self, document_uri: DocumentUri, range: Range)
187222
188223 bdd_token , token = self .split_bdd_prefix (namespace , keyword_token )
189224 if bdd_token is not None and token is not None :
190- keyword = token .value
191- else :
192- keyword = keyword_token .value
225+ keyword_token = token
226+
227+ lib_entry , kw_namespace = await self .get_namespace_info_from_keyword (namespace , keyword_token )
228+
229+ if lib_entry is not None and lib_entry .library_doc .type == "LIBRARY" :
230+ return
231+
232+ text = keyword_token .value
233+
234+ if lib_entry and kw_namespace :
235+ text = text [len (kw_namespace ) + 1 :].strip ()
236+
237+ if not text :
238+ return
193239
194240 arguments = []
195241
@@ -202,54 +248,65 @@ async def create_keyword_command(self, document_uri: DocumentUri, range: Range)
202248 arguments .append (f"${{arg{ len (arguments )+ 1 } }}" )
203249
204250 insert_text = (
205- KEYWORD_WITH_ARGS_TEMPLATE .substitute (name = keyword , args = " " .join (arguments ))
251+ KEYWORD_WITH_ARGS_TEMPLATE .substitute (name = text , args = " " .join (arguments ))
206252 if arguments
207- else KEYWORD_TEMPLATE .substitute (name = keyword )
253+ else KEYWORD_TEMPLATE .substitute (name = text )
208254 )
209255
210- keyword_sections = find_keyword_sections (model )
211- keyword_section = keyword_sections [- 1 ] if keyword_sections else None
256+ if lib_entry is not None and lib_entry .library_doc .type == "RESOURCE" and lib_entry .library_doc .source :
257+ dest_document = await self .parent .documents .get_or_open_document (lib_entry .library_doc .source )
258+ else :
259+ dest_document = document
212260
213- if keyword_section is not None :
214- node_range = range_from_node (keyword_section )
261+ await self ._apply_create_keyword (dest_document , insert_text )
215262
216- insert_pos = Position (node_range .end .line + 1 , 0 )
217- insert_range = Range (insert_pos , insert_pos )
263+ async def _apply_create_keyword (self , document : TextDocument , insert_text : str ) -> None :
264+ model = await self .parent .documents_cache .get_model (document , False )
265+ namespace = await self .parent .documents_cache .get_namespace (document )
218266
219- insert_text = f"\n { insert_text } "
220- else :
221- if namespace .languages is None or not namespace .languages .languages :
222- keywords_text = "Keywords"
223- else :
224- keywords_text = namespace .languages .languages [- 1 ].keywords_header
267+ keyword_sections = find_keyword_sections (model )
268+ keyword_section = keyword_sections [- 1 ] if keyword_sections else None
225269
226- insert_text = f"\n \n *** { keywords_text } ***\n { insert_text } "
270+ if keyword_section is not None :
271+ node_range = range_from_node (keyword_section )
227272
228- lines = document .get_lines ()
229- end_line = len (lines ) - 1
230- while end_line >= 0 and not lines [end_line ].strip ():
231- end_line -= 1
232- doc_pos = Position (end_line + 1 , 0 )
273+ insert_pos = Position (node_range .end .line + 1 , 0 )
274+ insert_range = Range (insert_pos , insert_pos )
233275
234- insert_range = Range (doc_pos , doc_pos )
276+ insert_text = f"\n { insert_text } "
277+ else :
278+ if namespace .languages is None or not namespace .languages .languages :
279+ keywords_text = "Keywords"
280+ else :
281+ keywords_text = namespace .languages .languages [- 1 ].keywords_header
235282
236- we = WorkspaceEdit (
237- document_changes = [
238- TextDocumentEdit (
239- OptionalVersionedTextDocumentIdentifier (str (document .uri ), document .version ),
240- [AnnotatedTextEdit ("create_keyword" , insert_range , insert_text )],
241- )
242- ],
243- change_annotations = {"create_keyword" : ChangeAnnotation ("Create Keyword" , False )},
244- )
283+ insert_text = f"\n \n *** { keywords_text } ***\n { insert_text } "
245284
246- if (await self .parent .workspace .apply_edit (we )).applied :
247- lines = insert_text .rstrip ().splitlines ()
248- insert_range .start .line += len (lines ) - 1
249- insert_range .start .character = 4
250- insert_range .end = Position (insert_range .start .line , insert_range .start .character )
251- insert_range .end .character += len (lines [- 1 ])
252- await self .parent .window .show_document (str (document .uri ), take_focus = True , selection = insert_range )
285+ lines = document .get_lines ()
286+ end_line = len (lines ) - 1
287+ while end_line >= 0 and not lines [end_line ].strip ():
288+ end_line -= 1
289+ doc_pos = Position (end_line + 1 , 0 )
290+
291+ insert_range = Range (doc_pos , doc_pos )
292+
293+ we = WorkspaceEdit (
294+ document_changes = [
295+ TextDocumentEdit (
296+ OptionalVersionedTextDocumentIdentifier (str (document .uri ), document .version ),
297+ [AnnotatedTextEdit ("create_keyword" , insert_range , insert_text )],
298+ )
299+ ],
300+ change_annotations = {"create_keyword" : ChangeAnnotation ("Create Keyword" , False )},
301+ )
302+
303+ if (await self .parent .workspace .apply_edit (we )).applied :
304+ lines = insert_text .rstrip ().splitlines ()
305+ insert_range .start .line += len (lines ) - 1
306+ insert_range .start .character = 4
307+ insert_range .end = Position (insert_range .start .line , insert_range .start .character )
308+ insert_range .end .character += len (lines [- 1 ])
309+ await self .parent .window .show_document (str (document .uri ), take_focus = True , selection = insert_range )
253310
254311 async def code_action_assign_result_to_variable (
255312 self , document : TextDocument , range : Range , context : CodeActionContext
@@ -262,10 +319,14 @@ async def code_action_assign_result_to_variable(
262319 TestTemplate ,
263320 )
264321
265- if (context .only and QUICK_FIX_OTHER in context .only ) or context .trigger_kind in [
266- CodeActionTriggerKind .INVOKED ,
267- CodeActionTriggerKind .AUTOMATIC ,
268- ]:
322+ if range .start .line == range .end .line and (
323+ (context .only and QUICK_FIX_OTHER in context .only )
324+ or context .trigger_kind
325+ in [
326+ CodeActionTriggerKind .INVOKED ,
327+ CodeActionTriggerKind .AUTOMATIC ,
328+ ]
329+ ):
269330 model = await self .parent .documents_cache .get_model (document , False )
270331 node = await get_node_at_position (model , range .start )
271332
0 commit comments