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

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. 

15 

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

27 

28 

29BDD_ENGLISH = 'Given|When|Then|And|But' 1ab

30 

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 

37 

38 

39class Occurrence(object): 1ab

40 

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

46 

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()) 

52 

53 @property 1ab

54 def item(self): 1ab

55 return self._item 1PQRST8|4

56 

57 @property 1ab

58 def source(self): 1ab

59 return self.datafile.source 184

60 

61 @property 1ab

62 def datafile(self): 1ab

63 return self._item.datafile 184

64 

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 

70 

71 @property 1ab

72 def location(self): 1ab

73 return self._item.parent.name 1XNY12{(OIKLJ3kdeflrstuvmngochpijq

74 

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

84 

85 def _in_settings(self): 1ab

86 return isinstance(self._item, settingcontrollers._SettingController) 1XNY12{(OIKLJ3kdeflrstuvmngochpijqPQRST

87 

88 def _in_variable_table(self): 1ab

89 from . import tablecontrollers 1XNY12{(OIKLJ3kdeflrstuvmngochpijqPQRST

90 return isinstance(self._item, tablecontrollers.VariableTableController) 1XNY12{(OIKLJ3kdeflrstuvmngochpijqPQRST

91 

92 def _in_kw_name(self): 1ab

93 from .macrocontrollers import KeywordNameController 1XNY12OIKLJ3kdeflmngochpijq

94 

95 return isinstance(self._item, KeywordNameController) 1XNY12OIKLJ3kdeflmngochpijq

96 

97 def _in_steps(self): 1ab

98 return not (self._in_settings() or self._in_kw_name()) 

99 

100 def _in_for_loop(self): 1ab

101 from .macrocontrollers import ForLoopStepController 

102 

103 return isinstance(self._item.parent, ForLoopStepController) 

104 

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

110 

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

115 

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

118 

119 

120class _Command(object): 1ab

121 modifying = True 1ab

122 

123 def execute(self, context): 1ab

124 raise NotImplementedError(self.__class__) 

125 

126 def __str__(self): 1ab

127 return '%s(%s)' % (self.__class__.__name__, self._params_str()) 

128 

129 def _params_str(self): 1ab

130 return ', '.join(self._format_param(p) for p in self._params()) 

131 

132 @staticmethod 1ab

133 def _format_param(param): 1ab

134 if isinstance(param, str): 

135 return '"%s"' % param 

136 return str(param) 

137 

138 def _params(self): 1ab

139 return [] 

140 

141 

142class CopyMacroAs(_Command): 1ab

143 

144 def __init__(self, new_name): 1ab

145 self._new_name = new_name 2M ,bDdEd

146 

147 def execute(self, context): 1ab

148 context.copy(self._new_name) 2M ,bDdEd

149 

150 def _params(self): 1ab

151 return [self._new_name] 

152 

153 

154class ChangeTag(_Command): 1ab

155 

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

159 

160 def _params(self): 1ab

161 return self._tag, self._value 

162 

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

167 

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 != '') 

174 

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

186 

187 

188class DeleteTag(_Command): 1ab

189 

190 def execute(self, tag): 1ab

191 tag.delete() 

192 tag.controller.notify_value_changed() 

193 

194 

195class _ReversibleCommand(_Command): 1ab

196 

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

201 

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

206 

207 @property 1ab

208 def _get_undo_command(self): 1ab

209 raise NotImplementedError(self.__class__.__name__) 

210 

211 

212class Undo(_Command): 1ab

213 

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

220 

221 

222class Redo(_Command): 1ab

223 

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

227 

228 

229class MoveTo(_Command): 1ab

230 

231 def __init__(self, destination): 1ab

232 self._destination = destination 2wdxdydzdAdBdCd

233 

234 def _params(self): 1ab

235 return [self._destination] 

236 

237 def execute(self, context): 1ab

238 context.delete() 2wdxdydzdAdBdCd

239 self._destination.add_test_or_keyword(context) 2wdxdydzdAdBdCd

240 

241 

242class CreateNewResource(_Command): 1ab

243 

244 def __init__(self, path): 1ab

245 self._path = path 2RcScTcUcVcWc

246 

247 def execute(self, context): 1ab

248 res = context.new_resource(self._path) 2RcScTcUcVcWc

249 RideSelectResource(item=res).publish() 2RcScTcUcVcWc

250 return res 2RcScTcUcVcWc

251 

252 

253class SetDataFile(_Command): 1ab

254 

255 def __init__(self, datafile): 1ab

256 self._datafile = datafile 

257 

258 def execute(self, context): 1ab

259 context.mark_dirty() 

260 context.set_datafile(self._datafile) 

261 

262 

263class _StepsChangingCommand(_ReversibleCommand): 1ab

264 

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

270 

271 def change_steps(self, context): 1ab

272 """Return True if steps changed, False otherwise""" 

273 raise NotImplementedError(self.__class__.__name__) 

274 

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() 

280 

281 

282class NonExistingStep(object): 1ab

283 def __getattr__(self, name): 1ab

284 return lambda *args: '' 

285 

286 

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

289 

290 

291class RenameKeywordOccurrences(_ReversibleCommand): 1ab

292 

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} ") 

308 

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

325 

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

330 

331 def _params(self): 1ab

332 return (self._original_name, self._new_name, 

333 self._observer, self._keyword_info) 

334 

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

344 

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

353 

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

358 

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

369 

370 def _get_undo_command(self): 1ab

371 self._observer = NullObserver() 1kdeflrstuvmngochpijqwxyz

372 return self 1kdeflrstuvmngochpijqwxyz

373 

374 

375class RenameTest(_ReversibleCommand): 1ab

376 

377 def __init__(self, new_name): 1ab

378 self._new_name = new_name 2gd

379 

380 def _params(self): 1ab

381 return self._new_name 

382 

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

387 

388 def _get_undo_command(self): 1ab

389 return self 2gd

390 

391 

392class RenameFile(_Command): 1ab

393 

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

397 

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

406 

407 

408class Include(_Command): 1ab

409 

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() 

414 

415 

416class Exclude(_Command): 1ab

417 

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() 

422 

423 

424class RenameResourceFile(_Command): 1ab

425 

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

429 

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

445 

446 

447class SortTests(_ReversibleCommand): 1ab

448 index_difference = None 1ab

449 

450 def _execute(self, context): 1ab

451 index_difference = context.sort_tests() 27c

452 self._undo_command = RestoreTestOrder(index_difference) 27c

453 

454 def _get_undo_command(self): 1ab

455 return self._undo_command 27c

456 

457 

458class SortKeywords(_ReversibleCommand): 1ab

459 index_difference = None 1ab

460 

461 def _execute(self, context): 1ab

462 index_difference = context.sort_keywords() 28c

463 self._undo_command = RestoreKeywordOrder(index_difference) 28c

464 

465 def _get_undo_command(self): 1ab

466 return self._undo_command 28c

467 

468 

469class SortVariables(_ReversibleCommand): 1ab

470 index_difference = None 1ab

471 

472 def _execute(self, context): 1ab

473 index_difference = context.sort_variables() 29c

474 self._undo_command = RestoreVariableOrder(index_difference) 29c

475 

476 def _get_undo_command(self): 1ab

477 return self._undo_command 29c

478 

479 

480class RestoreTestOrder(_ReversibleCommand): 1ab

481 

482 def __init__(self, index_difference): 1ab

483 self._index_difference = index_difference 27c

484 

485 def _execute(self, context): 1ab

486 context.restore_test_order(self._index_difference) 27c

487 

488 def _get_undo_command(self): 1ab

489 return SortTests() 27c

490 

491 

492class RestoreKeywordOrder(_ReversibleCommand): 1ab

493 

494 def __init__(self, index_difference): 1ab

495 self._index_difference = index_difference 28c

496 

497 def _execute(self, context): 1ab

498 context.restore_keyword_order(self._index_difference) 28c

499 

500 def _get_undo_command(self): 1ab

501 return SortKeywords() 28c

502 

503 

504class RestoreVariableOrder(_ReversibleCommand): 1ab

505 

506 def __init__(self, index_difference): 1ab

507 self._index_difference = index_difference 29c

508 

509 def _execute(self, context): 1ab

510 context.restore_variable_order(self._index_difference) 29c

511 

512 def _get_undo_command(self): 1ab

513 return SortVariables() 29c

514 

515 

516class _ItemCommand(_Command): 1ab

517 

518 def __init__(self, item): 1ab

519 self._item = item 

520 

521 

522class UpdateDocumentation(_ItemCommand): 1ab

523 

524 def execute(self, context): 1ab

525 context.editable_value = self._item 

526 

527 

528class MoveUp(_ItemCommand): 1ab

529 

530 def execute(self, context): 1ab

531 context.move_up(self._item) 

532 

533 

534class MoveDown(_ItemCommand): 1ab

535 

536 def execute(self, context): 1ab

537 context.move_down(self._item) 

538 

539 

540class DeleteItem(_ItemCommand): 1ab

541 

542 def execute(self, context): 1ab

543 context.delete(self._item) 

544 

545 

546class ClearSetting(_Command): 1ab

547 

548 def execute(self, context): 1ab

549 context.clear() 

550 

551 

552class DeleteFile(_Command): 1ab

553 

554 def execute(self, context): 1ab

555 context.remove_from_filesystem() 2Fdvd

556 context.remove() 2Fdvd

557 

558 

559class OpenContainingFolder(_Command): 1ab

560 modifying = False 1ab

561 

562 def __init__(self, tool: str = None, path: str = None): 1ab

563 self.tool = tool 

564 self.path = path 

565 

566 def execute(self, context): 1ab

567 context.open_filemanager(path=self.path, tool=self.tool) 

568 

569 

570class RemoveReadOnly(_Command): 1ab

571 

572 def execute(self, context): 1ab

573 context.remove_readonly() 

574 

575 

576class DeleteFolder(_Command): 1ab

577 

578 def execute(self, context): 1ab

579 context.remove_folder_from_filesystem() 

580 context.remove_from_model() 

581 

582 

583class SetValues(_Command): 1ab

584 

585 def __init__(self, values, comment): 1ab

586 self._values = values 

587 self._comment = comment 

588 

589 def execute(self, context): 1ab

590 context.set_value(*self._values) 

591 context.set_comment(self._comment) 

592 

593 

594class AddLibrary(_Command): 1ab

595 

596 def __init__(self, values, comment): 1ab

597 self._values = values 

598 self._comment = comment 

599 

600 def execute(self, context): 1ab

601 lib = context.add_library(*self._values) 

602 lib.set_comment(self._comment) 

603 return lib 

604 

605 

606class AddResource(_Command): 1ab

607 

608 def __init__(self, values, comment): 1ab

609 self._values = values 

610 self._comment = comment 

611 

612 def execute(self, context): 1ab

613 res = context.add_resource(*self._values) 

614 res.set_comment(self._comment) 

615 return res 

616 

617 

618class AddVariablesFileImport(_Command): 1ab

619 

620 def __init__(self, values, comment): 1ab

621 self._values = values 

622 self._comment = comment 

623 

624 def execute(self, context): 1ab

625 var = context.add_variables(*self._values) 

626 var.set_comment(self._comment) 

627 return var 

628 

629 

630class DeleteResourceAndImports(DeleteFile): 1ab

631 

632 def execute(self, context): 1ab

633 context.remove_static_imports_to_this() 2vd

634 DeleteFile.execute(self, context) 2vd

635 

636 

637class DeleteFolderAndImports(DeleteFolder): 1ab

638 

639 def execute(self, context): 1ab

640 context.remove_static_imports_to_this() 

641 DeleteFolder.execute(self, context) 

642 

643 

644class UpdateVariable(_Command): 1ab

645 

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 

650 

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() 

659 

660 

661class UpdateVariableName(_Command): 1ab

662 

663 def __init__(self, new_name): 1ab

664 self._new_name = new_name 

665 

666 def execute(self, context): 1ab

667 context.execute(UpdateVariable(self._new_name, context.value, 

668 context.comment)) 

669 

670 

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

677 

678 

679class FindOccurrences(_Command): 1ab

680 modifying = False 1ab

681 

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

705 

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

718 

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

733 

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

741 

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

747 

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

759 

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 []) 

763 

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

767 

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

773 

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

806 

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

821 

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 

826 

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

832 

833 

834class FindVariableOccurrences(FindOccurrences): 1ab

835 

836 def _contains_item(self, item): 1ab

837 self._yield_for_other_threads() 1IKLJ3

838 return item.contains_variable(self._keyword_name) 1IKLJ3

839 

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

844 

845 def _items_from_controller(self, ctrl): 1ab

846 from .macrocontrollers import TestCaseController 13

847 

848 if isinstance(ctrl, TestCaseController): 13

849 return self._items_from_test(ctrl) 13

850 else: 

851 return self._items_from_keyword(ctrl) 13

852 

853 def _items_from_keyword(self, kw): 1ab

854 return chain([kw.keyword_name], kw.steps, kw.settings) 1IKLJ3

855 

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

867 

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

879 

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) 

887 

888 @staticmethod 1ab

889 def _is_file_variable(name, context): 1ab

890 return context.datafile_controller.variables.contains_variable(name) 1IKLJ

891 

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] 

895 

896 @staticmethod 1ab

897 def _is_builtin_variable(name): 1ab

898 return name in list(namespace._VariableStash.global_variables.keys()) 

899 

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

905 

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

914 

915 @staticmethod 1ab

916 def _get_all_where_used(context): 1ab

917 from .filecontrollers import ResourceFileController 1L

918 

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

925 

926 

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

936 

937 

938class AddKeyword(_ReversibleCommand): 1ab

939 

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

943 

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

948 

949 def _get_undo_command(self): 1ab

950 return self._undo_command 2Ab}c~c!c1cadbdcddded'cOcRcScTcUcVcWc

951 

952 

953class AddTestCase(_Command): 1ab

954 

955 def __init__(self, new_test_name): 1ab

956 self._test_name = new_test_name 2Gdfd

957 

958 def execute(self, context): 1ab

959 return context.create_test(self._test_name) 2Gdfd

960 

961 

962class _AddDataFile(_Command): 1ab

963 

964 def __init__(self, path): 1ab

965 self._path = path 2tdud

966 

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

971 

972 def _add_data_file(self, context): 1ab

973 raise NotImplementedError(self.__class__.__name__) 

974 

975 

976class AddTestCaseFile(_AddDataFile): 1ab

977 

978 def _add_data_file(self, context): 1ab

979 return context.new_test_case_file(self._path) 2td

980 

981 

982class AddTestDataDirectory(_AddDataFile): 1ab

983 

984 def _add_data_file(self, context): 1ab

985 return context.new_test_data_directory(self._path) 2ud

986 

987 

988class CreateNewFileProject(_Command): 1ab

989 

990 def __init__(self, path, tasks, lang): 1ab

991 self._path = path 

992 self._tasks = tasks 

993 self._lang = lang 

994 

995 def execute(self, context): 1ab

996 context.new_file_project(self._path, self._tasks, self._lang) 

997 

998 

999class CreateNewDirectoryProject(_Command): 1ab

1000 

1001 def __init__(self, path, tasks, lang): 1ab

1002 self._path = path 

1003 self._tasks = tasks 

1004 self._lang = lang 

1005 

1006 def execute(self, context): 1ab

1007 context.new_directory_project(self._path, self._tasks, self._lang) 

1008 

1009 

1010class SetFileFormat(_Command): 1ab

1011 

1012 def __init__(self, format): 1ab

1013 self._format = format 

1014 

1015 def execute(self, context): 1ab

1016 context.save_with_new_format(self._format) 

1017 

1018 

1019class SetFileFormatRecuresively(_Command): 1ab

1020 

1021 def __init__(self, format): 1ab

1022 self._format = format 

1023 

1024 def execute(self, context): 1ab

1025 context.save_with_new_format_recursive(self._format) 

1026 

1027 

1028class RemoveVariable(_ReversibleCommand): 1ab

1029 

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) 

1035 

1036 def _execute(self, context): 1ab

1037 context.datafile_controller. \ 

1038 variables.remove_var(self._var_controller) 

1039 

1040 def _get_undo_command(self): 1ab

1041 return self._undo_command 

1042 

1043 

1044class AddVariable(_ReversibleCommand): 1ab

1045 

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

1050 

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

1056 

1057 def _get_undo_command(self): 1ab

1058 return self._undo_command 2U xb

1059 

1060 def __str__(self): 1ab

1061 return 'AddVariable("%s", "%s", "%s")' % \ 

1062 (self._name, self._value, self._comment) 

1063 

1064 

1065class RecreateMacro(_ReversibleCommand): 1ab

1066 

1067 def __init__(self, user_script): 1ab

1068 self._user_script = user_script 2Ab1c'cOcfd

1069 

1070 def _execute(self, context): 1ab

1071 _ = context 2Oc

1072 self._user_script.recreate() 2Oc

1073 

1074 def _get_undo_command(self): 1ab

1075 return RemoveMacro(self._user_script) 2Oc

1076 

1077 

1078class RemoveMacro(_ReversibleCommand): 1ab

1079 

1080 def __init__(self, item): 1ab

1081 self._item = item 2Ab}c~c!c1cadbdcddded'cOcfdRcScTcUcVcWc

