Coverage for src/robotide/controller/ctrlcommands.py: 86%
1032 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-06 10:40 +0100
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-06 10:40 +0100
1# Copyright 2008-2015 Nokia Networks
2# Copyright 2016- Robot Framework Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
16import os 1ab
17import re 1ab
18import time 1ab
19from itertools import chain 1ab
20from . import settingcontrollers 1ab
21from . import validators 1ab
22from ..namespace.embeddedargs import EmbeddedArgsHandler 1ab
23from ..namespace import namespace 1ab
24from ..publish.messages import (RideSelectResource, RideFileNameChanged, RideSaving, RideSaved, RideSaveAll, 1ab
25 RideExcludesChanged)
26from ..utils import variablematcher 1ab
29BDD_ENGLISH = 'Given|When|Then|And|But' 1ab
31def obtain_bdd_prefixes(language): 1ab
32 from robotide.lib.compat.parsing.language import Language
33 lang = Language.from_name(language[0] if isinstance(language, list) else language)
34 bdd_prefixes = [f"{x}|" for x in lang.bdd_prefixes]
35 bdd_prefixes = "".join(bdd_prefixes).strip('|')
36 return bdd_prefixes
39class Occurrence(object): 1ab
41 def __init__(self, item, value): 1ab
42 self._item = item 1XNY12{(OIKLJ3kdeflrstuvmngochpijqPQRST/:;8|wxyz4
43 self._value = value 1XNY12{(OIKLJ3kdeflrstuvmngochpijqPQRST/:;8|wxyz4
44 self._replaced = False 1XNY12{(OIKLJ3kdeflrstuvmngochpijqPQRST/:;8|wxyz4
45 self.count = 1 1XNY12{(OIKLJ3kdeflrstuvmngochpijqPQRST/:;8|wxyz4
47 def __eq__(self, other): 1ab
48 if not isinstance(other, Occurrence): 48 ↛ 50line 48 didn't jump to line 50 because the condition on line 48 was always true1PQRST
49 return False 1PQRST
50 return (self.parent is other.parent and
51 self._in_steps() and other._in_steps())
53 @property 1ab
54 def item(self): 1ab
55 return self._item 1PQRST8|4
57 @property 1ab
58 def source(self): 1ab
59 return self.datafile.source 184
61 @property 1ab
62 def datafile(self): 1ab
63 return self._item.datafile 184
65 @property 1ab
66 def parent(self): 1ab
67 if self._in_for_loop():
68 return self._item.parent.parent
69 return self._item.parent
71 @property 1ab
72 def location(self): 1ab
73 return self._item.parent.name 1XNY12{(OIKLJ3kdeflrstuvmngochpijq
75 @property 1ab
76 def usage(self): 1ab
77 if self._in_variable_table(): 1XNY12{(OIKLJ3kdeflrstuvmngochpijqPQRST
78 return "Variable Table" 1ILJ
79 elif self._in_settings(): 1XNY12{(OIKLJ3kdeflrstuvmngochpijqPQRST
80 return self._item.label 1Y{(ILJ3rstuvPQRST
81 elif self._in_kw_name(): 1XNY12OIKLJ3kdeflmngochpijq
82 return 'Keyword Name' 1Nq
83 return 'Steps' if self.count == 1 else 'Steps (%d usages)' % self.count 1XY12OIKLJ3kdeflmngochpij
85 def _in_settings(self): 1ab
86 return isinstance(self._item, settingcontrollers._SettingController) 1XNY12{(OIKLJ3kdeflrstuvmngochpijqPQRST
88 def _in_variable_table(self): 1ab
89 from . import tablecontrollers 1XNY12{(OIKLJ3kdeflrstuvmngochpijqPQRST
90 return isinstance(self._item, tablecontrollers.VariableTableController) 1XNY12{(OIKLJ3kdeflrstuvmngochpijqPQRST
92 def _in_kw_name(self): 1ab
93 from .macrocontrollers import KeywordNameController 1XNY12OIKLJ3kdeflmngochpijq
95 return isinstance(self._item, KeywordNameController) 1XNY12OIKLJ3kdeflmngochpijq
97 def _in_steps(self): 1ab
98 return not (self._in_settings() or self._in_kw_name())
100 def _in_for_loop(self): 1ab
101 from .macrocontrollers import ForLoopStepController
103 return isinstance(self._item.parent, ForLoopStepController)
105 def replace_keyword(self, new_name): 1ab
106 # print(f"DEBUG: ctrlcommands.py Occurrence replace_keyword BEFORE new_name={new_name} value={self._value}"
107 # f" self._replaced={self._replaced} item={self._item}")
108 self._item.replace_keyword(*self._get_replace_values(new_name)) 1kdeflrstuvmngochpijqwxyz
109 self._replaced = not self._replaced 1kdeflrstuvmngochpijqwxyz
111 def _get_replace_values(self, new_name): 1ab
112 if self._replaced: 1kdeflrstuvmngochpijqwxyz
113 return self._value, new_name 1ij
114 return new_name, self._value 1kdeflrstuvmngochpijqwxyz
116 def notify_value_changed(self, old_name=None, new_name=None): 1ab
117 self._item.notify_value_changed(old_name=old_name, new_name=new_name) 1kdeflrstuvmngochpijqwxyz
120class _Command(object): 1ab
121 modifying = True 1ab
123 def execute(self, context): 1ab
124 raise NotImplementedError(self.__class__)
126 def __str__(self): 1ab
127 return '%s(%s)' % (self.__class__.__name__, self._params_str())
129 def _params_str(self): 1ab
130 return ', '.join(self._format_param(p) for p in self._params())
132 @staticmethod 1ab
133 def _format_param(param): 1ab
134 if isinstance(param, str):
135 return '"%s"' % param
136 return str(param)
138 def _params(self): 1ab
139 return []
142class CopyMacroAs(_Command): 1ab
144 def __init__(self, new_name): 1ab
145 self._new_name = new_name 2M ,bDdEd
147 def execute(self, context): 1ab
148 context.copy(self._new_name) 2M ,bDdEd
150 def _params(self): 1ab
151 return [self._new_name]
154class ChangeTag(_Command): 1ab
156 def __init__(self, tag, value): 1ab
157 self._tag = tag 2kdid-c[c]c^c.chdld
158 self._value = value.strip() 2kdid-c[c]c^c.chdld
160 def _params(self): 1ab
161 return self._tag, self._value
163 def execute(self, context): 1ab
164 tags = [tag for tag in context if tag.controller == context] 2kdid-c[c]c^c.chdld
165 context.set_value(self._create_value(tags)) 2kdid-c[c]c^c.chdld
166 context.notify_value_changed() 2kdid-c[c]c^c.chdld
168 def _create_value(self, old_values): 1ab
169 if old_values == [] and self._tag.is_empty(): 2kdid-c[c]c^c.chdld
170 return self._value 2kdhdld
171 return ' | '.join(value for value in 2id-c[c]c^c.chd
172 self._create_value_list(old_values)
173 if value != '')
175 def _create_value_list(self, old_values): 1ab
176 if self._tag.is_empty(): 2id-c[c]c^c.chd
177 return [v.name for v in old_values] + [self._value] 2idhd
178 else:
179 new_list = [] 2-c[c]c^c.c
180 for v in old_values: 2-c[c]c^c.c
181 if v != self._tag: 2-c[c]c^c.c
182 new_list.append(v.name) 2-c.c
183 if self._value not in new_list: 183 ↛ 185line 183 didn't jump to line 185 because the condition on line 183 was always true2-c[c]c^c.c
184 new_list += [self._value] 2-c[c]c^c.c
185 return new_list 2-c[c]c^c.c
188class DeleteTag(_Command): 1ab
190 def execute(self, tag): 1ab
191 tag.delete()
192 tag.controller.notify_value_changed()
195class _ReversibleCommand(_Command): 1ab
197 def execute(self, context): 1ab
198 result = self._execute_without_redo_clear(context) 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,bMcA pbqbBbGbwb= LbEb? F 9 B ! )c] } G H E bc'brb+b3c4c5c6c5bqcrcsc~btcucvcwcxcXbycccNcdcecfcgc5 hcVb3bicacjcPczcbbcb7b8bWb9bXc!bQcYbZbdbCb) %b(bkcfbgb)b*bMb#b$b- U xb7c8c9cAcBcCc6bDcsbEcC 4bFcYcZc0cGcFb@ D HcIc0bJcKcLcybzbebHbIbtbubNbJbKbDb* 6 + # $ , % ^ _ ` [ ~ abhbibvb_c`c{c|cjbZ ' . W V 7 *c+c,c/c0 gd}c~c!c1cadbdcddded'cOcfdk d e f l r s t u v m n g o c h p i j q w x y z RcScTcUcVcWc
199 context.clear_redo() 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,bMcA pbqbBbGbwb= LbEb? F 9 B ! )c] } G H E bc'brb+b3c4c5c6c5bqcrcsc~btcucvcwcxcXbycccNcdcecfcgc5 hcVb3bicacjcPczcbbcb7b8bWb9bXc!bQcYbZbdbCb) %b(bkcfbgb)b*bMb#b$b- U xb7c8c9cAcBcCc6bDcsbEcC 4bFcYcZc0cGcFb@ D HcIc0bJcKcLcybzbebHbIbtbubNbJbKbDb* 6 + # $ , % ^ _ ` [ ~ abhbibvb_c`c{c|cjbZ ' . W V 7 *c+c,c/c0 gd}c~c!c1cadbdcddded'cOcfdk d e f l r s t u v m n g o c h p i j q w x y z RcScTcUcVcWc
200 return result 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,bMcA pbqbBbGbwb= LbEb? F 9 B ! )c] } G H E bc'brb+b3c4c5c6c5bqcrcsc~btcucvcwcxcXbycccNcdcecfcgc5 hcVb3bicacjcPczcbbcb7b8bWb9bXc!bQcYbZbdbCb) %b(bkcfbgb)b*bMb#b$b- U xb7c8c9cAcBcCc6bDcsbEcC 4bFcYcZc0cGcFb@ D HcIc0bJcKcLcybzbebHbIbtbubNbJbKbDb* 6 + # $ , % ^ _ ` [ ~ abhbibvb_c`c{c|cjbZ ' . W V 7 *c+c,c/c0 gd}c~c!c1cadbdcddded'cOcfdk d e f l r s t u v m n g o c h p i j q w x y z RcScTcUcVcWc
202 def _execute_without_redo_clear(self, context): 1ab
203 result = self._execute(context) 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,bMcA pbqbBbGbwb= LbEb? F 9 B ! )c] } G H E bc'brb+b3c4c5c6c5bqcrcsc~btcucvcwcxcXbycccNcdcecfcgc5 hcVb3bicacjcPczcbbcb7b8bWb9bXc!bQcYbZbdbCb) %b(bkcfbgb)b*bMb#b$b- U xb7c8c9cAcBcCc6bDcsbEcC 4bFcYcZc0cGcFb@ D HcIc0bJcKcLcybzbebHbIbtbubNbJbKbDb* 6 + # $ , % ^ _ ` [ ~ abhbibvb_c`c{c|cjbZ ' . W V 7 *c+c,c/c0 gd}c~c!c1cadbdcddded'cOcfdk d e f l r s t u v m n g o c h p i j q w x y z RcScTcUcVcWc
204 context.push_to_undo(self._get_undo_command()) 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,bMcA pbqbBbGbwb= LbEb? F 9 B ! )c] } G H E bc'brb+b3c4c5c6c5bqcrcsc~btcucvcwcxcXbycccNcdcecfcgc5 hcVb3bicacjcPczcbbcb7b8bWb9bXc!bQcYbZbdbCb) %b(bkcfbgb)b*bMb#b$b- U xb7c8c9cAcBcCc6bDcsbEcC 4bFcYcZc0cGcFb@ D HcIc0bJcKcLcybzbebHbIbtbubNbJbKbDb* 6 + # $ , % ^ _ ` [ ~ abhbibvb_c`c{c|cjbZ ' . W V 7 *c+c,c/c0 gd}c~c!c1cadbdcddded'cOcfdk d e f l r s t u v m n g o c h p i j q w x y z RcScTcUcVcWc
205 return result 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,bMcA pbqbBbGbwb= LbEb? F 9 B ! )c] } G H E bc'brb+b3c4c5c6c5bqcrcsc~btcucvcwcxcXbycccNcdcecfcgc5 hcVb3bicacjcPczcbbcb7b8bWb9bXc!bQcYbZbdbCb) %b(bkcfbgb)b*bMb#b$b- U xb7c8c9cAcBcCc6bDcsbEcC 4bFcYcZc0cGcFb@ D HcIc0bJcKcLcybzbebHbIbtbubNbJbKbDb* 6 + # $ , % ^ _ ` [ ~ abhbibvb_c`c{c|cjbZ ' . W V 7 *c+c,c/c0 gd}c~c!c1cadbdcddded'cOcfdk d e f l r s t u v m n g o c h p i j q w x y z RcScTcUcVcWc
207 @property 1ab
208 def _get_undo_command(self): 1ab
209 raise NotImplementedError(self.__class__.__name__)
212class Undo(_Command): 1ab
214 def execute(self, context): 1ab
215 if not context.is_undo_empty(): 2AbjdMcG H E 5 db%bMb#b$bHd- 7c8c9cC 1cOci j
216 result = context.pop_from_undo()._execute_without_redo_clear(context) 2AbMcG H E 5 db%bMb#b$b- 7c8c9cC 1cOci j
217 redo_command = context.pop_from_undo() 2AbMcG H E 5 db%bMb#b$b- 7c8c9cC 1cOci j
218 context.push_to_redo(redo_command) 2AbMcG H E 5 db%bMb#b$b- 7c8c9cC 1cOci j
219 return result 2AbMcG H E 5 db%bMb#b$b- 7c8c9cC 1cOci j
222class Redo(_Command): 1ab
224 def execute(self, context): 1ab
225 if not context.is_redo_empty(): 2Mc%bId#b$b7c8c9c
226 return context.pop_from_redo()._execute_without_redo_clear(context) 2Mc#b$b7c8c9c
229class MoveTo(_Command): 1ab
231 def __init__(self, destination): 1ab
232 self._destination = destination 2wdxdydzdAdBdCd
234 def _params(self): 1ab
235 return [self._destination]
237 def execute(self, context): 1ab
238 context.delete() 2wdxdydzdAdBdCd
239 self._destination.add_test_or_keyword(context) 2wdxdydzdAdBdCd
242class CreateNewResource(_Command): 1ab
244 def __init__(self, path): 1ab
245 self._path = path 2RcScTcUcVcWc
247 def execute(self, context): 1ab
248 res = context.new_resource(self._path) 2RcScTcUcVcWc
249 RideSelectResource(item=res).publish() 2RcScTcUcVcWc
250 return res 2RcScTcUcVcWc
253class SetDataFile(_Command): 1ab
255 def __init__(self, datafile): 1ab
256 self._datafile = datafile
258 def execute(self, context): 1ab
259 context.mark_dirty()
260 context.set_datafile(self._datafile)
263class _StepsChangingCommand(_ReversibleCommand): 1ab
265 def _execute(self, context): 1ab
266 if self.change_steps(context): 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,bMcA pbqbBbGbwb= LbEb? F 9 B ! )c] } G H E bc'brb+b3c4c5c6c5bqcrcsc~btcucvcwcxcXbycccNcdcecfcgc5 hcVb3bicacjcPczcbbcb7b8bWb9bXc!bQcYbZbdbCb) %b(bkcfbgb)b*bMb#b$b- U xbAcBcCc6bDcsbEcC 4bFcYcZc0cGcFb@ D HcIc0bJcKcLcybzbebHbIbtbubNbJbKbDb* 6 + # $ , % ^ _ ` [ ~ abhbibvb_c`c{c|cjbZ ' . W V 7 *c+c,c/c0 d e f g c h
267 context.notify_steps_changed() 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,bA pbqbBbGbwb= LbEb? F 9 B ! ] } G H E bc'brb+b3c4c5c6c5bqcrcsc~btcucvcwcxcXbycccdcecfcgc5 hcVb3bicacjczcbbcb7b8bWb9bXc!bYbZbdbCb) %b(bkcfbgb)b*bMb#b$b- U xbAcBcCc6bDcsbEcC 4bFcYcZc0cGcFb@ D HcIc0bJcKcLcybzbebHbIbtbubNbJbKbDb* 6 + # $ , % ^ _ ` [ ~ abhbibvbjbZ ' . W V 7 0 d e f g c h
268 return True 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,bA pbqbBbGbwb= LbEb? F 9 B ! ] } G H E bc'brb+b3c4c5c6c5bqcrcsc~btcucvcwcxcXbycccdcecfcgc5 hcVb3bicacjczcbbcb7b8bWb9bXc!bYbZbdbCb) %b(bkcfbgb)b*bMb#b$b- U xbAcBcCc6bDcsbEcC 4bFcYcZc0cGcFb@ D HcIc0bJcKcLcybzbebHbIbtbubNbJbKbDb* 6 + # $ , % ^ _ ` [ ~ abhbibvbjbZ ' . W V 7 0 d e f g c h
269 return False 2a Mc)crbNcPcQcsbab_c`c{c|c*c+c,c/c
271 def change_steps(self, context): 1ab
272 """Return True if steps changed, False otherwise"""
273 raise NotImplementedError(self.__class__.__name__)
275 def _step(self, context): 1ab
276 try: 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}bM SbTbUb,bbc'brb+b3c4c5c6c5bqcrcsc~btcucvcwcxcXbycccdcecfcgc5 Vbaczcbbcb7b8bWb9bXc!bYbZbdbCb) %b(bkcfbgb)b*bMb#b$b- U xbAcBcCc6bDcsbEc4bFcYcZc0cGcHcIc0bJcKcLcd e f g c h
277 return context.steps[self._row] 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}bM SbTbUb,bbc'brb+b3c4c5c6c5bqcrcsc~btcucvcwcxcXbycccdcecfcgc5 Vbaczcbbcb7b8bWb9bXc!bYbZbdbCb) %b(bkcfbgb)b*bMb#b$b- U xbAcBcCc6bDcsbEc4bFcYcZc0cGcHcIc0bJcKcLcd e f g c h
278 except IndexError:
279 return NonExistingStep()
282class NonExistingStep(object): 1ab
283 def __getattr__(self, name): 1ab
284 return lambda *args: ''
287class NullObserver(object): 1ab
288 notify = finish = lambda x: None 2a Jdb Kdk d e f l r s t u v m n g o c h p i j q w x y z
291class RenameKeywordOccurrences(_ReversibleCommand): 1ab
293 def __init__(self, original_name, new_name, observer, keyword_info=None, language='En'): 1ab
294 self._language = language[0] if isinstance(language, list) else language 2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
295 if self._language and self._language.lower() not in ['en', 'english']: 295 ↛ 296line 295 didn't jump to line 296 because the condition on line 295 was never true2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
296 bdd_prefix = f"{obtain_bdd_prefixes(self._language)}|{BDD_ENGLISH}"
297 else:
298 bdd_prefix = BDD_ENGLISH 2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
299 self._gherkin_prefix = re.compile(f'^({bdd_prefix}) ', re.IGNORECASE) 2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
300 self._original_name, self._new_name = self._check_gherkin(new_name, original_name) 2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
301 self._observer = observer 2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
302 self._keyword_info = keyword_info 2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
303 self._occurrences = None 2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
304 # print(f"DEBUG: ctrlcommands.py RenameKeywordOccurrences INIT\n"
305 # f"{original_name=}, {new_name=}, self._original_name={self._original_name} "
306 # f"self._new_name={self._new_name} self._keyword_info={self._keyword_info}"
307 # f" self._gherkin_prefix={self._gherkin_prefix} ")
309 def _check_gherkin(self, new_name, original_name): 1ab
310 was_gherkin, keyword_name = self._get_gherkin(original_name) 2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
311 is_gherkin, new_keyword_name = self._get_gherkin(new_name) 2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
312 if was_gherkin and not is_gherkin: 2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
313 keyword_name = original_name 2c 2c
314 if not was_gherkin and is_gherkin: 2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
315 # When we change non-gherkin to gherkin, the keyword changes too.
316 # The workaround is not to Rename keyword, but only edit field.
317 new_keyword_name = new_name 22c
318 if was_gherkin and is_gherkin: 2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
319 # Check if the first word has changed
320 if original_name.split(' ', 1)[0].lower() != new_name.split( 22c
321 ' ', 1)[0].lower():
322 new_keyword_name = new_name 22c
323 keyword_name = original_name 22c
324 return keyword_name, new_keyword_name 2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
326 def _get_gherkin(self, original_name): 1ab
327 keyword_value = re.sub(self._gherkin_prefix, '', original_name) 2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
328 value_is_gherkin = (keyword_value != original_name) 2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
329 return value_is_gherkin, keyword_value 2k d e f l r s t u v m n g o c h p i j q w x y 2c(cz
331 def _params(self): 1ab
332 return (self._original_name, self._new_name,
333 self._observer, self._keyword_info)
335 def _execute(self, context): 1ab
336 self._observer.notify() 1kdeflrstuvmngochpijqwxyz
337 self._occurrences = self._find_occurrences(context) if self._occurrences is None else self._occurrences 1kdeflrstuvmngochpijqwxyz
338 # print(f"DEBUG: ctlcommands.py RenameKeywordOccurrences _execute: found occurrences= {self._occurrences}\n"
339 # f"CONTEXT:{context}")
340 self._replace_keywords_in(self._occurrences) 1kdeflrstuvmngochpijqwxyz
341 context.update_namespace() 1kdeflrstuvmngochpijqwxyz
342 self._notify_values_changed(self._occurrences, old_name=self._original_name) 1kdeflrstuvmngochpijqwxyz
343 self._observer.finish() 1kdeflrstuvmngochpijqwxyz
345 def _find_occurrences(self, context): 1ab
346 occurrences = [] 1kdeflrstuvmngochpijqwxyz
347 for occ in context.execute(FindOccurrences( 1kdeflrstuvmngochpijqwxyz
348 self._original_name, keyword_info=self._keyword_info)):
349 self._observer.notify() 1kdeflrstuvmngochpijqwxyz
350 occurrences.append(occ) 1kdeflrstuvmngochpijqwxyz
351 self._observer.notify() 1kdeflrstuvmngochpijqwxyz
352 return occurrences 1kdeflrstuvmngochpijqwxyz
354 def _replace_keywords_in(self, occurrences): 1ab
355 for oc in occurrences: 1kdeflrstuvmngochpijqwxyz
356 oc.replace_keyword(self._new_name) 1kdeflrstuvmngochpijqwxyz
357 self._observer.notify() 1kdeflrstuvmngochpijqwxyz
359 def _notify_values_changed(self, occurrences, old_name=None): 1ab
360 for oc in occurrences: 1kdeflrstuvmngochpijqwxyz
361 # try:
362 # print(f"DEBUG: ctlcommands.py RenameKeywordOccurrences _notify_values_changed: "
363 # f"oc= {oc.source} {oc.item} {oc.usage} {oc._value}")
364 # except AttributeError:
365 # print(f"DEBUG: ctlcommands.py RenameKeywordOccurrences _notify_values_changed: "
366 # f" in AttributeError oc= {oc}")
367 oc.notify_value_changed(old_name=old_name, new_name=self._new_name) 1kdeflrstuvmngochpijqwxyz
368 self._observer.notify() 1kdeflrstuvmngochpijqwxyz
370 def _get_undo_command(self): 1ab
371 self._observer = NullObserver() 1kdeflrstuvmngochpijqwxyz
372 return self 1kdeflrstuvmngochpijqwxyz
375class RenameTest(_ReversibleCommand): 1ab
377 def __init__(self, new_name): 1ab
378 self._new_name = new_name 2gd
380 def _params(self): 1ab
381 return self._new_name
383 def _execute(self, context): 1ab
384 old_name = context.name 2gd
385 context.test_name.rename(self._new_name) 2gd
386 context.test_name._item.notify_name_changed(old_name=old_name, new_name=self._new_name) 2gd
388 def _get_undo_command(self): 1ab
389 return self 2gd
392class RenameFile(_Command): 1ab
394 def __init__(self, new_basename): 1ab
395 self._new_basename = new_basename 2qd:c;crdsd=c?c@c
396 self._validator = validators.BaseNameValidator(new_basename) 2qd:c;crdsd=c?c@c
398 def execute(self, context): 1ab
399 validation_result = self._validator.validate(context) 2qd:c;crdsd=c?c@c
400 if validation_result: 2qd:c;crdsd=c?c@c
401 old_filename = context.filename 2:c;c=c?c@c
402 context.set_basename(self._new_basename.strip()) 2:c;c=c?c@c
403 RideFileNameChanged(datafile=context, 2:c;c=c?c@c
404 old_filename=old_filename).publish()
405 return validation_result 2qd:c;crdsd=c?c@c
408class Include(_Command): 1ab
410 def execute(self, excluded_controller): 1ab
411 directory_controller = excluded_controller.remove_from_excludes()
412 RideExcludesChanged(old_controller=excluded_controller,
413 new_controller=directory_controller).publish()
416class Exclude(_Command): 1ab
418 def execute(self, directory_controller): 1ab
419 excluded_controller = directory_controller.exclude()
420 RideExcludesChanged(old_controller=directory_controller,
421 new_controller=excluded_controller).publish()
424class RenameResourceFile(_Command): 1ab
426 def __init__(self, new_basename, get_should_modify_imports): 1ab
427 self._new_basename = new_basename 2#c$c%c
428 self._should_modify_imports = get_should_modify_imports 2#c$c%c
430 def execute(self, context): 1ab
431 validation_result = validators.BaseNameValidator( 2#c$c%c
432 self._new_basename).validate(context)
433 if validation_result: 433 ↛ 444line 433 didn't jump to line 444 because the condition on line 433 was always true2#c$c%c
434 old_filename = context.filename 2#c$c%c
435 modify_imports = self._should_modify_imports() 2#c$c%c
436 if modify_imports is None: 436 ↛ 437line 436 didn't jump to line 437 because the condition on line 436 was never true2#c$c%c
437 return
438 if modify_imports: 2#c$c%c
439 context.set_basename_and_modify_imports(self._new_basename) 2$c%c
440 else:
441 context.set_basename(self._new_basename) 2#c
442 RideFileNameChanged(datafile=context, 2#c$c%c
443 old_filename=old_filename).publish()
444 return validation_result 2#c$c%c
447class SortTests(_ReversibleCommand): 1ab
448 index_difference = None 1ab
450 def _execute(self, context): 1ab
451 index_difference = context.sort_tests() 27c
452 self._undo_command = RestoreTestOrder(index_difference) 27c
454 def _get_undo_command(self): 1ab
455 return self._undo_command 27c
458class SortKeywords(_ReversibleCommand): 1ab
459 index_difference = None 1ab
461 def _execute(self, context): 1ab
462 index_difference = context.sort_keywords() 28c
463 self._undo_command = RestoreKeywordOrder(index_difference) 28c
465 def _get_undo_command(self): 1ab
466 return self._undo_command 28c
469class SortVariables(_ReversibleCommand): 1ab
470 index_difference = None 1ab
472 def _execute(self, context): 1ab
473 index_difference = context.sort_variables() 29c
474 self._undo_command = RestoreVariableOrder(index_difference) 29c
476 def _get_undo_command(self): 1ab
477 return self._undo_command 29c
480class RestoreTestOrder(_ReversibleCommand): 1ab
482 def __init__(self, index_difference): 1ab
483 self._index_difference = index_difference 27c
485 def _execute(self, context): 1ab
486 context.restore_test_order(self._index_difference) 27c
488 def _get_undo_command(self): 1ab
489 return SortTests() 27c
492class RestoreKeywordOrder(_ReversibleCommand): 1ab
494 def __init__(self, index_difference): 1ab
495 self._index_difference = index_difference 28c
497 def _execute(self, context): 1ab
498 context.restore_keyword_order(self._index_difference) 28c
500 def _get_undo_command(self): 1ab
501 return SortKeywords() 28c
504class RestoreVariableOrder(_ReversibleCommand): 1ab
506 def __init__(self, index_difference): 1ab
507 self._index_difference = index_difference 29c
509 def _execute(self, context): 1ab
510 context.restore_variable_order(self._index_difference) 29c
512 def _get_undo_command(self): 1ab
513 return SortVariables() 29c
516class _ItemCommand(_Command): 1ab
518 def __init__(self, item): 1ab
519 self._item = item
522class UpdateDocumentation(_ItemCommand): 1ab
524 def execute(self, context): 1ab
525 context.editable_value = self._item
528class MoveUp(_ItemCommand): 1ab
530 def execute(self, context): 1ab
531 context.move_up(self._item)
534class MoveDown(_ItemCommand): 1ab
536 def execute(self, context): 1ab
537 context.move_down(self._item)
540class DeleteItem(_ItemCommand): 1ab
542 def execute(self, context): 1ab
543 context.delete(self._item)
546class ClearSetting(_Command): 1ab
548 def execute(self, context): 1ab
549 context.clear()
552class DeleteFile(_Command): 1ab
554 def execute(self, context): 1ab
555 context.remove_from_filesystem() 2Fdvd
556 context.remove() 2Fdvd
559class OpenContainingFolder(_Command): 1ab
560 modifying = False 1ab
562 def __init__(self, tool: str = None, path: str = None): 1ab
563 self.tool = tool
564 self.path = path
566 def execute(self, context): 1ab
567 context.open_filemanager(path=self.path, tool=self.tool)
570class RemoveReadOnly(_Command): 1ab
572 def execute(self, context): 1ab
573 context.remove_readonly()
576class DeleteFolder(_Command): 1ab
578 def execute(self, context): 1ab
579 context.remove_folder_from_filesystem()
580 context.remove_from_model()
583class SetValues(_Command): 1ab
585 def __init__(self, values, comment): 1ab
586 self._values = values
587 self._comment = comment
589 def execute(self, context): 1ab
590 context.set_value(*self._values)
591 context.set_comment(self._comment)
594class AddLibrary(_Command): 1ab
596 def __init__(self, values, comment): 1ab
597 self._values = values
598 self._comment = comment
600 def execute(self, context): 1ab
601 lib = context.add_library(*self._values)
602 lib.set_comment(self._comment)
603 return lib
606class AddResource(_Command): 1ab
608 def __init__(self, values, comment): 1ab
609 self._values = values
610 self._comment = comment
612 def execute(self, context): 1ab
613 res = context.add_resource(*self._values)
614 res.set_comment(self._comment)
615 return res
618class AddVariablesFileImport(_Command): 1ab
620 def __init__(self, values, comment): 1ab
621 self._values = values
622 self._comment = comment
624 def execute(self, context): 1ab
625 var = context.add_variables(*self._values)
626 var.set_comment(self._comment)
627 return var
630class DeleteResourceAndImports(DeleteFile): 1ab
632 def execute(self, context): 1ab
633 context.remove_static_imports_to_this() 2vd
634 DeleteFile.execute(self, context) 2vd
637class DeleteFolderAndImports(DeleteFolder): 1ab
639 def execute(self, context): 1ab
640 context.remove_static_imports_to_this()
641 DeleteFolder.execute(self, context)
644class UpdateVariable(_Command): 1ab
646 def __init__(self, new_name, new_value, new_comment): 1ab
647 self._new_name = new_name
648 self._new_value = new_value
649 self._new_comment = new_comment
651 def execute(self, context): 1ab
652 has_data = context.has_data()
653 context.set_value(self._new_name, self._new_value)
654 context.set_comment(self._new_comment)
655 if has_data:
656 context.notify_value_changed()
657 else:
658 context.notify_variable_added()
661class UpdateVariableName(_Command): 1ab
663 def __init__(self, new_name): 1ab
664 self._new_name = new_name
666 def execute(self, context): 1ab
667 context.execute(UpdateVariable(self._new_name, context.value,
668 context.comment))
671def normalize_kw_name(name): 1ab
672 name = re.sub('(.)([A-Z][a-z]+)', r'\1 \2', name) 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
673 # print(f"DEBUG: ctlcommands.py normalize_kw_name First step keyword_name={name}")
674 name = re.sub('([a-z0-9])([A-Z])', r'\1 \2', name).lower().replace('_', ' ') 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
675 # print(f"DEBUG: ctlcommands.py normalize_kw_name RETURN keyword_name={name}")
676 return name 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
679class FindOccurrences(_Command): 1ab
680 modifying = False 1ab
682 def __init__(self, keyword_name, keyword_info=None, prefix=None): 1ab
683 if keyword_name.strip() == '': 683 ↛ 684line 683 didn't jump to line 684 because the condition on line 683 was never true2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
684 raise ValueError('Keyword name can not be "%s"' % keyword_name)
685 self.normalized_name = normalize_kw_name(keyword_name) 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
686 # print(f"DEBUG: ctlcommands.py FindOccurrences INIT keyword_name={keyword_name}")
687 self._keyword_name = keyword_name 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
688 self._keyword_info = keyword_info 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
689 self.normalized_name_res = None 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
690 if self._keyword_info: 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
691 self.normalized_name_res = (keyword_name if '.' in keyword_name 1y
692 else (self._keyword_info.source.replace('.robot', '').replace('.resource', '')
693 +"."+keyword_name))
694 self._keyword_source = self._keyword_info.source 1y
695 # if keyword_name == self.normalized_name_res:
696 # self.normalized_name_res = None
697 else:
698 self._keyword_source = None 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x z 4
699 self.prefix = prefix 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
700 if self.prefix and not self.normalized_name_res: 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
701 self.normalized_name_res = f"{self.prefix}.{self._keyword_name}" 14
702 # print(f"DEBUG: ctlcommands.py FindOccurrences INIT normalized_name_res={self.normalized_name_res}"
703 # f"\nSOURCE={self._keyword_source} PREFIX={self.prefix}")
704 self._keyword_regexp = self._create_regexp(keyword_name) 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
706 @staticmethod 1ab
707 def _create_regexp(keyword_name): 1ab
708 if variablematcher.contains_scalar_variable(keyword_name) and \ 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
709 not variablematcher.is_variable(keyword_name):
710 kw = lambda: 0 1Xwx
711 kw.arguments = None 1Xwx
712 kw.name = keyword_name 1Xwx
713 return EmbeddedArgsHandler(kw).name_regexp 1Xwx
714 else: # Certain kws are not found when with Gherkin
715 name_regexp = fr'^{re.escape(keyword_name)}$' # DEBUG removed (.*?) to ignore prefixed by resources 2kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw y z 4
716 name = re.compile(name_regexp, re.IGNORECASE) 2kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw y z 4
717 return name 2kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw y z 4
719 def execute(self, context): 1ab
720 # print(f"DEBUG: ctrlcommands FindOccurrences EXECUTE context={context}")
721 self._keyword_source = \ 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
722 self._keyword_info and self._keyword_info.source or \
723 self._find_keyword_source(context.datafile_controller)
724 """ DEBUG: this is always defined at init 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
725 if not self.normalized_name_res:
726 self.normalized_name_res = (self._keyword_name if '.' in self._keyword_name
727 else (self._keyword_source.replace('.robot', '').replace('.resource', '')
728 +"."+self._keyword_name))
729 """
730 if self._keyword_name == self.normalized_name_res and '.' in self._keyword_name: 730 ↛ 731line 730 didn't jump to line 731 because the condition on line 730 was never true2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
731 self._keyword_name = self._keyword_name.split('.')[-1]
732 return self._find_occurrences_in(self._items_from(context)) 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
734 def _items_from(self, context): 1ab
735 for df in context.datafiles: 2X kbN Y 1 2 { ( O lbk d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
736 # print(f"DEBUG: ctrlcommands FindOccurrences _items_from FILENAME: df={df.source}")
737 self._yield_for_other_threads() 2X kbN Y 1 2 { ( O lbk d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
738 if self._items_from_datafile_should_be_checked(df): 2X kbN Y 1 2 { ( O lbk d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
739 for item in self._items_from_datafile(df): 2X kbN Y 1 2 { ( O lbk d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
740 yield item 2X kbN Y 1 2 { ( O lbk d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
742 def _items_from_datafile_should_be_checked(self, datafile): 1ab
743 if datafile.filename and \ 2X kbN Y 1 2 { ( O lbk d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
744 os.path.basename(datafile.filename) == self._keyword_source:
745 return True 1XNkoqPQRST/:;8|wxyz4
746 return self._find_keyword_source(datafile) == self._keyword_source 2kbY 1 2 { ( O lbd e f l r s t u v m n g c h p i j P Q R S T / : ; 8 | mbnbobw x y 4
748 def _items_from_datafile(self, df): 1ab
749 for setting in df.settings: 2X kbN Y 1 2 { ( O lbI K L J k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
750 yield setting 2X kbN Y 1 2 { ( O lbI K L J k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
751 for test_items in (self._items_from_test(test) for test in df.tests): 2X kbN Y 1 2 ( O lbI K L J k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
752 for item in test_items: 2X kbN Y 1 2 ( O lbI K L J k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 mbnbobw x y z 4
753 yield item 2X kbN Y 1 2 ( O lbI K L J k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 mbnbobw x y z 4
754 for kw_items in (self._items_from_keyword(kw) for kw in df.keywords): 2kbN O lbI K L J k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
755 for item in kw_items: 2kbN O lbI K L J k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
756 # print(f"DEBUG: ctrlcommands FindOccurrences _items_from_datafile kw_items yield {item}"
757 # f"\nself._keyword_source = {self._keyword_source}")
758 yield item 2kbN O lbI K L J k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
760 def _items_from_keyword(self, kw): 1ab
761 return chain([kw.keyword_name] if kw.source == self._keyword_source 2kbN O lbk d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
762 else [], kw.steps, [kw.setup] if kw.setup else [], [kw.teardown] if kw.teardown else [])
764 @staticmethod 1ab
765 def _items_from_test(test): 1ab
766 return chain(test.settings, test.steps) 2X kbN Y 1 2 ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 mbnbobw x y z 4
768 def _find_keyword_source(self, datafile_controller): 1ab
769 item_info = datafile_controller.keyword_info(None, self._keyword_name) 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
770 # print(f"DEBUG: ctrlcommands _find_keyword_source datafile_controller={datafile_controller}"
771 # f"item_info={item_info}")
772 return item_info.source if item_info else None 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
774 def _find_occurrences_in(self, items): 1ab
775 # print(f"DEBUG: ctrlcommands _find_occurrences_in ENTER normalized_name={self.normalized_name} WITH resource"
776 # f" {self.normalized_name_res} PREFIX={self.prefix}\n"
777 # f"LIST OF ITEMS={items}")
778 """ DEBUG: not conditioning
779 if not self._keyword_source.startswith(self.prefix):
780 print(f"DEBUG: ctrlcommands FindOccurrences _find_occurrences_in SKIP SEARCH"
781 f" self._keyword_source={self._keyword_source}\n"
782 f"prefix={self.prefix}")
783 yield None
784 else:
785 """
786 for item in items: 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
787 # print(f"DEBUG: ctrlcommands _find_occurrences_in searching item={item}")
788 if isinstance(self.normalized_name_res, str) and (self.prefix and 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
789 self.normalized_name_res.startswith(self.prefix) and
790 item.contains_keyword(self.normalized_name_res)):
791 # This block is active when finding from a cell with resource prefix
792 # print(f"DEBUG: ctrlcommands _find_occurrences_in searching item={item} ADD TO OCCURRENCES: FOUND "
793 # f"{self.normalized_name_res} "
794 # f"kwsource={self._keyword_source}")
795 yield Occurrence(item, self.normalized_name_res) 14
796 elif self._contains_exact_item(item): 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
797 # print(f"DEBUG: ctrlcommands _find_occurrences_in searching item={item} NAME={self._keyword_name}"
798 # f" source={self._keyword_source}\n"
799 # f"self.normalized_name_res={self.normalized_name_res} parent={item.parent}\n"
800 # f" PREFIX={self.prefix}")
801 # print(f"DEBUG: ctrlcommands _find_occurrences_in searching item type = {type(item)}"
802 # f" kwsource={self._keyword_source}")
803 # if self._keyword_source.startswith(self.prefix):
804 # print(f"DEBUG: ctrlcommands _find_occurrences_in searching ADD TO OCCURRENCES: {self._keyword_name}")
805 yield Occurrence(item, self._keyword_name) 1XNY12{(OIKLJ3kdeflrstuvmngochpijqPQRST/:;8|wxyz4
807 def _contains_exact_item(self, item): 1ab
808 from .tablecontrollers import VariableTableController 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
809 match_name = self._contains_item(item) 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
810 # print(f"DEBUG: ctrlcommands _find_occurrences_in _contains_exact_item Match Name is TYPE {type(match_name)}")
811 if match_name and isinstance(match_name, re.Match) and '.' in match_name.string and self.prefix: 811 ↛ 815line 811 didn't jump to line 815 because the condition on line 811 was never true2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
812 # print(f"DEBUG: ctrlcommands _find_occurrences_in _contains_exact_item PREFIXED Name={match_name}"
813 # f"\n groups={match_name.groups()} string={match_name.string}"
814 # f" RETURNS {match_name.string.startswith(self.prefix)}")
815 return match_name.string.startswith(self.prefix) # Avoid false positive for res prefixed
816 elif match_name or (not isinstance(item, VariableTableController) and 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
817 (item.contains_keyword(self.normalized_name) or
818 item.contains_keyword(self.normalized_name.replace(' ', '_')) or
819 item.contains_keyword(self._keyword_name) )):
820 return True 1XNY12{(OIKLJ3kdeflrstuvmngochpijqPQRST/:;8|wxyz4
822 def _contains_item(self, item): 1ab
823 self._yield_for_other_threads() 2X kbN Y 1 2 { ( O lbk d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
824 return item.contains_keyword(self._keyword_regexp or self.normalized_name_res) 2X kbN Y 1 2 { ( O lbk d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
825 # DEBUG: self._keyword_name
827 @staticmethod 1ab
828 def _yield_for_other_threads(): 1ab
829 # GIL !?#!!!
830 # THIS IS TO ENSURE THAT OTHER THREADS WILL GET SOME SPACE ALSO
831 time.sleep(0) 2X kbN Y 1 2 { ( O lbI K L J 3 k d e f l r s t u v m n g o c h p i j q P Q R S T / : ; 8 | mbnbobw x y z 4
834class FindVariableOccurrences(FindOccurrences): 1ab
836 def _contains_item(self, item): 1ab
837 self._yield_for_other_threads() 1IKLJ3
838 return item.contains_variable(self._keyword_name) 1IKLJ3
840 def _items_from_datafile(self, df): 1ab
841 for itm in FindOccurrences._items_from_datafile(self, df): 1IKLJ
842 yield itm 1IKLJ
843 yield df.variables 1IKLJ
845 def _items_from_controller(self, ctrl): 1ab
846 from .macrocontrollers import TestCaseController 13
848 if isinstance(ctrl, TestCaseController): 13
849 return self._items_from_test(ctrl) 13
850 else:
851 return self._items_from_keyword(ctrl) 13
853 def _items_from_keyword(self, kw): 1ab
854 return chain([kw.keyword_name], kw.steps, kw.settings) 1IKLJ3
856 def _items_from(self, context): 1ab
857 self._context = context 1IKLJ3
858 if self._is_local_variable(self._keyword_name, context): 1IKLJ3
859 for item in self._items_from_controller(context): 13
860 yield item 13
861 else:
862 for df in context.datafiles: 1IKLJ
863 self._yield_for_other_threads() 1IKLJ
864 if self._items_from_datafile_should_be_checked(df): 1IKLJ
865 for item in self._items_from_datafile(df): 1IKLJ
866 yield item 1IKLJ
868 def _items_from_datafile_should_be_checked(self, datafile): 1ab
869 if self._is_file_variable(self._keyword_name, self._context): 1IKLJ
870 return datafile in [self._context.datafile_controller] + \ 1L
871 self._get_all_where_used(self._context)
872 elif self._is_imported_variable(self._keyword_name, self._context): 872 ↛ 873line 872 didn't jump to line 873 because the condition on line 872 was never true1IKJ
873 return datafile in [self._get_source_of_imported_var(
874 self._keyword_name, self._context)] + \
875 self._get_all_where_used(self._get_source_of_imported_var(
876 self._keyword_name, self._context))
877 else:
878 return True 1IKJ
880 @staticmethod 1ab
881 def _is_local_variable(name, context): 1ab
882 if isinstance(context, settingcontrollers.VariableController): 882 ↛ 883line 882 didn't jump to line 883 because the condition on line 882 was never true1IKLJ3
883 return False
884 return name in context.get_local_variables() or \ 1IKLJ3
885 any(step.contains_variable_assignment(name)
886 for step in context.steps)
888 @staticmethod 1ab
889 def _is_file_variable(name, context): 1ab
890 return context.datafile_controller.variables.contains_variable(name) 1IKLJ
892 def _is_imported_variable(self, name, context): 1ab
893 return self._get_source_of_imported_var(name, context) not in \ 1IKJ
894 [None, context.datafile_controller]
896 @staticmethod 1ab
897 def _is_builtin_variable(name): 1ab
898 return name in list(namespace._VariableStash.global_variables.keys())
900 def _get_source_of_imported_var(self, name, context): 1ab
901 for df in self._get_all_imported(context): 1IKJ
902 if df.variables.contains_variable(name): 902 ↛ 903line 902 didn't jump to line 903 because the condition on line 902 was never true1IKJ
903 return df
904 return None 1IKJ
906 @staticmethod 1ab
907 def _get_all_imported(context): 1ab
908 files = [context.datafile_controller] 1IKJ
909 for f in files: 1IKJ
910 files += [imp.get_imported_controller() 1IKJ
911 for imp in f.imports if imp.is_resource and
912 imp.get_imported_controller() not in files]
913 return files 1IKJ
915 @staticmethod 1ab
916 def _get_all_where_used(context): 1ab
917 from .filecontrollers import ResourceFileController 1L
919 files = [context.datafile_controller] 1L
920 for f in files: 1L
921 if isinstance(f, ResourceFileController): 1L
922 files += [imp.datafile_controller 1L
923 for imp in f.get_where_used()]
924 return files 1L
927def add_keyword_from_cells(cells): 1ab
928 if not cells: 928 ↛ 929line 928 didn't jump to line 929 because the condition on line 928 was never true2!c
929 raise ValueError('Keyword can not be empty')
930 while cells[0] == '': 930 ↛ 931line 930 didn't jump to line 931 because the condition on line 930 was never true2!c
931 cells.pop(0)
932 name = cells[0] 2!c
933 args = cells[1:] 2!c
934 argstr = ' | '.join(('${arg%s}' % (i + 1) for i in range(len(args)))) 2!c
935 return AddKeyword(name, argstr) 2!c
938class AddKeyword(_ReversibleCommand): 1ab
940 def __init__(self, new_kw_name, args=None): 1ab
941 self._kw_name = new_kw_name 2Ab}c~c!c1cadbdcddded'cOcRcScTcUcVcWc
942 self._args = args or [] 2Ab}c~c!c1cadbdcddded'cOcRcScTcUcVcWc
944 def _execute(self, context): 1ab
945 kw = context.create_keyword(self._kw_name, self._args) 2Ab}c~c!c1cadbdcddded'cOcRcScTcUcVcWc
946 self._undo_command = RemoveMacro(kw) 2Ab}c~c!c1cadbdcddded'cOcRcScTcUcVcWc
947 return kw 2Ab}c~c!c1cadbdcddded'cOcRcScTcUcVcWc
949 def _get_undo_command(self): 1ab
950 return self._undo_command 2Ab}c~c!c1cadbdcddded'cOcRcScTcUcVcWc
953class AddTestCase(_Command): 1ab
955 def __init__(self, new_test_name): 1ab
956 self._test_name = new_test_name 2Gdfd
958 def execute(self, context): 1ab
959 return context.create_test(self._test_name) 2Gdfd
962class _AddDataFile(_Command): 1ab
964 def __init__(self, path): 1ab
965 self._path = path 2tdud
967 def execute(self, context): 1ab
968 ctrl = self._add_data_file(context) 2tdud
969 context.notify_suite_added(ctrl) 2tdud
970 return ctrl 2tdud
972 def _add_data_file(self, context): 1ab
973 raise NotImplementedError(self.__class__.__name__)
976class AddTestCaseFile(_AddDataFile): 1ab
978 def _add_data_file(self, context): 1ab
979 return context.new_test_case_file(self._path) 2td
982class AddTestDataDirectory(_AddDataFile): 1ab
984 def _add_data_file(self, context): 1ab
985 return context.new_test_data_directory(self._path) 2ud
988class CreateNewFileProject(_Command): 1ab
990 def __init__(self, path, tasks, lang): 1ab
991 self._path = path
992 self._tasks = tasks
993 self._lang = lang
995 def execute(self, context): 1ab
996 context.new_file_project(self._path, self._tasks, self._lang)
999class CreateNewDirectoryProject(_Command): 1ab
1001 def __init__(self, path, tasks, lang): 1ab
1002 self._path = path
1003 self._tasks = tasks
1004 self._lang = lang
1006 def execute(self, context): 1ab
1007 context.new_directory_project(self._path, self._tasks, self._lang)
1010class SetFileFormat(_Command): 1ab
1012 def __init__(self, format): 1ab
1013 self._format = format
1015 def execute(self, context): 1ab
1016 context.save_with_new_format(self._format)
1019class SetFileFormatRecuresively(_Command): 1ab
1021 def __init__(self, format): 1ab
1022 self._format = format
1024 def execute(self, context): 1ab
1025 context.save_with_new_format_recursive(self._format)
1028class RemoveVariable(_ReversibleCommand): 1ab
1030 def __init__(self, var_controller): 1ab
1031 self._var_controller = var_controller 2U xb
1032 self._undo_command = AddVariable(var_controller.name, 2U xb
1033 var_controller.value,
1034 var_controller.comment)
1036 def _execute(self, context): 1ab
1037 context.datafile_controller. \
1038 variables.remove_var(self._var_controller)
1040 def _get_undo_command(self): 1ab
1041 return self._undo_command
1044class AddVariable(_ReversibleCommand): 1ab
1046 def __init__(self, name, value, comment): 1ab
1047 self._name = name 2U xb
1048 self._value = value 2U xb
1049 self._comment = comment 2U xb
1051 def _execute(self, context): 1ab
1052 var_controller = context.datafile_controller. \ 2U xb
1053 variables.add_variable(self._name, self._value, self._comment)
1054 self._undo_command = RemoveVariable(var_controller) 2U xb
1055 return var_controller 2U xb
1057 def _get_undo_command(self): 1ab
1058 return self._undo_command 2U xb
1060 def __str__(self): 1ab
1061 return 'AddVariable("%s", "%s", "%s")' % \
1062 (self._name, self._value, self._comment)
1065class RecreateMacro(_ReversibleCommand): 1ab
1067 def __init__(self, user_script): 1ab
1068 self._user_script = user_script 2Ab1c'cOcfd
1070 def _execute(self, context): 1ab
1071 _ = context 2Oc
1072 self._user_script.recreate() 2Oc
1074 def _get_undo_command(self): 1ab
1075 return RemoveMacro(self._user_script) 2Oc
1078class RemoveMacro(_ReversibleCommand): 1ab
1080 def __init__(self, item): 1ab
1081 self._item = item 2Ab}c~c!c1cadbdcddded'cOcfdRcScTcUcVcWc
1083 def _execute(self, context): 1ab
1084 _ = context 2Ab1c'cOcfd
1085 self._item.delete() 2Ab1c'cOcfd
1087 def _get_undo_command(self): 1ab
1088 return RecreateMacro(self._item) 2Ab1c'cOcfd
1091class ExtractKeyword(_Command): 1ab
1093 def __init__(self, new_kw_name, new_kw_args, step_range): 1ab
1094 self._name = new_kw_name 2odpd
1095 self._args = new_kw_args 2odpd
1096 self._rows = step_range 2odpd
1098 def _params(self): 1ab
1099 return self._name, self._args, self._rows
1101 def execute(self, context): 1ab
1102 context.extract_keyword(self._name, self._args, self._rows) 2odpd
1103 context.notify_steps_changed() 2odpd
1104 context.clear_undo() 2odpd
1107def extract_scalar(name, value, comment, cell): 1ab
1108 # print(f"DEBUG: ctrlcommands.py ExtractScalar name{name} value{value}, comment{comment}, cell{cell}")
1109 return CompositeCommand(AddVariable(name, value, comment), 2xb
1110 ChangeCellValue(cell[0], cell[1], name))
1113def extract_list(name, value, comment, cells): 1ab
1114 row, col = cells[0] 1U
1115 return CompositeCommand(AddVariable(name, value, comment), 1U
1116 ChangeCellValue(row, col, name),
1117 delete_cells(
1118 (row, col + 1), (row, col + len(cells) - 1)))
1121class ChangeCellValue(_StepsChangingCommand): 1ab
1123 def __init__(self, row, col, value, insert=False): 1ab
1124 self._row = row 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,brb5bqcrcsc~btcucvcwcxcXbyc5 hcVb3bicacjczcbbcbWbYbZb) %bfbgb#b$b- U xbAcBcCc6bDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1125 self._col = col 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,brb5bqcrcsc~btcucvcwcxcXbyc5 hcVb3bicacjczcbbcbWbYbZb) %bfbgb#b$b- U xbAcBcCc6bDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1126 self._value = self._escape_newlines(value) 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,brb5bqcrcsc~btcucvcwcxcXbyc5 hcVb3bicacjczcbbcbWbYbZb) %bfbgb#b$b- U xbAcBcCc6bDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1127 self._insert = insert 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,brb5bqcrcsc~btcucvcwcxcXbyc5 hcVb3bicacjczcbbcbWbYbZb) %bfbgb#b$b- U xbAcBcCc6bDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1128 self._undo_command = None 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,brb5bqcrcsc~btcucvcwcxcXbyc5 hcVb3bicacjczcbbcbWbYbZb) %bfbgb#b$b- U xbAcBcCc6bDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1130 def change_steps(self, context): 1ab
1131 steps = context.steps 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}bM ,brb5bqcrcsc~btcucvcwcxcXbyc5 zcbbcbYbZb) %bfbgb#b$b- U xbAcBcCcDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1132 while len(steps) <= self._row: 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}bM ,brb5bqcrcsc~btcucvcwcxcXbyc5 zcbbcbYbZb) %bfbgb#b$b- U xbAcBcCcDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1133 context.add_step(len(steps)) 2-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRb{b|b}bM ~bd e f g c h
1134 steps = context.steps 2-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRb{b|b}bM ~bd e f g c h
1135 step = self._step(context) 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}bM ,brb5bqcrcsc~btcucvcwcxcXbyc5 zcbbcbYbZb) %bfbgb#b$b- U xbAcBcCcDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1136 self._undo_command = ChangeCellValue( 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}bM ,brb5bqcrcsc~btcucvcwcxcXbyc5 zcbbcbYbZb) %bfbgb#b$b- U xbAcBcCcDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1137 self._row, self._col, step.get_value(self._col), insert=False)
1138 if self._insert: 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}bM ,brb5bqcrcsc~btcucvcwcxcXbyc5 zcbbcbYbZb) %bfbgb#b$b- U xbAcBcCcDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1139 step.insert_value_before(self._col, self._value) 2tc0b
1140 # print(f"DEBUG: change_steps after insert cell Line: {context.steps[self._row].as_list()}")
1141 else:
1142 step.change(self._col, self._value) 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}bM ,brb5bqcrcsc~bucvcwcxcXbyc5 zcbbcbYbZb) %bfbgb#b$b- U xbAcBcCcDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1143 self._step(context).remove_empty_columns_from_end() 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}bM ,brb5bqcrcsc~btcucvcwcxcXbyc5 zcbbcbYbZb) %bfbgb#b$b- U xbAcBcCcDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1144 # DEGUG: Next validation is not possible to call when the step is Indented
1145 # assert self._validate_postcondition(context), 'Should have correct value after change'
1146 return True 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}bM ,brb5bqcrcsc~btcucvcwcxcXbyc5 zcbbcbYbZb) %bfbgb#b$b- U xbAcBcCcDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1148 @staticmethod 1ab
1149 def _escape_newlines(item): 1ab
1150 for newline in ('\r\n', '\n', '\r'): 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,brb5bqcrcsc~btcucvcwcxcXbyc5 hcVb3bicacjczcbbcbWbYbZb) %bfbgb#b$b- U xbAcBcCc6bDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1151 item = item.replace(newline, '\\n') 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,brb5bqcrcsc~btcucvcwcxcXbyc5 hcVb3bicacjczcbbcbWbYbZb) %bfbgb#b$b- U xbAcBcCc6bDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1152 return item 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}b1b2bM SbTbUb,brb5bqcrcsc~btcucvcwcxcXbyc5 hcVb3bicacjczcbbcbWbYbZb) %bfbgb#b$b- U xbAcBcCc6bDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1154 def _validate_postcondition(self, context): 1ab
1155 value = self._step(context).get_value(self._col).strip()
1156 should_be = self._value.strip()
1157 # print(f"DEBUG: change_steps _validate_postcondition: value={value} should_be={should_be}")
1158 if value == should_be:
1159 return True
1160 return (value.replace(' ', '') == 'FOR' and
1161 should_be.replace(' ', '') == 'FOR') or \
1162 (value.replace(' ', '') == 'FOR' and
1163 should_be.replace(' ', '').upper() == ':FOR') or \
1164 (value.replace(' ', '') == 'END' and
1165 should_be.replace(' ', '') == 'END')
1167 def _get_undo_command(self): 1ab
1168 return self._undo_command 2a lc-b.b/b:bAbOb;b=b?b@b[b]bPbQb^b_b`bRbmcncocpc{b|b}bM ,brb5bqcrcsc~btcucvcwcxcXbyc5 zcbbcbYbZb) %bfbgb#b$b- U xbAcBcCcDcsbEc4bFcGcHcIc0bJcKcLcd e f g c h
1170 def __str__(self): 1ab
1171 return '%s(%s, %s, "%s")' % \
1172 (self.__class__.__name__, self._row, self._col, self._value)
1175class SaveFile(_Command): 1ab
1177 def __init__(self, reformat=False): 1ab
1178 self._reformat = reformat 2a mdM jd:c;c=c?c@c#c$c%cndRcScTcUcVcWc
1180 def execute(self, context): 1ab
1181 RideSaving(path=context.filename, datafile=context).publish() 2a mdM jd:c;c=c?c@c#c$c%cndRcScTcUcVcWc
1182 datafile_controller = context.datafile_controller 2a mdM jd:c;c=c?c@c#c$c%cndRcScTcUcVcWc
1183 if self._reformat: 2a mdM jd:c;c=c?c@c#c$c%cndRcScTcUcVcWc
1184 for macro_controller in chain(datafile_controller.tests, datafile_controller.keywords): 1M
1185 macro_controller.execute(Purify()) 1M
1186 datafile_controller.save() 2a mdM jd:c;c=c?c@c#c$c%cndRcScTcUcVcWc
1187 datafile_controller.unmark_dirty() 2a mdM jd:c;c=c?c@c#c$c%cndRcScTcUcVcWc
1188 RideSaved(path=context.filename).publish() 2a mdM jd:c;c=c?c@c#c$c%cndRcScTcUcVcWc
1191class SaveAll(_Command): 1ab
1193 def __init__(self, reformat=False): 1ab
1194 self._reformat = reformat
1196 def execute(self, context): 1ab
1197 for datafile_controller in context._get_all_dirty_controllers():
1198 if datafile_controller.has_format():
1199 datafile_controller.execute(SaveFile(self._reformat))
1200 RideSaveAll().publish()
1203class Purify(_Command): 1ab
1205 def execute(self, context): 1ab
1206 i = 0 2M rbdbCb) sb
1207 while True: 2M rbdbCb) sb
1208 if len(context.steps) <= i: 2M rbdbCb) sb
1209 break 2M rbdbCb) sb
1210 # Steps can change during this operation
1211 # this is why index based iteration - step reference can be stale
1212 step = context.steps[i] 2M rbdbCb) sb
1213 step.remove_empty_columns_from_end() 2M rbdbCb) sb
1214 # print(f"DEBUG: Purify after remove_empty_columns_from_end step={step}")
1215 # DEBUG Purify not changing rmpty columns from begining
1216 # if step.has_only_comment():
1217 # step.remove_empty_columns_from_beginning()
1218 i += 1 2M rbdbCb) sb
1219 # print(f"DEBUG: Purify before DeleteRows")
1220 context.execute(delete_rows(context.get_empty_rows())) 2M rbdbCb) sb
1221 context.notify_steps_changed() 2M rbdbCb) sb
1224class InsertCell(_StepsChangingCommand): 1ab
1226 def __init__(self, row, col): 1ab
1227 self._row = row 2SbTbUb5 Vbac7b8bWb9bXc!bU 6b4bYcZc0c0b
1228 self._col = col 2SbTbUb5 Vbac7b8bWb9bXc!bU 6b4bYcZc0c0b
1230 def _params_str(self): 1ab
1231 return '%s, %s' % (self._row, self._col)
1233 def change_steps(self, context, delete=False): 1ab
1234 self._step(context).shift_right(self._col) 25 7b8bWb9bXc!b6bYcZc0c
1235 if not delete: 1235 ↛ 1237line 1235 didn't jump to line 1237 because the condition on line 1235 was always true25 7b8bWb9bXc!b6bYcZc0c
1236 self._step(context).recreate(context.steps[self._row].as_list()) 25 7b8bWb9bXc!b6bYcZc0c
1237 assert self._step(context).get_value(self._col) == '', 'Should have an empty value after insert' 25 7b8bWb9bXc!b6bYcZc0c
1238 return True 25 7b8bWb9bXc!b6bYcZc0c
1240 def _get_undo_command(self): 1ab
1241 return DeleteCell(self._row, self._col) 25 7b8bWb9bXc!b6bYcZc0c
1244class DeleteCell(_StepsChangingCommand): 1ab
1246 def __init__(self, row, col): 1ab
1247 self._row = row 2SbTbUb5 Vbac7b8bWb9bXc!bU 6b4bYcZc0c0b
1248 self._col = col 2SbTbUb5 Vbac7b8bWb9bXc!bU 6b4bYcZc0c0b
1249 self._undo_command = None 2SbTbUb5 Vbac7b8bWb9bXc!bU 6b4bYcZc0c0b
1251 def _params(self): 1ab
1252 return self._row, self._col
1254 def change_steps(self, context): 1ab
1255 step = self._step(context) 2SbTbUb5 VbacU 6b4b0b
1256 # print(f"DEBUG: DeleteCell enter change: {step.as_list()}")
1257 self._undo_command = StepsChangingCompositeCommand( 2SbTbUb5 VbacU 6b4b0b
1258 InsertCell(self._row, self._col),
1259 ChangeCellValue(self._row, self._col,
1260 step.get_value(self._col), insert=False))
1261 step.shift_left(self._col, delete=True) 2SbTbUb5 VbacU 6b4b0b
1262 return True 2SbTbUb5 VbacU 6b4b0b
1264 def _get_undo_command(self): 1ab
1265 return self._undo_command 2SbTbUb5 VbacU 6b4b0b
1268class _RowChangingCommand(_StepsChangingCommand): 1ab
1270 def __init__(self, row): 1ab
1271 """Command that will operate on a given logical `row` of test/user keyword.
1273 Giving -1 as `row` means that operation is done on the last row.
1274 """
1275 self._row = row 2a 1b2bM bc'b+b3c4c5c6c5bccNcdcecfcgchc3bicjcPcbbcbWbQcdbCb) (bkcfbgb)b*bMb-
1277 def change_steps(self, context): 1ab
1278 if len(context.steps) <= self._row: 2a 1b2bM bc'b+b3c4c5c6c5bccNcdcecfcgchc3bicjcPcbbcbWbQcdbCb) (bkcfbgb)b*bMb-
1279 return False 2NcPcQc
1280 self._change_value(context) 2a 1b2bM bc'b+b3c4c5c6c5bccdcecfcgchc3bicjcbbcbWbdbCb) (bkcfbgb)b*bMb-
1281 return True 2a 1b2bM bc'b+b3c4c5c6c5bccdcecfcgchc3bicjcbbcbWbdbCb) (bkcfbgb)b*bMb-
1283 def __str__(self): 1ab
1284 return '%s(%s)' % (self.__class__.__name__, self._row)
1287class DeleteRow(_RowChangingCommand): 1ab
1289 def _change_value(self, context): 1ab
1290 step = context.steps[self._row] 2a 1b2bM hc3bicjcWbdbCb) Mb-
1291 # print(f"DEBUG: DeleteRow enter change row={self._row}: {step.as_list()}")
1292 self._undo_command = StepsChangingCompositeCommand( 2a 1b2bM hc3bicjcWbdbCb) Mb-
1293 AddRow(self._row), paste_area((self._row, 0), [step.as_list()]))
1294 context.remove_step(self._row) 2a 1b2bM hc3bicjcWbdbCb) Mb-
1296 def _get_undo_command(self): 1ab
1297 if hasattr(self, '_undo_command'): 2a 1b2bM hc3bicjcPcWbdbCb) Mb-
1298 return self._undo_command 2a 1b2bM hc3bicjcWbdbCb) Mb-
1299 return AddRow(self._row) 2Pc
1302class AddRow(_RowChangingCommand): 1ab
1304 def _change_value(self, context): 1ab
1305 row = self._row if self._row != -1 else len(context.steps) 2+b3c4c5c6c5bbbcbdbCbMb-
1306 context.add_step(row) 2+b3c4c5c6c5bbbcbdbCbMb-
1307 # print(f"DEBUG: AddRow after adding = {context.steps}")
1308 assert not (any(i for i in self._step(context).as_list() if i)), \ 2+b3c4c5c6c5bbbcbdbCbMb-
1309 'Should have an empty row after add instead %r' % \
1310 self._step(context).as_list()
1312 def _get_undo_command(self): 1ab
1313 return DeleteRow(self._row) 2+b3c4c5c6c5bbbcbQcdbCbMb-
1316class CommentRow(_RowChangingCommand): 1ab
1318 def _change_value(self, context): 1ab
1319 # print(f"DEBUG: enter CommentRow")
1320 self._step(context).comment() 2ccdcecfcgc(bfbgb)b*b
1321 return True 2ccdcecfcgc(bfbgb)b*b
1323 def _get_undo_command(self): 1ab
1324 return UncommentRow(self._row) 2ccNcdcecfcgc(bfbgb)b*b
1327class UncommentRow(_RowChangingCommand): 1ab
1329 def _change_value(self, context): 1ab
1330 self._step(context).uncomment() 2(bkcfbgb)b*b
1331 return True 2(bkcfbgb)b*b
1333 def _get_undo_command(self): 1ab
1334 return CommentRow(self._row) 2Nc(bkcfbgb)b*b
1337class SharpCommentRow(_RowChangingCommand): 1ab
1339 def _change_value(self, context): 1ab
1340 self._step(context).sharp_comment() 2bc'b
1341 return True 2bc'b
1343 def _get_undo_command(self): 1ab
1344 return SharpUncommentRow(self._row) 2bc'b
1347class SharpUncommentRow(_RowChangingCommand): 1ab
1349 def _change_value(self, context): 1ab
1350 self._step(context).sharp_uncomment() 2'b
1351 return True 2'b
1353 def _get_undo_command(self): 1ab
1354 return SharpCommentRow(self._row) 2'b
1357INDENTED_INNER = ['ELSE', 'ELSE IF', 'EXCEPT', 'FINALLY'] 1ab
1358INDENTED_START = ['FOR', 'GROUP', 'IF', 'WHILE', 'TRY'] + INDENTED_INNER 1ab
1361def is_indent_start(cell): 1ab
1362 return cell in INDENTED_START 2A pbqbBbGbwb= LbEb? F 9 B ! ] } G H E C Fb@ D ybzbebHbIbtbubNbJbKbDb* 6 + # $ , % ^ _ ` [ ~ abhbibvbjbZ ' . W V 7 0
1365def is_indent_inner(cell): 1ab
1366 return cell in INDENTED_INNER 2A pbqbF 9 B ! C D ebub* 6 + # $ , % ~ abhbvbjbZ ' W V 7 0
1369class MoveRowsUp(_StepsChangingCommand): 1ab
1371 def __init__(self, rows): 1ab
1372 self._rows = rows 2McA pbqbBbGbwb= LbEb? F 9 B ! )c] } G H E C Fb@ D ybzbebHbIbtbubNbJbKbDb* 6 + # $ , % ^ _ ` [ ~ abhbibvb_c`c{c|cjbZ ' . W V 7 *c+c,c/c0
1374 def _params(self): 1ab
1375 return [self._rows]
1377 def change_steps(self, context): # NOTE: Nevermind the quality of this code. Unit tests are passing ;) 1ab
1379 def non_empty_from_left(line): 2McA = ? F 9 B ! )c] } G H E C @ D * 6 + # $ , % ^ _ ` [ Z ' . W V 7 *c+c,c/c0
1380 assert line >= 0 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1381 steps = context.steps[line].as_list() 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1382 idx = 0 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1383 while idx < len(steps) and steps[idx] == '': 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1384 idx += 1 1A=?F9B!@D*6+#$,%^_`[Z'.WV70
1385 if idx == len(steps): 1385 ↛ 1386line 1385 didn't jump to line 1386 because the condition on line 1385 was never true1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1386 return -1
1387 return idx 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1389 if len(self._rows) == 0 or max(self._rows) > len(context.steps) - 1 or \ 2McA = ? F 9 B ! )c] } G H E C @ D * 6 + # $ , % ^ _ ` [ Z ' . W V 7 *c+c,c/c0
1390 self._first_row == 0 or min(self._rows) < 0:
1391 return False 2Mc)c*c+c,c/c
1392 if len(self._rows) == 1: 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1393 rows = [self._rows[0]] 1A=?F9B!}GHC@D*6+#$,%^_`['W
1394 elif self._rows[-1] <= self._rows[0]: 1]EZ.V70
1395 rows = range(self._rows[-1], self._rows[0] + 1) 170
1396 else:
1397 rows = range(self._rows[0], self._rows[-1] + 1) 1]EZ.V
1399 number_of_steps_before = len(context.steps) 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1400 for row in rows: 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1401 # print(f"\nDEBUG: MoveRowsUp start new: {row=}")
1402 index = non_empty_from_left(row) 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1403 prev_cell_row = row - 1 if row > 0 else 0 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1404 prev_cell = non_empty_from_left(prev_cell_row) 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1405 pre_prev_row = row - 2 if row > 1 else 0 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1406 pre_prev_col = non_empty_from_left(pre_prev_row) 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1407 next_cell_row = row + 1 if row + 1 < number_of_steps_before else row 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1408 next_row_col = non_empty_from_left(next_cell_row) 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1409 add_indent = pre_prev_col > index and (not is_indent_inner(context.steps[row].as_list()[index]) and 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1410 context.steps[row].step_controller_step.cells[index] != 'END' or
1411 is_indent_start(context.steps[pre_prev_row].step_controller_step.
1412 cells[pre_prev_col]))
1413 del_indent = (prev_cell > index and context.steps[row].step_controller_step.cells[index] == 'END') or \ 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1414 (pre_prev_col < index and not is_indent_start(context.steps[pre_prev_row].step_controller_step.
1415 cells[pre_prev_col])) or \
1416 (prev_cell < index and not is_indent_start(context.steps[prev_cell_row].step_controller_step.
1417 cells[prev_cell]))
1418 del_prev_indent = (pre_prev_col < prev_cell) and (is_indent_start(context.steps[prev_cell_row]. 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1419 step_controller_step.cells[prev_cell]))
1420 if is_indent_start(context.steps[row].as_list()[index]) or add_indent: 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1421 new_next_indent = (prev_cell == index and 1=F!C@D*6+,%Z'WV70
1422 (not is_indent_start(context.steps[prev_cell_row].step_controller_step.
1423 cells[prev_cell])
1424 and context.steps[prev_cell_row].step_controller_step.cells[prev_cell] != 'END')) \
1425 or (next_row_col > prev_cell and
1426 is_indent_start(context.steps[prev_cell_row].
1427 step_controller_step.cells[prev_cell])) \
1428 or (not is_indent_inner(context.steps[row].as_list()[index]) and context.
1429 steps[next_cell_row].step_controller_step.cells[next_row_col] != 'END' and
1430 next_row_col < index)
1431 else:
1432 new_next_indent = (pre_prev_col > next_row_col and 1A?F9B]}GHE#$^_`[Z.WV0
1433 context.steps[row].step_controller_step.cells[index] != 'END'
1434 and prev_cell < index) or (
1435 next_row_col > prev_cell and
1436 is_indent_start(
1437 context.steps[next_cell_row].step_controller_step.cells[next_row_col])
1438 and not is_indent_start(context.steps[prev_cell_row].
1439 step_controller_step.cells[prev_cell]))
1440 new_next_deindent = (prev_cell > index and context.steps[row].step_controller_step.cells[index] == 'END') 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1441 keep_indent = not add_indent and pre_prev_col == index and\ 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1442 context.steps[next_cell_row].step_controller_step.cells[next_row_col] == 'END'
1443 context.move_step_up(row) 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1444 new_index = non_empty_from_left(prev_cell_row) 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1445 if new_next_deindent: 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1446 context.steps[row].shift_left(0, delete=False) 1A9B#$ZWV0
1447 if not (keep_indent and del_indent and add_indent) and new_index > index: 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1448 del_indent = True 1AF9B!D*6+#$,%Z'WV70
1449 # In case indent was added at Stepcontroller
1450 if new_index > index + 1: # and (add_indent or not del_indent): 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1451 context.steps[prev_cell_row].shift_left(0, delete=False) 1ABD6WV
1452 del_indent = True # not del_indent 1ABD6WV
1453 prev_cell = non_empty_from_left(prev_cell_row) 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1454 if keep_indent and new_index > index: 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1455 for _ in range(index, new_index): 1D
1456 context.steps[prev_cell_row].shift_left(0, delete=False) 1D
1457 if add_indent and (not is_indent_start(context.steps[prev_cell_row].step_controller_step.cells[prev_cell]) 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1458 and context.steps[row].step_controller_step.cells[index] != 'END') or new_next_indent:
1459 context.steps[row].shift_right(0) 1=C@D6ZWV0
1460 if add_indent and not is_indent_start(context.steps[prev_cell_row].step_controller_step. 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1461 cells[pre_prev_col]):
1462 context.steps[prev_cell_row].shift_right(0) # new_next_indent and\ 1F!%'7
1463 if (del_indent and not keep_indent) or \ 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1464 (keep_indent and context.steps[prev_cell_row].step_controller_step.cells[index] == 'END'
1465 and not is_indent_start(context.steps[row].step_controller_step.cells[index])):
1466 context.steps[prev_cell_row].shift_left(0, delete=False) 1A?F9B!D*6+#$,%[Z'.WV70
1467 if del_prev_indent and not keep_indent: 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1468 context.steps[prev_cell_row].shift_left(0, delete=False) 1WV
1469 if keep_indent and prev_cell > index and \ 1469 ↛ 1472line 1469 didn't jump to line 1472 because the condition on line 1469 was never true1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1470 len(context.steps[prev_cell_row].step_controller_step.cells) > prev_cell \
1471 and context.steps[prev_cell_row].step_controller_step.cells[prev_cell] == 'END':
1472 context.steps[prev_cell_row].shift_left(0, delete=False)
1473 assert len(context.steps) == number_of_steps_before 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1474 return True 1A=?F9B!]}GHEC@D*6+#$,%^_`[Z'.WV70
1476 @property 1ab
1477 def _last_row(self): 1ab
1478 return self._rows[-1]
1480 @property 1ab
1481 def _first_row(self): 1ab
1482 return self._rows[0] 2McA = ? F 9 B ! )c] } G H E C @ D * 6 + # $ , % ^ _ ` [ Z ' . W V 7 *c+c,c0
1484 def _get_undo_command(self): 1ab
1485 return MoveRowsDown([r - 1 for r in self._rows if r > 0]) 2McA = ? F 9 B ! )c] } G H E C @ D * 6 + # $ , % ^ _ ` [ Z ' . W V 7 *c+c,c/c0
1488class MoveRowsDown(_StepsChangingCommand): 1ab
1489 """ Moves a single row down, argument single element list, or lines in a range for a two elements argument """
1491 def __init__(self, rows: (list, tuple)): 1ab
1492 self._rows = rows 2McA pbqbBbGbwb= LbEb? F 9 B ! )c] } G H E C Fb@ D ybzbebHbIbtbubNbJbKbDb* 6 + # $ , % ^ _ ` [ ~ abhbibvb_c`c{c|cjbZ ' . W V 7 *c+c,c/c0
1493 self._context = [] 2McA pbqbBbGbwb= LbEb? F 9 B ! )c] } G H E C Fb@ D ybzbebHbIbtbubNbJbKbDb* 6 + # $ , % ^ _ ` [ ~ abhbibvb_c`c{c|cjbZ ' . W V 7 *c+c,c/c0
1495 def _params(self): 1ab
1496 return [self._rows]
1498 def change_steps(self, context): # NOTE: Nevermind the quality of this code. Unit tests are passing ;) 1ab
1499 if len(self._rows) == 0 or max(self._rows) >= len(context.steps) - 1 or min(self._rows) < 0: 2McA pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvb_c`c{c|cjb
1500 return False 2Mcab_c`c{c|c
1501 if len(self._rows) == 1: 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1502 rows = [self._rows[0]] 2A pbqbBbGbwbLbF B G H C FbD ybzbebHbIbtbubNbJbKbDb~ ab
1503 elif self._rows[-1] <= self._rows[0]: 2EbE hbibvbjb
1504 rows = range(self._rows[0], self._rows[-1] - 1, -1) 2vbjb
1505 else:
1506 rows = range(self._rows[-1], self._rows[0] - 1, -1) 2EbE hbib
1507 number_of_steps_before = len(context.steps) 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1508 for row in rows: 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1509 moving_start = context.steps[row]._first_non_empty_cell() 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1510 existing_start = context.steps[row + 1]._first_non_empty_cell() 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1511 keep_indent = moving_start == existing_start # No indented cells 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1512 if row >= 1: 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1513 previous_start = context.steps[row - 1]._first_non_empty_cell() 2A pbqbBbGbwbEbF B E C FbybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1514 else:
1515 previous_start = moving_start 2LbEbG H E C D tb~ abib
1516 decrease_indent = moving_start > existing_start and ( 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1517 not is_indent_start(context.steps[row + 1].as_list()[existing_start]) and
1518 not is_indent_start(context.steps[row].as_list()[moving_start])) # after move must decrease
1519 # DEBUG: Add protection IndexError when moving down on empty lines
1520 increase_indent = moving_start < existing_start and context.steps[row].as_list()[moving_start] != 'END'\ 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1521 and is_indent_start(context.steps[row + 1].as_list()[existing_start])
1522 prev_decrease_indent = previous_start <= moving_start < existing_start and\ 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1523 context.steps[row].as_list()[moving_start] != 'END'
1524 prev_increase_indent = context.steps[row].as_list()[moving_start] == 'END' and\ 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1525 previous_start > existing_start
1526 if is_indent_start(context.steps[row + 1].as_list()[existing_start]) and\ 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1527 context.steps[row].as_list()[moving_start] != 'END' and keep_indent:
1528 if not is_indent_start(context.steps[row].as_list()[moving_start]): 2wbFbebtb~
1529 increase_indent = True 2wbtb
1530 keep_indent = False 2wbtb
1531 else:
1532 increase_indent = False 2Fbeb~
1533 if not keep_indent: 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1534 if (context.steps[row + 1].as_list()[existing_start] == 'END') or ( 2A pbqbBbwbB C D ybzbebHbtbubJbKbDb~ abhbibvbjb
1535 not increase_indent and is_indent_start(context.steps[row].as_list()[moving_start])):
1536 decrease_indent = True 2A pbqbBbB C ybzbebubDb~ abhbibvbjb
1537 # print(f"DEBUG: MoveRowsDown before: {row=} {context.steps[row].as_list()}\n "
1538 # f"next {row+1} {context.steps[row + 1].as_list()} {decrease_indent=}"
1539 # f"\n{increase_indent=} {keep_indent=} {existing_start=} {moving_start=}"
1540 # f"{prev_decrease_indent=} {prev_increase_indent=}")
1541 context.move_step_down(row) 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1542 new_existing_start = context.steps[row]._first_non_empty_cell() 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1543 new_moved_start = context.steps[row + 1]._first_non_empty_cell() 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1544 previous_start = context.steps[row - 1]._first_non_empty_cell() if row >= 1 else moving_start 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1545 if row > 0 and prev_decrease_indent and new_existing_start == existing_start and ( 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1546 is_indent_inner(context.steps[row + 1].as_list()[new_moved_start])
1547 or context.steps[row].as_list()[new_existing_start] == 'END'
1548 or previous_start == new_existing_start):
1549 prev_decrease_indent = False 2eb
1550 if is_indent_start(context.steps[row + 1].as_list()[new_moved_start]) and new_moved_start > moving_start: 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1551 increase_indent = False # Compensation for auto indent 2A pbqbB ybzbebub~ ab
1552 if decrease_indent and (new_moved_start == moving_start 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1553 and is_indent_start(context.steps[row + 1].as_list()[new_moved_start]) and
1554 (prev_decrease_indent and
1555 context.steps[row].as_list()[new_existing_start] != 'END')):
1556 decrease_indent = False # Compensation for auto indent 2C ~ abhbvbjb
1557 # print(f"DEBUG: MoveRowsDown after move: {decrease_indent=} {increase_indent=} "
1558 # f"{keep_indent=} {existing_start=}"
1559 # f" {moving_start=} {new_existing_start=} {new_moved_start=}"
1560 # f"{prev_decrease_indent=} {prev_increase_indent=}")
1561 if decrease_indent: 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1562 context.steps[row + 1].shift_left(moving_start) 2A pbqbBbB ybzbebubDb~ abib
1563 if prev_decrease_indent: 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1564 context.steps[row].shift_left(new_existing_start) 2A pbqbB C D ub~ abhbibvbjb
1565 # continue
1566 if prev_increase_indent and not is_indent_start(context.steps[row].as_list()[new_existing_start]): 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1567 context.steps[row].shift_right(new_existing_start) 2GbIbhbibjb
1568 if increase_indent and not keep_indent: 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1569 context.steps[row + 1].shift_right(0) 2wbD tb~
1570 assert len(context.steps) == number_of_steps_before 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1571 return True 2A pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvbjb
1573 @property 1ab
1574 def _last_row(self): 1ab
1575 return self._rows[-1] if self._rows[-1] >= self._rows[0] else self._rows[0]
1577 def _get_undo_command(self): 1ab
1578 return MoveRowsUp([r + 1 for r in self._rows]) 2McA pbqbBbGbwbLbEbF B G H E C FbD ybzbebHbIbtbubNbJbKbDb~ abhbibvb_c`c{c|cjb
1581class CompositeCommand(_ReversibleCommand): 1ab
1583 def __init__(self, *commands): 1ab
1584 self._commands = commands 2U xb
1586 def _execute(self, context): 1ab
1587 executions = self._executions(context) 2a ObPbQbRb1b2bM SbTbUbbc'brb+bXbccNcdcecfcgc5 Vb3bPcbbcb7b8b9b!bQcYbZbdbCb) (bkcfbgb)b*bMb- U xbsb
1588 undos = [undo for _, undo in executions] 2a ObPbQbRb1b2bM SbTbUbbc'brb+bXbccNcdcecfcgc5 Vb3bPcbbcb7b8b9b!bQcYbZbdbCb) (bkcfbgb)b*bMb- U xbsb
1589 undos.reverse() 2a ObPbQbRb1b2bM SbTbUbbc'brb+bXbccNcdcecfcgc5 Vb3bPcbbcb7b8b9b!bQcYbZbdbCb) (bkcfbgb)b*bMb- U xbsb
1590 self._undo_command = self._create_undo_command(undos) 2a ObPbQbRb1b2bM SbTbUbbc'brb+bXbccNcdcecfcgc5 Vb3bPcbbcb7b8b9b!bQcYbZbdbCb) (bkcfbgb)b*bMb- U xbsb
1591 return [result for result, _ in executions] 2a ObPbQbRb1b2bM SbTbUbbc'brb+bXbccNcdcecfcgc5 Vb3bPcbbcb7b8b9b!bQcYbZbdbCb) (bkcfbgb)b*bMb- U xbsb
1593 def _get_undo_command(self): 1ab
1594 return self._undo_command 2U xb
1596 def _create_undo_command(self, undos): 1ab
1597 return CompositeCommand(*undos) 2U xb
1599 def _executions(self, context): 1ab
1600 return [(cmd._execute(context), cmd._get_undo_command()) 2U xb
1601 for cmd in self._commands]
1604class StepsChangingCompositeCommand(_StepsChangingCommand, CompositeCommand): 1ab
1606 def __init__(self, *commands): 1ab
1607 self._commands = commands 2a ObPbQbRb1b2bM SbTbUbbc'brb+bXbccNcdcecfcgc5 hcVb3bicacjcPcbbcb7b8bWb9b!bQcYbZbdbCb) (bkcfbgb)b*bMb- U 6bsb4b0b
1609 def change_steps(self, context): 1ab
1610 return any(changed 2a ObPbQbRb1b2bM SbTbUbbc'brb+bXbccNcdcecfcgc5 Vb3bPcbbcb7b8b9b!bQcYbZbdbCb) (bkcfbgb)b*bMb- U sb
1611 for changed in CompositeCommand._execute(self, context))
1613 def _get_undo_command(self): 1ab
1614 return self._undo_command 2a ObPbQbRb1b2bM SbTbUbbc'brb+bXbccNcdcecfcgc5 Vb3bPcbbcb7b8b9b!bQcYbZbdbCb) (bkcfbgb)b*bMb- U sb
1616 def _create_undo_command(self, undos): 1ab
1617 return StepsChangingCompositeCommand(*undos) 2a ObPbQbRb1b2bM SbTbUbbc'brb+bXbccNcdcecfcgc5 Vb3bPcbbcb7b8b9b!bQcYbZbdbCb) (bkcfbgb)b*bMb- U sb
1619 def _executions(self, context): 1ab
1620 return [(cmd.change_steps(context), cmd._get_undo_command()) 2a ObPbQbRb1b2bM SbTbUbbc'brb+bXbccNcdcecfcgc5 Vb3bPcbbcb7b8b9b!bQcYbZbdbCb) (bkcfbgb)b*bMb- U sb
1621 for cmd in self._commands]
1624def delete_rows(rows): 1ab
1625 return StepsChangingCompositeCommand(*[DeleteRow(r) 2a 1b2bM rb3bPcdbCb) sb
1626 for r in reversed(sorted(rows))])
1629def add_rows(rows): 1ab
1630 # DEBUG: Refactor to use AddRows(_StepsChangingCommand) command
1631 first_row = sorted(rows)[0] 2+bbbcbQcMb
1632 return StepsChangingCompositeCommand(*[AddRow(first_row) for _ in rows]) 2+bbbcbQcMb
1635def comment_rows(rows): 1ab
1636 return StepsChangingCompositeCommand(*[CommentRow(r) for r in rows]) 2ccNcdcecfcgc(bfbgb)b*b
1639def uncomment_rows(rows): 1ab
1640 return StepsChangingCompositeCommand(*[UncommentRow(r) for r in rows]) 2Nc(bkcfbgb)b*b
1643def sharp_comment_rows(rows): 1ab
1644 return StepsChangingCompositeCommand(*[SharpCommentRow(r) for r in rows]) 2bc'b
1647def sharp_uncomment_rows(rows): 1ab
1648 return StepsChangingCompositeCommand(*[SharpUncommentRow(r) for r in rows]) 2'b
1651def clear_area(top_left, bottom_right): 1ab
1652 row_s, col_s = top_left 2Xb
1653 row_e, col_e = bottom_right 2Xb
1654 return StepsChangingCompositeCommand( 2Xb
1655 *[ChangeCellValue(row, col, '')
1656 for row in range(row_s, row_e + 1)
1657 for col in range(col_s, col_e + 1)])
1660def paste_area(top_left, content): 1ab
1661 row_s, col_s = top_left 2a ObPbQbRb1b2bM hc3bicjcbbcbWbYbZbdbCb) Mb-
1662 return StepsChangingCompositeCommand( 2a ObPbQbRb1b2bM hc3bicjcbbcbWbYbZbdbCb) Mb-
1663 *[ChangeCellValue(row + row_s, col + col_s, content[row][col])
1664 for row in range(len(content))
1665 for col in range(len(content[row]))])
1668def insert_area(top_left, content): 1ab
1669 row, _ = top_left 2bbcb
1670 return StepsChangingCompositeCommand( 2bbcb
1671 add_rows([row + i for i in range(len(content))]),
1672 paste_area(top_left, content))
1675def _rows_from_selection(selection): 1ab
1676 res = []
1677 for row, col in selection:
1678 if row not in res:
1679 res += [row]
1680 return res
1683def _cols_from_selection(selection): 1ab
1684 res = []
1685 for row, col in selection:
1686 if col not in res:
1687 res += [col]
1688 return res
1691def insert_cells(top_left, bottom_right): 1ab
1692 row_s, col_s = top_left 27b8b9b!b
1693 row_e, col_e = bottom_right 27b8b9b!b
1694 return StepsChangingCompositeCommand( 27b8b9b!b
1695 *[InsertCell(row, col)
1696 for row in range(row_s, row_e + 1)
1697 for col in range(col_s, col_e + 1)])
1700def delete_cells(top_left, bottom_right): 1ab
1701 row_s, col_s = top_left 2SbTbUb5 VbU
1702 row_e, col_e = bottom_right 2SbTbUb5 VbU
1703 return StepsChangingCompositeCommand( 2SbTbUb5 VbU
1704 *[DeleteCell(row, col_s)
1705 for row in range(row_s, row_e + 1)
1706 for _ in range(col_s, col_e + 1)])