1082 

1083 def _execute(self, context): 1ab

1084 _ = context 2Ab1c'cOcfd

1085 self._item.delete() 2Ab1c'cOcfd

1086 

1087 def _get_undo_command(self): 1ab

1088 return RecreateMacro(self._item) 2Ab1c'cOcfd

1089 

1090 

1091class ExtractKeyword(_Command): 1ab

1092 

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

1097 

1098 def _params(self): 1ab

1099 return self._name, self._args, self._rows 

1100 

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

1105 

1106 

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)) 

1111 

1112 

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))) 

1119 

1120 

1121class ChangeCellValue(_StepsChangingCommand): 1ab

1122 

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

1129 

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

1147 

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

1153 

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') 

1166 

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

1169 

1170 def __str__(self): 1ab

1171 return '%s(%s, %s, "%s")' % \ 

1172 (self.__class__.__name__, self._row, self._col, self._value) 

1173 

1174 

1175class SaveFile(_Command): 1ab

1176 

1177 def __init__(self, reformat=False): 1ab

1178 self._reformat = reformat 2a mdM jd:c;c=c?c@c#c$c%cndRcScTcUcVcWc

1179 

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

1189 

1190 

1191class SaveAll(_Command): 1ab

1192 

1193 def __init__(self, reformat=False): 1ab

1194 self._reformat = reformat 

1195 

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() 

1201 

1202 

1203class Purify(_Command): 1ab

1204 

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

1222 

1223 

1224class InsertCell(_StepsChangingCommand): 1ab

1225 

1226 def __init__(self, row, col): 1ab

1227 self._row = row 2SbTbUb5 Vbac7b8bWb9bXc!bU 6b4bYcZc0c0b

1228 self._col = col 2SbTbUb5 Vbac7b8bWb9bXc!bU 6b4bYcZc0c0b

1229 

1230 def _params_str(self): 1ab

1231 return '%s, %s' % (self._row, self._col) 

1232 

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

1239 

1240 def _get_undo_command(self): 1ab

1241 return DeleteCell(self._row, self._col) 25 7b8bWb9bXc!b6bYcZc0c

1242 

1243 

1244class DeleteCell(_StepsChangingCommand): 1ab

1245 

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

1250 

1251 def _params(self): 1ab

1252 return self._row, self._col 

1253 

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

1263 

1264 def _get_undo_command(self): 1ab

1265 return self._undo_command 2SbTbUb5 VbacU 6b4b0b

1266 

1267 

1268class _RowChangingCommand(_StepsChangingCommand): 1ab

1269 

1270 def __init__(self, row): 1ab

1271 """Command that will operate on a given logical `row` of test/user keyword. 

1272 

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-

1276 

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-

1282 

1283 def __str__(self): 1ab

1284 return '%s(%s)' % (self.__class__.__name__, self._row) 

1285 

1286 

1287class DeleteRow(_RowChangingCommand): 1ab

1288 

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-

1295 

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

1300 

1301 

1302class AddRow(_RowChangingCommand): 1ab

1303 

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() 

1311 

1312 def _get_undo_command(self): 1ab

1313 return DeleteRow(self._row) 2+b3c4c5c6c5bbbcbQcdbCbMb-

1314 

1315 

1316class CommentRow(_RowChangingCommand): 1ab

1317 

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

1322 

1323 def _get_undo_command(self): 1ab

1324 return UncommentRow(self._row) 2ccNcdcecfcgc(bfbgb)b*b

1325 

1326 

1327class UncommentRow(_RowChangingCommand): 1ab

1328 

1329 def _change_value(self, context): 1ab

1330 self._step(context).uncomment() 2(bkcfbgb)b*b

1331 return True 2(bkcfbgb)b*b

1332 

1333 def _get_undo_command(self): 1ab

1334 return CommentRow(self._row) 2Nc(bkcfbgb)b*b

1335 

1336 

1337class SharpCommentRow(_RowChangingCommand): 1ab

1338 

1339 def _change_value(self, context): 1ab

1340 self._step(context).sharp_comment() 2bc'b

1341 return True 2bc'b

1342 

1343 def _get_undo_command(self): 1ab

1344 return SharpUncommentRow(self._row) 2bc'b

1345 

1346 

1347class SharpUncommentRow(_RowChangingCommand): 1ab

1348 

1349 def _change_value(self, context): 1ab

1350 self._step(context).sharp_uncomment() 2'b

1351 return True 2'b

1352 

1353 def _get_undo_command(self): 1ab

1354 return SharpCommentRow(self._row) 2'b

1355 

1356 

1357INDENTED_INNER = ['ELSE', 'ELSE IF', 'EXCEPT', 'FINALLY'] 1ab

1358INDENTED_START = ['FOR', 'GROUP', 'IF', 'WHILE', 'TRY'] + INDENTED_INNER 1ab

1359 

1360 

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

1363 

1364 

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

1367 

1368 

1369class MoveRowsUp(_StepsChangingCommand): 1ab

1370 

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

1373 

1374 def _params(self): 1ab

1375 return [self._rows] 

1376 

1377 def change_steps(self, context): # NOTE: Nevermind the quality of this code. Unit tests are passing ;) 1ab

1378 

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

1388 

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

1398 

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

1475 

1476 @property 1ab

1477 def _last_row(self): 1ab

1478 return self._rows[-1] 

1479 

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

1483 

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

1486 

1487 

1488class MoveRowsDown(_StepsChangingCommand): 1ab

1489 """ Moves a single row down, argument single element list, or lines in a range for a two elements argument """ 

1490 

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

1494 

1495 def _params(self): 1ab

1496 return [self._rows] 

1497 

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

1572 

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] 

1576 

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

1579 

1580 

1581class CompositeCommand(_ReversibleCommand): 1ab

1582 

1583 def __init__(self, *commands): 1ab

1584 self._commands = commands 2U xb

1585 

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

1592 

1593 def _get_undo_command(self): 1ab

1594 return self._undo_command 2U xb

1595 

1596 def _create_undo_command(self, undos): 1ab

1597 return CompositeCommand(*undos) 2U xb

1598 

1599 def _executions(self, context): 1ab

1600 return [(cmd._execute(context), cmd._get_undo_command()) 2U xb

1601 for cmd in self._commands] 

1602 

1603 

1604class StepsChangingCompositeCommand(_StepsChangingCommand, CompositeCommand): 1ab

1605 

1606 def __init__(self, *commands): 1ab

1607 self._commands = commands 2a ObPbQbRb1b2bM SbTbUbbc'brb+bXbccNcdcecfcgc5 hcVb3bicacjcPcbbcb7b8bWb9b!bQcYbZbdbCb) (bkcfbgb)b*bMb- U 6bsb4b0b

1608 

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)) 

1612 

1613 def _get_undo_command(self): 1ab

1614 return self._undo_command 2a ObPbQbRb1b2bM SbTbUbbc'brb+bXbccNcdcecfcgc5 Vb3bPcbbcb7b8b9b!bQcYbZbdbCb) (bkcfbgb)b*bMb- U sb

1615 

1616 def _create_undo_command(self, undos): 1ab

1617 return StepsChangingCompositeCommand(*undos) 2a ObPbQbRb1b2bM SbTbUbbc'brb+bXbccNcdcecfcgc5 Vb3bPcbbcb7b8b9b!bQcYbZbdbCb) (bkcfbgb)b*bMb- U sb

1618 

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] 

1622 

1623 

1624def delete_rows(rows): 1ab

1625 return StepsChangingCompositeCommand(*[DeleteRow(r) 2a 1b2bM rb3bPcdbCb) sb

1626 for r in reversed(sorted(rows))]) 

1627 

1628 

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

1633 

1634 

1635def comment_rows(rows): 1ab

1636 return StepsChangingCompositeCommand(*[CommentRow(r) for r in rows]) 2ccNcdcecfcgc(bfbgb)b*b

1637 

1638 

1639def uncomment_rows(rows): 1ab

1640 return StepsChangingCompositeCommand(*[UncommentRow(r) for r in rows]) 2Nc(bkcfbgb)b*b

1641 

1642 

1643def sharp_comment_rows(rows): 1ab

1644 return StepsChangingCompositeCommand(*[SharpCommentRow(r) for r in rows]) 2bc'b

1645 

1646 

1647def sharp_uncomment_rows(rows): 1ab

1648 return StepsChangingCompositeCommand(*[SharpUncommentRow(r) for r in rows]) 2'b

1649 

1650 

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)]) 

1658 

1659 

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]))]) 

1666 

1667 

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)) 

1673 

1674 

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 

1681 

1682 

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 

1689 

1690 

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)]) 

1698 

1699 

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)])