Coverage for src/robotide/editor/texteditor.py: 59%

2350 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. 

15import builtins 

16import os 

17import re 

18import tempfile 

19from io import StringIO, BytesIO 

20from os.path import dirname 

21from time import time 

22 

23import wx 

24from wx import stc, Colour 

25from wx.adv import HyperlinkCtrl, EVT_HYPERLINK 

26from multiprocessing import shared_memory 

27from .popupwindow import HtmlPopupWindow 

28from .pythoneditor import PythonSTC 

29from . import _EDIT_nt, get_menudata 

30from .. import robotapi 

31from ..context import IS_WINDOWS, IS_MAC 

32from ..controller.ctrlcommands import SetDataFile, INDENTED_START 

33from ..controller.dataloader import TestDataDirectoryWithExcludes 

34from ..controller.filecontrollers import ResourceFileController 

35from ..controller.macrocontrollers import WithStepsController 

36from ..namespace.suggesters import SuggestionSource 

37from ..pluginapi import Plugin, action_info_collection, TreeAwarePluginMixin 

38from ..publish.messages import (RideSaved, RideTreeSelection, RideNotebookTabChanging, RideDataChanged, RideOpenSuite, 

39 RideDataChangedToDirty, RideBeforeSaving, RideSaving, RideDataDirtyCleared) 

40from ..preferences import TextEditorPreferences, PreferenceEditor 

41from ..preferences.editors import read_fonts 

42from ..publish import RideSettingsChanged, PUBLISHER 

43from ..publish.messages import RideMessage 

44from ..widgets import SearchField, Label, HtmlDialog 

45from ..widgets import VerticalSizer, HorizontalSizer, ButtonWithHandler, RIDEDialog 

46 

47from robotide.lib.compat.parsing.language import Language 

48robotframeworklexer = None 

49if Language: 49 ↛ 55line 49 didn't jump to line 55 because the condition on line 49 was always true

50 try: # import our modified version 

51 from robotide.lib.compat.pygments import robotframework as robotframeworklexer 

52 except ImportError: 

53 robotframeworklexer = None 

54 

55if not robotframeworklexer: 55 ↛ 56line 55 didn't jump to line 56 because the condition on line 55 was never true

56 try: # import original version 

57 from pygments.lexers import robotframework as robotframeworklexer 

58 except ImportError: 

59 robotframeworklexer = None 

60 

61_ = wx.GetTranslation # To keep linter/code analyser happy 

62builtins.__dict__['_'] = wx.GetTranslation 

63 

64AUTO_SUGGESTIONS = 'enable auto suggestions' 

65LANG_SETTING = 'Language: ' 

66PATH_EXCLUSIONS = dirname(__file__) 

67PLUGIN_NAME = 'Text Edit' 

68TOKEN_TXT = 'Token(' 

69TXT_NUM_SPACES = 'txt number of spaces' 

70ZOOM_FACTOR = 'zoom factor' 

71RSPC = r"\s{2}" 

72 

73 

74def read_language(content): 

75 from tempfile import mkstemp 1rcbjefdgh

76 from ..lib.compat.parsing.language import read as lread 1rcbjefdgh

77 

78 fp, fname = mkstemp() 1rcbjefdgh

79 

80 with open(fp, mode='wb') as fp: 1rcbjefdgh

81 fp.write(content) 1rcbjefdgh

82 fp.close() 1rcbjefdgh

83 with open(fname, mode='rb') as readfp: 1rcbjefdgh

84 lang = lread(readfp) 1rcbjefdgh

85 os.remove(fname) 1rcbjefdgh

86 return lang 1rcbjefdgh

87 

88 

89def obtain_language(existing, content): 

90 try: 1rcbjefdgh

91 set_lang = shared_memory.ShareableList(name="language") 1rcbjefdgh

92 except Exception as e: # AttributeError: # Unittests fails here 

93 print(f"DEBUG: texteditor.py obtain_language EXCEPTION: {e}") 

94 set_lang = [] 

95 doc_lang = read_language(content) 1rcbjefdgh

96 adoc_lang = [] 1rcbjefdgh

97 if doc_lang is not None: 97 ↛ 101line 97 didn't jump to line 101 because the condition on line 97 was always true1rcbjefdgh

98 if isinstance(doc_lang, str): 98 ↛ 100line 98 didn't jump to line 100 because the condition on line 98 was always true1rcbjefdgh

99 adoc_lang.append(doc_lang) 1rcbjefdgh

100 set_lang = _get_lang(set_lang, adoc_lang) 1rcbjefdgh

101 elif len(set_lang) > 0: 

102 if existing is not None: 

103 if isinstance(existing, list): 

104 lang = existing[0] 

105 else: 

106 lang = existing 

107 try: 

108 mlang = Language.from_name(lang.replace('_', '-')) 

109 set_lang[0] = get_rf_lang_code(mlang.code) # .replace('-', '_') 

110 except ValueError: 

111 set_lang[0] = 'en' 

112 else: 

113 set_lang[0] = 'en' 

114 return [set_lang[0]] 1rcbjefdgh

115 

116 

117def _get_lang(set_lang:list, adoc_lang: list) -> list: 

118 for idx, lang in enumerate(adoc_lang): 1rcbjefdgh

119 try: 1rcbjefdgh

120 mlang = Language.from_name(lang.replace('_', '-').strip()) 1rcbjefdgh

121 except ValueError as e: 1rcb

122 print(f"DEBUG: TextEditor, could not find Language:{lang}") 1rcb

123 raise e 1rcb

124 set_lang[idx] = get_rf_lang_code(mlang.code) # .code.replace('-','_') 1rcbjefdgh

125 return set_lang 1rcbjefdgh

126 

127 

128def get_rf_lang_code(lang: (str, list), iso: bool=False) -> str: 

129 if isinstance(lang, list): 1Frcbjefdgh

130 clean_lang = lang 1F

131 else: 

132 clean_lang = lang.split(' ') # The cases we have two words 1Frcbjefdgh

133 clean_lang = clean_lang[0].replace('-', '_').split('_') # The cases we have variant code 1Frcbjefdgh

134 lc = len(clean_lang) 1Frcbjefdgh

135 code = clean_lang[0].lower() 1Frcbjefdgh

136 if not iso: 1Frcbjefdgh

137 if lc == 1: 1Frcbjefdgh

138 return code.title() 1Frcbjefdgh

139 elif lc == 2: 139 ↛ 143line 139 didn't jump to line 143 because the condition on line 139 was always true1Frcd

140 with_variant_code = f"{code.title()}{clean_lang[1].lower().title()}" 1Frcd

141 if with_variant_code in ("PtBr", "ZhCn", "ZhTw") and not iso: 141 ↛ 143line 141 didn't jump to line 143 because the condition on line 141 was always true1Frcd

142 return with_variant_code 1Frcd

143 if iso: 143 ↛ 145line 143 didn't jump to line 145 because the condition on line 143 was always true1befdgh

144 return _four_letters_code(clean_lang) 1befdgh

145 return code.title() 

146 

147 

148def _four_letters_code(clean_lang: list) -> str: 

149 variant = {"bs": "BA", "cs": "CZ", "da": "DK", "en": "US", "hi": "IN", "ja": "JP", 1befdgh

150 "ko": "KR", "sv": "SE", "uk": "UA", "vi": "VN"} 

151 lc = len(clean_lang) 1befdgh

152 code = clean_lang[0].lower() 1befdgh

153 if lc == 1: 1befdgh

154 if code in variant.keys(): 1befdgh

155 return f"{code}_{variant[code].upper()}" 1d

156 else: 

157 return f"{code}_{code.upper()}" 1befdgh

158 else: 

159 return f"{code}_{clean_lang[1].upper()}" 1d

160 

161 

162def _get_lang_classes(old_lang: str, new_lang: str) -> (Language, Language): 

163 try: 1bjefdgh

164 old_lang_class = Language.from_name(old_lang) 1bjefdgh

165 except ValueError as ex: 1bj

166 print(ex) 1bj

167 old_lang_class = Language.from_name('English') 1bj

168 try: 1bjefdgh

169 new_lang_class = Language.from_name(new_lang) 1bjefdgh

170 except ValueError as ex: 1j

171 print(ex) 1j

172 new_lang_class = Language.from_name('English') 1j

173 return old_lang_class, new_lang_class 1bjefdgh

174 

175 

176def _check_lang_error(node_info: tuple, m_text) -> (bool, str): 

177 signal_correct_language = False 1befdgh

178 if node_info != ('', ) and node_info[0] == 'ERROR': 1befdgh

179 c_msg = node_info[1].replace(TOKEN_TXT, '').replace(')', '').split(',') 1b

180 line = c_msg[1].replace('\'', '').strip() 1b

181 # print(f"DEBUG: textedit.py transform_doc_language ERROR:{line}") 

182 if line.startswith(LANG_SETTING): 182 ↛ 187line 182 didn't jump to line 187 because the condition on line 182 was always true1b

183 tail = line.replace(LANG_SETTING, '') 1b

184 # print(f"DEBUG: textedit.py transform_doc_language INSIDE BLOCK {tail=}") 

185 m_text = m_text.replace(LANG_SETTING + tail, LANG_SETTING + 'English' + ' # ' + tail) 1b

186 signal_correct_language = True 1b

187 return signal_correct_language, m_text 1befdgh

188 

189 

190def _final_lang_transformation(signal_correct_language: bool, old_lang_name: str, new_lang_name: str, m_text: str) -> str: 

191 if signal_correct_language: 1befdgh

192 m_text = m_text.replace(fr'{LANG_SETTING}English', fr'{LANG_SETTING}{new_lang_name}') 1b

193 else: 

194 m_text = m_text.replace(fr'{LANG_SETTING}{old_lang_name}', fr'{LANG_SETTING}{new_lang_name}') 1efdgh

195 try: 1befdgh

196 set_lang = shared_memory.ShareableList(name="language") 1befdgh

197 except AttributeError: # Unittests fails here 

198 set_lang = [] 

199 try: 1befdgh

200 mlang = Language.from_name(new_lang_name.replace('_', '-')) 1befdgh

201 set_lang[0] = get_rf_lang_code(mlang.code) 1befdgh

202 except ValueError: 

203 set_lang[0] = 'en' 

204 return m_text 1befdgh

205 

206def transform_doc_language(old_lang, new_lang, m_text, node_info: tuple = ('', )): 

207 if isinstance(old_lang, list): 1bjefdgh

208 old_lang = old_lang[0] 1jefdgh

209 if isinstance(new_lang, list): 1bjefdgh

210 new_lang = new_lang[0] 1jefdgh

211 old_lang = old_lang.title() 1bjefdgh

212 new_lang = new_lang.title() 1bjefdgh

213 if old_lang == new_lang: 1bjefdgh

214 return m_text 1jd

215 old_lang_class, new_lang_class = _get_lang_classes(old_lang, new_lang) 1bjefdgh

216 old_lang_name = old_lang_class.name 1bjefdgh

217 new_lang_name = new_lang_class.name 1bjefdgh

218 if old_lang_name == new_lang_name: 1bjefdgh

219 return m_text 1j

220 old_lang_headers = old_lang_class.headers 1befdgh

221 new_lang_headers = new_lang_class.headers 1befdgh

222 old_library_setting = old_lang_class.library_setting 1befdgh

223 old_resource_setting = old_lang_class.resource_setting 1befdgh

224 old_variables_setting = old_lang_class.variables_setting 1befdgh

225 old_name_setting = old_lang_class.name_setting 1befdgh

226 old_documentation_setting = old_lang_class.documentation_setting 1befdgh

227 old_metadata_setting = old_lang_class.metadata_setting 1befdgh

228 old_suite_setup_setting = old_lang_class.suite_setup_setting 1befdgh

229 old_suite_teardown_setting = old_lang_class.suite_teardown_setting 1befdgh

230 old_test_setup_setting = old_lang_class.test_setup_setting 1befdgh

231 old_task_setup_setting = old_lang_class.task_setup_setting 1befdgh

232 old_test_teardown_setting = old_lang_class.test_teardown_setting 1befdgh

233 old_task_teardown_setting = old_lang_class.task_teardown_setting 1befdgh

234 old_test_template_setting = old_lang_class.test_template_setting 1befdgh

235 old_task_template_setting = old_lang_class.task_template_setting 1befdgh

236 old_test_timeout_setting = old_lang_class.test_timeout_setting 1befdgh

237 old_task_timeout_setting = old_lang_class.task_timeout_setting 1befdgh

238 old_test_tags_setting = old_lang_class.test_tags_setting 1befdgh

239 old_task_tags_setting = old_lang_class.task_tags_setting 1befdgh

240 old_keyword_tags_setting = old_lang_class.keyword_tags_setting 1befdgh

241 old_tags_setting = old_lang_class.tags_setting 1befdgh

242 old_setup_setting = old_lang_class.setup_setting 1befdgh

243 old_teardown_setting = old_lang_class.teardown_setting 1befdgh

244 old_template_setting = old_lang_class.template_setting 1befdgh

245 old_timeout_setting = old_lang_class.timeout_setting 1befdgh

246 old_arguments_setting = old_lang_class.arguments_setting 1befdgh

247 new_library_setting = new_lang_class.library_setting 1befdgh

248 new_resource_setting = new_lang_class.resource_setting 1befdgh

249 new_variables_setting = new_lang_class.variables_setting 1befdgh

250 new_name_setting = new_lang_class.name_setting 1befdgh

251 new_documentation_setting = new_lang_class.documentation_setting 1befdgh

252 new_metadata_setting = new_lang_class.metadata_setting 1befdgh

253 new_suite_setup_setting = new_lang_class.suite_setup_setting 1befdgh

254 new_suite_teardown_setting = new_lang_class.suite_teardown_setting 1befdgh

255 new_test_setup_setting = new_lang_class.test_setup_setting 1befdgh

256 new_task_setup_setting = new_lang_class.task_setup_setting 1befdgh

257 new_test_teardown_setting = new_lang_class.test_teardown_setting 1befdgh

258 new_task_teardown_setting = new_lang_class.task_teardown_setting 1befdgh

259 new_test_template_setting = new_lang_class.test_template_setting 1befdgh

260 new_task_template_setting = new_lang_class.task_template_setting 1befdgh

261 new_test_timeout_setting = new_lang_class.test_timeout_setting 1befdgh

262 new_task_timeout_setting = new_lang_class.task_timeout_setting 1befdgh

263 new_test_tags_setting = new_lang_class.test_tags_setting 1befdgh

264 new_task_tags_setting = new_lang_class.task_tags_setting 1befdgh

265 new_keyword_tags_setting = new_lang_class.keyword_tags_setting 1befdgh

266 new_tags_setting = new_lang_class.tags_setting 1befdgh

267 new_setup_setting = new_lang_class.setup_setting 1befdgh

268 new_teardown_setting = new_lang_class.teardown_setting 1befdgh

269 new_template_setting = new_lang_class.template_setting 1befdgh

270 new_timeout_setting = new_lang_class.timeout_setting 1befdgh

271 new_arguments_setting = new_lang_class.arguments_setting 1befdgh

272 old_lang_given_prefixes = old_lang_class.given_prefixes 1befdgh

273 old_lang_when_prefixes = old_lang_class.when_prefixes 1befdgh

274 old_lang_then_prefixes = old_lang_class.then_prefixes 1befdgh

275 old_lang_and_prefixes = old_lang_class.and_prefixes 1befdgh

276 old_lang_but_prefixes = old_lang_class.but_prefixes 1befdgh

277 new_lang_given_prefixes = new_lang_class.given_prefixes 1befdgh

278 new_lang_when_prefixes = new_lang_class.when_prefixes 1befdgh

279 new_lang_then_prefixes = new_lang_class.then_prefixes 1befdgh

280 new_lang_and_prefixes = new_lang_class.and_prefixes 1befdgh

281 new_lang_but_prefixes = new_lang_class.but_prefixes 1befdgh

282 old_true_strings = old_lang_class.true_strings 1befdgh

283 old_false_strings = old_lang_class.false_strings 1befdgh

284 new_true_strings = new_lang_class.true_strings 1befdgh

285 new_false_strings = new_lang_class.false_strings 1befdgh

286 # If error in Language, do final replacement 

287 signal_correct_language, m_text = _check_lang_error(node_info, m_text) 1befdgh

288 for old, new in zip(old_lang_headers.keys(), new_lang_headers.keys()): 1befdgh

289 m_text = re.sub(r"[*]+\s"+fr"{old}"+r"\s[*]+", fr"*** {new} ***", m_text) 1befdgh

290 # Settings must be replaced individually 

291 # Order of replacements seems to be important 

292 m_text = re.sub(fr'^{old_library_setting}\b', fr'{new_library_setting}', m_text, flags=re.M) 1befdgh

293 m_text = re.sub(fr'^{old_resource_setting}\b', fr'{new_resource_setting}', m_text, flags=re.M) 1befdgh

294 m_text = re.sub(fr'^{old_variables_setting}\b', fr'{new_variables_setting}', m_text, flags=re.M) 1befdgh

295 m_text = re.sub(fr'^{old_documentation_setting}\b', fr'{new_documentation_setting}', m_text, flags=re.M) 1befdgh

296 m_text = re.sub(fr'\[{old_documentation_setting}]', fr'[{new_documentation_setting}]', m_text) 1befdgh

297 m_text = re.sub(fr'\[{old_arguments_setting}]', fr'[{new_arguments_setting}]', m_text) 1befdgh

298 m_text = re.sub(fr'^{old_test_timeout_setting}\b', fr'{new_test_timeout_setting}', m_text, flags=re.M) 1befdgh

299 m_text = re.sub(fr'^{old_task_timeout_setting}\b', fr'{new_task_timeout_setting}', m_text, flags=re.M) 1befdgh

300 m_text = re.sub(fr'^{old_suite_setup_setting}\b', fr'{new_suite_setup_setting}', m_text, flags=re.M) 1befdgh

301 m_text = re.sub(fr'^{old_suite_teardown_setting}\b', fr'{new_suite_teardown_setting}', m_text, flags=re.M) 1befdgh

302 m_text = re.sub(fr'^{old_test_setup_setting}\b', fr'{new_test_setup_setting}', m_text, flags=re.M) 1befdgh

303 m_text = re.sub(fr'^{old_task_setup_setting}\b', fr'{new_task_setup_setting}', m_text, flags=re.M) 1befdgh

304 m_text = re.sub(fr'\[{old_template_setting}]', fr'[{new_template_setting}]', m_text) 1befdgh

305 m_text = re.sub(fr'^{old_test_teardown_setting}\b', fr'{new_test_teardown_setting}', m_text, flags=re.M) 1befdgh

306 m_text = re.sub(fr'^{old_task_teardown_setting}\b', fr'{new_task_teardown_setting}', m_text, flags=re.M) 1befdgh

307 m_text = re.sub(fr'\[{old_tags_setting}]', fr'[{new_tags_setting}]', m_text) 1befdgh

308 m_text = re.sub(fr'\[{old_setup_setting}]', fr'[{new_setup_setting}]', m_text) 1befdgh

309 m_text = re.sub(fr'\[{old_teardown_setting}]', fr'[{new_teardown_setting}]', m_text) 1befdgh

310 m_text = re.sub(fr'^{old_metadata_setting}\b', fr'{new_metadata_setting}', m_text, flags=re.M) 1befdgh

311 m_text = re.sub(fr'^{old_test_template_setting}\b', fr'{new_test_template_setting}', m_text, flags=re.M) 1befdgh

312 m_text = re.sub(fr'^{old_task_template_setting}\b', fr'{new_task_template_setting}', m_text, flags=re.M) 1befdgh

313 m_text = re.sub(fr'\[{old_keyword_tags_setting}]', fr'[{new_keyword_tags_setting}]', m_text) 1befdgh

314 m_text = re.sub(fr'\[{old_timeout_setting}]', fr'[{new_timeout_setting}]', m_text) 1befdgh

315 m_text = re.sub(fr'^{old_test_tags_setting}\b', fr'{new_test_tags_setting}', m_text, flags=re.M) 1befdgh

316 m_text = re.sub(fr'^{old_task_tags_setting}\b', fr'{new_task_tags_setting}', m_text, flags=re.M) 1befdgh

317 

318 # Only the False/True words will be replaced not positionally or bound by [] 

319 for old, new in zip(old_true_strings, new_true_strings): 1befdgh

320 m_text = re.sub(fr"\b{old}\b", fr"{new}", m_text) 1befdgh

321 for old, new in zip(old_false_strings, new_false_strings): 1befdgh

322 m_text = re.sub(fr"\b{old}\b", fr"{new}", m_text) 1befdgh

323 # At least in Portuguese No Operation would change to Não Operation, 

324 # But Name would change to Nãome when should be Nome, so we do after false strings 

325 m_text = re.sub(fr'^{old_name_setting}\b', fr'{new_name_setting}', m_text, flags=re.M) 1befdgh

326 

327 for old, new in zip(old_lang_given_prefixes, new_lang_given_prefixes): 1befdgh

328 m_text = re.sub(RSPC+fr"{old}"+r"\s", fr" {new} ", m_text) 1befdgh

329 for old, new in zip(old_lang_when_prefixes, new_lang_when_prefixes): 1befdgh

330 m_text = re.sub(RSPC+fr"{old}"+r"\s", fr" {new} ", m_text) 1befdgh

331 for old, new in zip(old_lang_then_prefixes, new_lang_then_prefixes): 1befdgh

332 m_text = re.sub(RSPC+fr"{old}"+r"\s", fr" {new} ", m_text) 1befdgh

333 for old, new in zip(old_lang_and_prefixes, new_lang_and_prefixes): 1befdgh

334 m_text = re.sub(RSPC+fr"{old}"+r"\s", fr" {new} ", m_text) 1befdgh

335 for old, new in zip(old_lang_but_prefixes, new_lang_but_prefixes): 1befdgh

336 m_text = re.sub(RSPC+fr"{old}"+r"\s", fr" {new} ", m_text) 1befdgh

337 

338 # Before ending, we replace broken keywords from excluded known bad tanslations 

339 m_text = transform_standard_keywords(new_lang_name, m_text) 1befdgh

340 return _final_lang_transformation(signal_correct_language, old_lang_name, new_lang_name, m_text) 1befdgh

341 

342 

343def transform_standard_keywords(new_lang: str, content: str) -> str: 

344 """ 

345 This function must be called after proper setting of parameters old_lang, new_lang. From transform_doc_language. 

346 It reads the corresponding new_lang exclusion file and does the replacing. 

347 :param new_lang: Name of the language to correct 

348 :param content: Content to apply recovery of keywords 

349 :return: Corrected test suite content with English keywords from Standard libraries 

350 """ 

351 try: 1befdgh

352 mlang = Language.from_name(new_lang.replace('_', '-')) 1befdgh

353 lang_code = get_rf_lang_code(mlang.code, iso=True) 1befdgh

354 except ValueError: 

355 return content 

356 

357 path_to_exclusion = f"{PATH_EXCLUSIONS}/../localization/{lang_code}/restore_keywords.json" 1befdgh

358 print(f"DEBUG: texteditor.py transform_standard_keywords path={path_to_exclusion}\n" 1befdgh

359 f"{lang_code=}\n" 

360 f"{mlang.code=}") 

361 import json 1befdgh

362 

363 try: 1befdgh

364 with open(path_to_exclusion) as file: 1befdgh

365 data = json.load(file) 1befdgh

366 except FileNotFoundError: 1d

367 return content 1d

368 

369 fix_list = data['fix_list'] 1befdgh

370 if len(fix_list) == 0: 370 ↛ 371line 370 didn't jump to line 371 because the condition on line 370 was never true1befdgh

371 return content 

372 # print(f"DEBUG: texteditor.py transform_standard_keywords my_variable={fix_list}") 

373 for kw in fix_list: 1befdgh

374 # print(f"DEBUG: texteditor.py transform_standard_keywords kws BAD={kw[1]} GOOD={kw[0]}") 

375 content = re.sub(fr'\b{kw[1]}\b', fr'{kw[0]}', content) 1befdgh

376 return content 1befdgh

377 

378 

379class TextEditorPlugin(Plugin, TreeAwarePluginMixin): 

380 title = PLUGIN_NAME 

381 

382 def __init__(self, application): 

383 self.title = _('Text Edit') 

384 Plugin.__init__(self, application) 

385 self._editor_component = None 

386 self._tab = None 

387 self._doc_language = None 

388 self._save_flag = 0 

389 self.jump = True 

390 # self.status_bar = self.frame.FindWindowByName("StatusBar", self.frame) 

391 # self.frame.SetStatusBarPane(1) 

392 self.reformat = application.settings.get('reformat', False) 

393 self.settings = application.settings 

394 self.application = application 

395 self._register_shortcuts() 

396 

397 @property 

398 def _editor(self): 

399 if self._editor_component is None: 1akDcE

400 self._editor_component = SourceEditor(self, self.notebook, 

401 self.title, 

402 DataValidationHandler(self, lang=self._doc_language)) 

403 self._refresh_timer = wx.Timer(self._editor_component) 

404 self._editor_component.Bind(wx.EVT_TIMER, self._on_timer) 

405 return self._editor_component 1akDcE

406 

407 def enable(self): 

408 self._tab = self._editor 1aE

409 _menudata = get_menudata() 1aE

410 self.register_actions(action_info_collection(_menudata, self._tab, data_nt=_EDIT_nt, container=self._tab)) 1aE

411 self.subscribe(self.on_tree_selection, RideTreeSelection) 1aE

412 self.subscribe(self.on_data_changed, RideMessage) 1aE

413 self.subscribe(self.on_tab_change, RideNotebookTabChanging) 1aE

414 self.add_self_as_tree_aware_plugin() 1aE

415 if self._editor.is_focused(): 415 ↛ exitline 415 didn't return from function 'enable' because the condition on line 415 was always true1aE

416 self._register_shortcuts() 1aE

417 self._open() 1aE

418 

419 def _register_shortcuts(self): 

420 def focused(func): 1aE

421 def f(event): 1aE

422 if self.is_focused() and self._editor.is_focused(): 

423 func(event) 

424 

425 return f 1aE

426 

427 if IS_MAC: # Mac needs this key binding 427 ↛ 431line 427 didn't jump to line 431 because the condition on line 427 was always true1aE

428 self.register_shortcut('CtrlCmd-A', focused(lambda e: self._editor.select_all())) 1aE

429 # No system needs this key binding, because is already global 

430 # self.register_shortcut('CtrlCmd-V', focused(lambda e: self._editor.paste())) 

431 self.register_shortcut('CtrlCmd-F', focused(lambda e: self._editor.search_field.SetFocus())) 1aE

432 # To avoid double actions these moved to on_key_down 

433 # self.register_shortcut('CtrlCmd-G', focused(lambda e: self._editor.on_find(e))) 

434 # self.register_shortcut('CtrlCmd-Shift-G', focused(lambda e: self._editor.on_find_backwards(e))) 

435 self.register_shortcut('Ctrl-Space', lambda e: focused(self._editor.on_content_assist(e))) 1aE

436 self.register_shortcut('CtrlCmd-Space', lambda e: focused(self._editor.on_content_assist(e))) 1aE

437 self.register_shortcut('Alt-Space', lambda e: focused(self._editor.on_content_assist(e))) 1aE

438 

439 def disable(self): 

440 self.remove_self_from_tree_aware_plugins() 

441 self.unsubscribe_all() 

442 self.unregister_actions() 

443 self.delete_tab(self._editor) 

444 wx.CallLater(500, self.unregister_actions()) 

445 self._tab = None 

446 self._editor_component = None 

447 

448 def on_open(self, event): 

449 __ = event 

450 self._open() 

451 

452 def _open(self): 

453 # print(f"DEBUG: texteditor.py TextEditorPlugin _open ENTER curpos={self._editor._position}") 

454 datafile_controller = self.tree.get_selected_datafile_controller() 1aDrcbjefdghE

455 if datafile_controller: 455 ↛ 456line 455 didn't jump to line 456 because the condition on line 455 was never true1aDrcbjefdghE

456 self._save_flag = 0 

457 if hasattr(datafile_controller, 'language'): 

458 if datafile_controller.language is not None: 

459 self._set_shared_doc_lang(datafile_controller.language) 

460 # print(f"DEBUG: texteditor _open SET FROM CONTROLLER language={self._doc_language}") 

461 else: 

462 self._set_shared_doc_lang('en') 

463 self._editor.language = self._doc_language 

464 # print(f"DEBUG: texteditor _open language={self._doc_language}") 

465 self._open_data_for_controller(datafile_controller) 

466 self._editor.store_position() 

467 wx.CallLater(2000, self.statusbar_message, f'{_("Source: ")}{self._editor.datafile_controller.source}', 2000) 

468 

469 def _get_shared_doc_lang(self): 

470 try: 

471 set_lang = shared_memory.ShareableList(name="language") 

472 except AttributeError: # Unittests fails here 

473 set_lang = [] 

474 if set_lang is not None: 

475 self._doc_language = set_lang[0] 

476 else: 

477 self._doc_language = 'en' 

478 return self._doc_language 

479 

480 def _set_shared_doc_lang(self, lang='en'): 

481 # print(f"DEBUG: textedit.py TextEditorPlugin _set_shared_doc_lang ENTER" 

482 # f" params: {lang=}") 

483 if isinstance(lang, list): 

484 lang = lang[0] 

485 # Shared memory to store language definition 

486 try: 

487 set_lang = shared_memory.ShareableList([lang], name="language") 

488 except FileExistsError: # Other instance created file 

489 set_lang = shared_memory.ShareableList(name="language") 

490 except AttributeError: # Unittests fails here 

491 set_lang = [] 

492 self._doc_language = set_lang[0] = lang 

493 

494 def _check_message(self, message: RideMessage) -> None: 

495 if isinstance(message, RideOpenSuite): # Not reached 495 ↛ 496line 495 didn't jump to line 496 because the condition on line 495 was never true

496 self._editor.reset() 

497 self._editor.set_editor_caret_position() 

498 if isinstance(message, RideNotebookTabChanging): # Not reached 498 ↛ 499line 498 didn't jump to line 499 because the condition on line 498 was never true

499 return 

500 # Workaround for remarked dirty with Ctrl-S 

501 if self.is_focused() and self._save_flag == 0 and isinstance(message, RideSaving): 501 ↛ 502line 501 didn't jump to line 502 because the condition on line 501 was never true

502 self._save_flag = 1 

503 RideBeforeSaving().publish() 

504 if self.is_focused() and self._save_flag == 1 and isinstance(message, RideDataDirtyCleared): 504 ↛ 505line 504 didn't jump to line 505 because the condition on line 504 was never true

505 self._save_flag = 2 

506 if self.is_focused() and self._save_flag == 2 and isinstance(message, RideSaved): 506 ↛ 507line 506 didn't jump to line 507 because the condition on line 506 was never true

507 self._save_flag = 3 

508 print(f"DEBUG: textedit.py TextEditorPlugin _check_message call undirty, {message}") 

509 # wx.CallAfter(self._editor.mark_file_dirty, False) 

510 self._editor.mark_file_dirty(False) 

511 self.tree._data_undirty(message) 

512 # DEBUG: This is the unwanted chnge after saving but excluded in this block for performance 

513 # if self.is_focused() and self._save_flag == 3 and isinstance(message, RideDataChangedToDirty): 

514 # self._save_flag = 4 

515 # wx.CallAfter(self._editor.mark_file_dirty, False) 

516 if isinstance(message, RideBeforeSaving): 516 ↛ 519line 516 didn't jump to line 519 because the condition on line 516 was never true

517 # self._editor.is_saving = False 

518 # Reset counter for Workaround for remarked dirty with Ctrl-S 

519 self._save_flag = 0 

520 self._apply_txt_changes_to_model() 

521 

522 def on_data_changed(self, message): 

523 """ This block is now inside try/except to avoid errors from unit test """ 

524 try: 1akDcE

525 if self.is_focused() and isinstance(message, RideDataChangedToDirty): 525 ↛ 527line 525 didn't jump to line 527 because the condition on line 525 was never true1akDcE

526 # print(f"DEBUG: textedit OnDataChanged returning RideDataChangedToDirty {self._save_flag=}") 

527 return 

528 if self._should_process_data_changed_message(message): 1akDcE

529 # print(f"DEBUG: textedit after _should_process_data_changed_message save_flag={self._save_flag}") 

530 self._check_message(message) 

531 self._refresh_timer.Start(500, True) 

532 # For performance reasons only run after all the data changes 

533 except AttributeError: 

534 pass 

535 

536 def _on_timer(self, event): 

537 self._editor.store_position() 

538 self._open_tree_selection_in_editor() 

539 event.Skip() 

540 

541 @staticmethod 

542 def _should_process_data_changed_message(message): 

543 return isinstance(message, (RideDataChanged, RideBeforeSaving, RideSaved, RideSaving, RideDataDirtyCleared)) 1akDcE

544 # and not isinstance(message, RideDataChangedToDirty)) 

545 

546 def on_tree_selection(self, message): 

547 if not self.is_focused(): 547 ↛ 548line 547 didn't jump to line 548 because the condition on line 547 was never true1ak

548 return 

549 # print(f"DEBUG: texteditor.py TextEditorPlugin on_tree_selection ENTER {message=} type={type(message.item)}") 

550 self._editor.store_position() 1ak

551 # self.jump = True 

552 if self.is_focused(): 552 ↛ 574line 552 didn't jump to line 574 because the condition on line 552 was always true1ak

553 next_datafile_controller = message.item and message.item.datafile_controller 1ak

554 if self._editor.datafile_controller == message.item.datafile_controller == next_datafile_controller: 554 ↛ 557line 554 didn't jump to line 557 because the condition on line 554 was never true1ak

555 # print(f"DEBUG: OnTreeSelection Same FILE item type={type(message.item)}\n" 

556 # f"value of {self._save_flag=}") 

557 self._editor.locate_tree_item(message.item) 

558 self.jump = True 

559 return 

560 if self._editor.dirty and not self._apply_txt_changes_to_model(): 560 ↛ 561line 560 didn't jump to line 561 because the condition on line 560 was never true

561 if self._editor.datafile_controller != next_datafile_controller: 

562 self.tree.select_controller_node(self._editor.datafile_controller) 

563 self._editor.set_editor_caret_position() 

564 return 

565 if next_datafile_controller: 565 ↛ 571line 565 didn't jump to line 571 because the condition on line 565 was always true

566 self.jump = True 

567 self._open_data_for_controller(next_datafile_controller) 

568 # print(f"DEBUG: OnTreeSelection OTHER FILE item type={type(message.item)}\n" 

569 # f"value of {self._save_flag=}") 

570 wx.CallAfter(self._editor.locate_tree_item, message.item) 

571 self._set_read_only(message) 

572 self._editor.set_editor_caret_position() 

573 else: 

574 self._editor.GetFocus(None) 

575 

576 def _set_read_only(self, message): 

577 if not isinstance(message, bool): 1aD

578 self._editor.source_editor.readonly = not message.item.datafile_controller.is_modifiable() 

579 self._editor.source_editor.SetReadOnly(self._editor.source_editor.readonly) 1aD

580 self._editor.source_editor.stylizer.set_styles(self._editor.source_editor.readonly) 1aD

581 self._editor.source_editor.Update() 1aD

582 

583 def _open_tree_selection_in_editor(self): 

584 try: 

585 datafile_controller = self.tree.get_selected_datafile_controller() 

586 if datafile_controller: 586 ↛ 587line 586 didn't jump to line 587 because the condition on line 586 was never true

587 self._editor.language = datafile_controller.language 

588 except AttributeError: 

589 return 

590 if datafile_controller: 590 ↛ 591line 590 didn't jump to line 591 because the condition on line 590 was never true

591 self._editor.language = datafile_controller.language 

592 self.global_settings['doc language'] = datafile_controller.language 

593 self._editor.open(DataFileWrapper(datafile_controller, self.global_settings, self._editor.language)) 

594 self._editor.source_editor.readonly = not datafile_controller.is_modifiable() 

595 self._editor.set_editor_caret_position() 

596 

597 def _open_data_for_controller(self, datafile_controller): 

598 self._editor.language = datafile_controller.language 

599 self.global_settings['doc language'] = datafile_controller.language 

600 self._editor.selected(DataFileWrapper(datafile_controller, self.global_settings, self._editor.language)) 

601 self._editor.source_editor.readonly = not datafile_controller.is_modifiable() 

602 

603 def on_tab_change(self, message): 

604 if message.newtab == self.title: 1aD

605 self._open() 1D

606 self._editor.set_editor_caret_position() 1D

607 try: 1D

608 self._set_read_only(self._editor.source_editor.readonly) 1D

609 except Exception as e: # DEBUG: When using only Text Editor exists error in message topic 

610 print(e) 

611 # wx.CallAfter(self._editor.source_editor.on_style, None) # DEBUG Text Edit, styles were not applied 

612 self._editor.source_editor.on_style(None) 1D

613 self._editor.Refresh() 1D

614 elif message.oldtab == self.title: 1aD

615 self._editor.remove_and_store_state() 1D

616 self._apply_txt_changes_to_model() 1D

617 

618 def on_tab_changed(self, event): 

619 __ = event 

620 self._show_editor() 

621 event.Skip() 

622 

623 def _apply_txt_changes_to_model(self): 

624 if not self.is_focused() and not self._editor.dirty: 624 ↛ anywhereline 624 didn't jump anywhere: it always raised an exception.1D

625 return 

626 # self._editor.is_saving = False 

627 # self._editor.store_position() 

628 # print(f"DEBUG: textedit.py _apply_txt_changes_to_model CALL content_save lang={self._doc_language}" 

629 # f" curpos={self._editor._position}") 

630 if not self._editor.content_save(lang=self._doc_language): 

631 return False 

632 self._editor.reset() 

633 self._editor.set_editor_caret_position() 

634 return True 

635 

636 def is_focused(self): 

637 try: 1akDcEH

638 return self.notebook.current_page_title == self.title 1akDcEH

639 except AttributeError: 1akDc

640 return self._editor.is_focused() 1akDc

641 

642 def on_config_panel(self): 

643 dlg = self.config_panel(self.frame) 

644 dlg.Show(True) 

645 

646 def config_panel(self, parent): 

647 __ = parent 

648 _parent = wx.GetTopLevelWindows() 

649 dlg = PreferenceEditor(_parent[0], _("RIDE - Preferences"), 

650 self.application.preferences, style='single', index=4) 

651 dlg.Show(False) 

652 return dlg 

653 

654 

655class DummyController(WithStepsController): 

656 _populator = robotapi.UserKeywordPopulator 

657 filename = "" 

658 

659 def _init(self, data=None): 

660 self.data = data 

661 

662 @staticmethod 

663 def get_local_variables(): 

664 return {} 

665 

666 def __eq__(self, other): 

667 if self is other: 

668 return True 

669 if other.__class__ != self.__class__: 

670 return False 

671 return self.data == other.data 

672 

673 def __hash__(self): 

674 return hash(repr(self)) 

675 

676 

677class DataValidationHandler(object): 

678 

679 def __init__(self, plugin, lang=None): 

680 self._plugin = plugin 

681 self._last_answer = None 

682 self._last_answer_time = 0 

683 self._editor = None 

684 if lang is not None: 

685 self._doc_language = lang 

686 else: 

687 self._get_shared_doc_lang() 

688 file = tempfile.NamedTemporaryFile(prefix="model_saved_from_RIDE_", 

689 suffix=".robot", mode="w+", delete=False) 

690 self.tempfilename=file.name 

691 

692 def _get_shared_doc_lang(self): 

693 try: 

694 set_lang = shared_memory.ShareableList(name="language") 

695 except AttributeError: # Unittests fails here 

696 set_lang = [] 

697 if set_lang is not None: 697 ↛ 700line 697 didn't jump to line 700 because the condition on line 697 was always true

698 self._doc_language = set_lang[0] 

699 else: 

700 self._doc_language = 'en' 

701 return self._doc_language 

702 

703 def _set_shared_doc_lang(self, lang='en'): 

704 # print(f"DEBUG: textedit.py _set_shared_doc_lang ENTER" 

705 # f" params: {lang=}") 

706 if isinstance(lang, list): 

707 lang = lang[0] 

708 # Shared memory to store language definition 

709 try: 

710 set_lang = shared_memory.ShareableList([lang], name="language") 

711 except FileExistsError: # Other instance created file 

712 set_lang = shared_memory.ShareableList(name="language") 

713 except AttributeError: # Unittests fails here 

714 set_lang = [] 

715 self._doc_language = set_lang[0] = lang 

716 

717 def set_editor(self, editor): 

718 self._editor = editor 

719 

720 

721 def validate_and_update(self, data, text, lang='en'): 

722 

723 try: 

724 from robot.parsing.parser.parser import get_model # RF > 4.0 

725 except ImportError: 

726 return self._old_validate_and_update(data, text) 

727 return self._new_validate_and_update(data, text, lang) 

728 

729 """ 

730 Backwards compatible code v1.7.4.2 

731 """ 

732 

733 def _old_validate_and_update(self, data, text): 

734 m_text = text.decode("utf-8") 

735 if not self._old_sanity_check(data, m_text): 

736 handled = self._old_handle_sanity_check_failure() 

737 if not handled: 

738 return False 

739 self._editor.reset() 

740 # print("DEBUG: updating text") # %s" % (self._editor.GetCurrentPos())) 

741 data.update_from(m_text) 

742 # print("DEBUG: AFTER updating text") 

743 self._editor.set_editor_caret_position() 

744 # %s" % (self._editor.GetCurrentPos())) 

745 return True 

746 

747 def _old_sanity_check(self, data, text): 

748 formatted_text = data.format_text(text) 

749 # print(f"DEBUG: texteditor old_sanity_check {formatted_text=}") 

750 c = self._normalize(formatted_text) 

751 e = self._normalize(text) 

752 return len(c) == len(e) 

753 

754 @staticmethod 

755 def _normalize(text): 

756 for item in [' ', r'\t', r'\n', r'\r\n', '...', '*']: 

757 if item in text: 

758 # print("DEBUG: _normaliz item %s txt %s" % (item, text)) 

759 text = text.replace(item, '') 

760 return text 

761 

762 def _old_handle_sanity_check_failure(self): 

763 if self._last_answer == wx.ID_NO and \ 

764 time() - self._last_answer_time <= 0.2: 

765 self._editor._mark_file_dirty() 

766 return False 

767 # TODO: use widgets.Dialog 

768 id = wx.MessageDialog(self._editor, 

769 'ERROR: Data sanity check failed!\n' 

770 'Reset changes?', 

771 'Can not apply changes from Txt Editor', 

772 style=wx.YES | wx.NO).ShowModal() 

773 self._last_answer = id 

774 self._last_answer_time = time() 

775 if id == wx.ID_YES: 

776 self._editor._revert() 

777 return True 

778 else: 

779 self._editor._mark_file_dirty() 

780 return False 

781 

782 """ 

783 End Backwards compatible code v1.7.4.2 

784 """ 

785 

786 def _new_validate_and_update(self, data, text, lang='en'): 

787 from robotide.lib.robot.errors import DataError 

788 m_text = text.decode("utf-8") 

789 # print(f"DEBUG: textedit.py validate_and_update ENTER" 

790 # f" params: {lang=} doc_language={self._doc_language}") 

791 initial_lang = lang # if lang is not None else self._doc_language self._doc_language or 

792 if LANG_SETTING in m_text: 

793 try: 

794 self._doc_language = obtain_language(lang, text) 

795 except ValueError: 

796 self._doc_language = 'en' 

797 # print(f"DEBUG: textedit.py validate_and_update Language in doc--> lang={self._doc_language}") 

798 else: 

799 self._doc_language = lang if lang is not None else 'en' 

800 # print(f"DEBUG: textedit.py validate_and_update NO Language in doc--> arg lang={lang} " 

801 # f"set to={self._doc_language}") 

802 self._editor.language = self._doc_language 

803 

804 try: 

805 result = self._sanity_check(data, m_text) # First check 

806 # print(f"DEBUG: textedit.py validate_and_update Language after sanity_check result={result}") 

807 except DataError as err: 

808 result = (err.message, err.details) 

809 

810 # print(f"DEBUG: textedit.py validate_and_update Language after sanity_check result={result}\n" 

811 # f" lang params: {initial_lang=}, {self._doc_language=}") 

812 if isinstance(result, tuple): 

813 m_text = transform_doc_language(initial_lang, self._doc_language, m_text, node_info=result) 

814 __ = self._get_shared_doc_lang() 

815 try: 

816 result = self._sanity_check(data, m_text) # Check if language changed and is valid content 

817 except DataError as err: 

818 result = (err.message, err.details) 

819 if isinstance(result, tuple): 

820 handled = self._handle_sanity_check_failure(result) 

821 if not handled: 

822 return False 

823 # Save language 

824 self._set_shared_doc_lang(self._doc_language) 

825 if self._editor.reformat: 

826 data.update_from(data.format_text(m_text)) 

827 else: 

828 # DEBUG: This is the area where we will implement to not reformat code 

829 # print(f"DEBUG: TextEditor.py DataValidationHandler validate_and_update calling update NO REFORMAT" 

830 # f" lang={self._doc_language} text={m_text}") 

831 data.update_from(m_text) 

832 # There is no way to update the model without reformatting 

833 # DEBUG: this only updates the editor, but not the model, changes in Text Editor are not reflected 

834 # in Grid or when saving 

835 # self.source_editor.source_editor.set_text(m_text) 

836 self._editor.set_editor_caret_position() 

837 return True 

838 

839 def _sanity_check(self, data, text): 

840 from robotide.lib.compat.parsing import ErrorReporter 

841 from robot.parsing.parser.parser import get_model 

842 from robotide.lib.robot.errors import DataError 

843 result = None 

844 rf_lang = get_rf_lang_code(self._doc_language) 

845 # print(f"DEBUG: textedit.py _sanity_check data is type={type(data)} lang={self._doc_language}," 

846 # f" transformed lang={rf_lang}") 

847 from robot.api.parsing import get_tokens 

848 for token in get_tokens(text): 

849 # print(repr(token)) 

850 if token.type == token.ERROR: 

851 # print("DEBUG: textedit.py _sanity_check TOKEN in ERROR") 

852 result = 'ERROR', repr(token) 

853 return result 

854 if token.type == token.INVALID_HEADER: 

855 # print("DEBUG: textedit.py _sanity_check TOKEN in INVALID_HEADER") 

856 result = 'INVALID_HEADER', repr(token) 

857 return result 

858 

859 try: 

860 model = get_model(text, lang=rf_lang) 

861 except AttributeError: 

862 return "Failed validation by Robot Framework", "Please, check if Language setting is valid!" 

863 validator = ErrorReporter() 

864 try: 

865 validator.visit(model) 

866 except DataError as err: 

867 result = (err.message, err.details) 

868 model.save(self.tempfilename) 

869 # print(f"DEBUG: textedit.py _sanity_check after calling validator {validator}\n" 

870 # f"Save model in /tmp/model_saved_from_RIDE.robot" 

871 # f" result={result}") 

872 return True if not result else result 

873 

874 """ DEBUG 

875 formatted_text = data.format_text(text) 

876 c = self._normalize(formatted_text) 

877 e = self._normalize(text) 

878 return len(c) == len(e) 

879 """ 

880 

881 """ DEBUG: This is no longer used 

882 @staticmethod 

883 def _normalize(text): 

884 for item in tuple(string.whitespace) + ('...', '*'): 

885 if item in text: 

886 text = text.replace(item, '') 

887 return text 

888 """ 

889 

890 def _handle_sanity_check_failure(self, message): 

891 if isinstance(message[1], str) and message[1].startswith(TOKEN_TXT): 

892 c_msg = message[1].replace(TOKEN_TXT, '').replace(')', '').split(',') 

893 message = [" ".join(c_msg[4:]), c_msg[2].strip()] 

894 

895 if self._last_answer == wx.ID_NO and time() - self._last_answer_time <= 0.2: 

896 # self.source_editor._mark_file_dirty(True) 

897 return False 

898 dlg = RIDEDialog(title=_("Can not apply changes from Text Editor"), 

899 message=f"{_('ERROR: Data sanity check failed!')}\n{_('Error at line')}" 

900 f" {message[1]}:\n{message[0]}\n\n{_('Reset changes?')}", 

901 style=wx.ICON_ERROR | wx.YES_NO) 

902 dlg.InheritAttributes() 

903 did = dlg.ShowModal() 

904 self._last_answer = did 

905 self._last_answer_time = time() 

906 if did == wx.ID_YES: 

907 self._editor.revert() 

908 return True 

909 # else: 

910 # self.source_editor._mark_file_dirty() 

911 return False 

912 

913 

914class DataFileWrapper(object): # DEBUG: bad class name 

915 

916 def __init__(self, data, settings, language=None): 

917 self.wrapper_data = data 

918 self._settings = settings 

919 self._tab_size = self._settings.get(TXT_NUM_SPACES, 2) if self._settings else 2 

920 self._reformat = self._settings.get('reformat', False) if self._settings else False 

921 if language is not None: 921 ↛ 922line 921 didn't jump to line 922 because the condition on line 921 was never true

922 self._doc_language = language 

923 else: 

924 self._doc_language = ['en'] 

925 

926 def __eq__(self, other): 

927 if other is None: 927 ↛ 929line 927 didn't jump to line 929 because the condition on line 927 was always true

928 return False 

929 return self.wrapper_data == other.wrapper_data 

930 

931 def update_from(self, content): 

932 self.wrapper_data.execute(SetDataFile(self._create_target_from(content))) 

933 

934 def _create_target_from(self, content): 

935 src = BytesIO(content.encode("utf-8")) 

936 target = self._create_target(content.encode("utf-8")) 

937 FromStringIOPopulator(target, lang=self._doc_language).populate(src, self._tab_size) 

938 return target 

939 

940 def format_text(self, text): 

941 return self._txt_data(self._create_target_from(text)) 

942 

943 def mark_data_dirty(self): 

944 if not self.wrapper_data.is_dirty: 

945 self.wrapper_data.mark_dirty() 

946 

947 def mark_data_pristine(self): 

948 if self.wrapper_data.is_dirty: 948 ↛ 949line 948 didn't jump to line 949 because the condition on line 948 was never true1c

949 self.wrapper_data.unmark_dirty() 

950 

951 def _create_target(self, content=None): 

952 data = self.wrapper_data.data 

953 target_class = type(data) 

954 self._doc_language = obtain_language(self._doc_language, content=content) 

955 # print(f"DEBUG: textedit.py DataFileWrapper _create_target self._doc_language={self._doc_language}" 

956 # f"\n target class={target_class}") 

957 if isinstance(data, robotapi.TestDataDirectory): 

958 target = robotapi.TestDataDirectory(parent=None, source=self.wrapper_data.directory, 

959 settings=self._settings, language=self._doc_language) 

960 target.initfile = data.initfile 

961 return target 

962 elif isinstance(data, TestDataDirectoryWithExcludes): 

963 target = TestDataDirectoryWithExcludes(parent=None, source=self.wrapper_data.directory, 

964 settings=self._settings, language=self._doc_language) 

965 target.initfile = data.initfile 

966 return target 

967 return target_class(source=self.wrapper_data.source) 

968 

969 @property 

970 def content(self): 

971 return self._txt_data(self.wrapper_data.data) 

972 

973 def _txt_data(self, data): 

974 output = StringIO() 

975 data.save(output=output, fformat='txt', txt_separating_spaces=self._settings.get(TXT_NUM_SPACES, 4), 

976 language=self._doc_language) 

977 text = output.getvalue() 

978 """ DEBUG: This is a good place to call Tidy 

979 # if self._reformat:  

980 # text = self.collapse_blanks(text) # This breaks formatting in Templated tests 

981 # print(f"DEBUG: textedit.py DataFileWrapper content _txt_data = {text=} language={self._doc_language}") 

982 """ 

983 return text 

984 

985 """ DEBUG: This is no longer used 

986 def collapse_blanks(self, content: str) -> str: 

987 spaces = self._tab_size * ' ' 

988 block = [] 

989 for ln in content.splitlines(): 

990 block.append(ln.replace(f'\\{spaces}', '').replace(f'\\\\ ', '').split(spaces)) 

991 # print(f"DEBUG: texteditor.py collapse_blanks block={block}\n") 

992 new_text = '' 

993 for ln in block: 

994 blank_found = 0 

995 for sl in ln: 

996 if len(ln) == 1 and sl == '': 

997 blank_found = 0 

998 # sl = '\n' 

999 elif len(ln) > 1 and sl == '': 

1000 blank_found += 1 

1001 elif sl != '': 

1002 blank_found = 0 

1003 if blank_found < 2: 

1004 new_text += sl + spaces 

1005 new_text = new_text.strip(' ') + '\n' 

1006 # print(f"DEBUG: texteditor.py collapse_blanks new_text={new_text}") 

1007 return new_text 

1008 """ 

1009 

1010 

1011class SourceEditor(wx.Panel): 

1012 

1013 def __init__(self, plugin, parent, title, data_validator): 

1014 wx.Panel.__init__(self, parent) 

1015 self.dlg = RIDEDialog() 

1016 self.SetBackgroundColour(Colour(self.dlg.color_background)) 

1017 self.SetForegroundColour(Colour(self.dlg.color_foreground)) 

1018 self._syntax_colorization_help_exists = False 

1019 self._data_validator = data_validator 

1020 self._data_validator.set_editor(self) 

1021 self.source_editor_parent = parent 

1022 self.plugin = plugin 

1023 self.datafile = None 

1024 self._title = title 

1025 self.tab_size = self.source_editor_parent.app.settings.get(TXT_NUM_SPACES, 4) 

1026 self.reformat = self.source_editor_parent.app.settings.get('reformat', False) 

1027 self._doc_language = None 

1028 try: 

1029 set_lang = shared_memory.ShareableList(name="language") 

1030 except AttributeError: # Unittests fails here 

1031 set_lang = [] 

1032 if not set_lang: 1032 ↛ 1033line 1032 didn't jump to line 1033 because the condition on line 1032 was never true

1033 set_lang[0] = ['en'] 

1034 self._doc_language = set_lang[0] 

1035 self._create_ui(title) 

1036 self._data = None 

1037 self._position = 0 # Start at 0 if first time access 

1038 self.restore_start_pos = self._position 

1039 self.restore_end_pos = self._position 

1040 self.restore_anchor = self._position 

1041 self._showing_list = False 

1042 self.autocomp_pos = None 

1043 self._tab_open = self._title # When starting standalone this was not being set 

1044 self._controller_for_context = None 

1045 self._suggestions = None 

1046 self.doc_size: int = 0 # Number of lines in document to be used in collecting words 

1047 self._words_cache = set() # Actual cache of words to add to suggestions 

1048 self._stored_text = None 

1049 self._ctrl_action = None 

1050 # self.is_saving = False # To avoid double calls to save 

1051 self.old_information_popup = None 

1052 PUBLISHER.subscribe(self.on_settings_changed, RideSettingsChanged) 

1053 PUBLISHER.subscribe(self.on_tab_change, RideNotebookTabChanging) 

1054 

1055 @property 

1056 def general_font_size(self): 

1057 fsize = self.source_editor_parent.app.settings.get('General', None)['font size'] 

1058 return fsize 

1059 

1060 def is_focused(self): 

1061 # DEBUG: original method: foc = wx.Window.FindFocus() 

1062 # DEBUG: any(elem == foc for elem in [self]+list(self.GetChildren())) 

1063 return self._tab_open == self._title 1aAikDcE

1064 

1065 def on_tab_change(self, message): 

1066 self._tab_open = message.newtab 1aD

1067 

1068 def _create_ui(self, title): 

1069 cnt = self.source_editor_parent.GetPageCount() 

1070 if cnt >= 0: 1070 ↛ exitline 1070 didn't return from function '_create_ui' because the condition on line 1070 was always true

1071 editor_created = False 

1072 while cnt > 0 and not editor_created: 

1073 cnt -= 1 

1074 editor_created = self.source_editor_parent.GetPageText(cnt) == self._title 

1075 # DEBUG: Later we can adjust for several Text Editor tabs 

1076 if not editor_created: 1076 ↛ exitline 1076 didn't return from function '_create_ui' because the condition on line 1076 was always true

1077 self.SetSizer(VerticalSizer()) 

1078 self._create_editor_toolbar() 

1079 self._create_editor_text_control(language=self.language) 

1080 self.source_editor_parent.add_tab(self, title, allow_closing=False) 

1081 

1082 def _create_editor_toolbar(self): 

1083 # needs extra container, since we might add helper 

1084 # text about syntax colorization 

1085 self.editor_toolbar = HorizontalSizer() 

1086 default_components = HorizontalSizer() 

1087 button = ButtonWithHandler(self, _('Apply Changes'), fsize=self.general_font_size, 

1088 handler=lambda e: self.plugin._apply_txt_changes_to_model()) 

1089 button.SetBackgroundColour(Colour(self.dlg.color_secondary_background)) 

1090 button.SetForegroundColour(Colour(self.dlg.color_secondary_foreground)) 

1091 config_button = ButtonWithHandler(self, _('Settings'), bitmap='wrench.png', fsize=self.general_font_size, 

1092 handler=lambda e: self.plugin.on_config_panel()) 

1093 config_button.SetBackgroundColour(Colour(self.dlg.color_background)) 

1094 config_button.SetOwnBackgroundColour(Colour(self.dlg.color_background)) 

1095 config_button.SetForegroundColour(Colour(self.dlg.color_foreground)) 

1096 default_components.add_with_padding(button) 

1097 self._create_search(default_components) 

1098 self.editor_toolbar.add_expanding(default_components) 

1099 self.editor_toolbar.add_with_padding(config_button) 

1100 self.Sizer.add_expanding(self.editor_toolbar, propotion=0) 

1101 

1102 def _create_search(self, container_sizer): 

1103 container_sizer.AddSpacer(5) 

1104 size = wx.Size(200, 32) 

1105 self.search_field = SearchField(self, '', size=size, process_enters=True) 

1106 self.search_field.SetBackgroundColour(Colour(self.dlg.color_secondary_background)) 

1107 self.search_field.SetForegroundColour(Colour(self.dlg.color_secondary_foreground)) 

1108 self.search_field.Bind(wx.EVT_TEXT_ENTER, self.on_find) 

1109 self.search_field.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN, self.on_find) 

1110 self.search_field.SetHint(_('Search')) 

1111 container_sizer.add_with_padding(self.search_field) 

1112 button = ButtonWithHandler(self, _('Search'), fsize=self.general_font_size, handler=self.on_find) 

1113 button.SetBackgroundColour(Colour(self.dlg.color_secondary_background)) 

1114 button.SetForegroundColour(Colour(self.dlg.color_secondary_foreground)) 

1115 container_sizer.add_with_padding(button) 

1116 self._search_field_notification = Label(self, label='') 

1117 container_sizer.add_with_padding(self._search_field_notification) 

1118 

1119 def create_syntax_colorization_help(self): 

1120 if self._syntax_colorization_help_exists: 

1121 return 

1122 label = Label(self, label=_("Syntax colorization disabled due to missing requirements.")) 

1123 link = HyperlinkCtrl(self, -1, label=_("Get help"), url="") 

1124 link.Bind(EVT_HYPERLINK, self.show_help_dialog) 

1125 flags = wx.ALIGN_RIGHT 

1126 syntax_colorization_help_sizer = wx.BoxSizer(wx.VERTICAL) 

1127 syntax_colorization_help_sizer.AddMany([ 

1128 (label, 0, flags), 

1129 (link, 0, flags) 

1130 ]) 

1131 self.editor_toolbar.add_expanding(syntax_colorization_help_sizer) 

1132 self.Layout() 

1133 self._syntax_colorization_help_exists = True 

1134 

1135 @staticmethod 

1136 def show_help_dialog(event): 

1137 __ = event 

1138 content = _("""<h1>Syntax colorization</h1> 

1139 <p> 

1140 Syntax colorization for Text Edit uses <a href='https://pygments.org/'>Pygments</a> syntax highlighter. 

1141 </p> 

1142 <p> 

1143 Install Pygments from command line with: 

1144 <pre> 

1145 pip install pygments 

1146 </pre> 

1147 Or: 

1148 <pre> 

1149 easy_install pygments 

1150 </pre> 

1151 Then, restart RIDE. 

1152 </p> 

1153 <p> 

1154 If you do not have pip or easy_install, 

1155 <a  

1156 href='https://pythonhosted.org/an_example_pypi_project/setuptools.html#installing-setuptools-and-easy-install' 

1157 >follow these instructions</a>. 

1158 </p> 

1159 <p> 

1160 For more information about installing Pygments, <a href='https://pygments.org/download/'>see the site</a>. 

1161 </p> 

1162 """) 

1163 HtmlDialog(_("Getting syntax colorization"), content).Show() 

1164 

1165 def store_position(self, force=False): 

1166 if self.source_editor: # We don't necessarily need a data controller, was: "and self.datafile_controller:" 1166 ↛ exitline 1166 didn't return from function 'store_position' because the condition on line 1166 was always true1aACxzuyvwstiBkDc

1167 cur_pos = self.source_editor.GetCurrentPos() 1aACxzuyvwstiBkDc

1168 self.restore_start_pos = self.source_editor.GetSelectionStart() 1aACxzuyvwstiBkDc

1169 self.restore_end_pos = self.source_editor.GetSelectionEnd() 1aACxzuyvwstiBkDc

1170 self.restore_anchor = self.source_editor.GetAnchor() 1aACxzuyvwstiBkDc

1171 if cur_pos > 0: # Cheating because it always goes to zero 1aACxzuyvwstiBkDc

1172 self._position = cur_pos 1axzuyvwstkc

1173 if force: 1axzuyvwstkc

1174 self.source_editor.GotoPos(self._position) 1c

1175 

1176 def set_editor_caret_position(self): 

1177 if not self.is_focused(): # DEBUG was typing text when at Grid Editor 1177 ↛ 1178line 1177 didn't jump to line 1178 because the condition on line 1177 was never true1aAiDc

1178 return 

1179 position = self._position 1aAiDc

1180 self.source_editor.SetFocus() 1aAiDc

1181 # print(f"DEBUG: texteditor.py SourceEditor set_editor_caret_position position={position}") 

1182 if position: 1aAiDc

1183 self.source_editor.SetCurrentPos(position) 1c

1184 self.source_editor.SetSelection(self.restore_start_pos, self.restore_end_pos) 1c

1185 self.source_editor.SetAnchor(self.restore_anchor) 1c

1186 self.source_editor.GotoPos(position) 1c

1187 self.source_editor.Refresh() 1c

1188 self.source_editor.Update() 1c

1189 

1190 @property 

1191 def dirty(self): 

1192 return self._data.wrapper_data.is_dirty if self._data else False 1aD

1193 

1194 @property 

1195 def datafile_controller(self): 

1196 return self._data.wrapper_data if self._data else None 1ak

1197 

1198 @property 

1199 def language(self): 

1200 return self._doc_language 1ac

1201 

1202 @language.setter 

1203 def language(self, flanguage): 

1204 self._doc_language = flanguage 1ac

1205 

1206 def on_find(self, event, forward=True): 

1207 if self.source_editor: 

1208 if event.GetEventType() == wx.wxEVT_SEARCH: 

1209 text = event.GetString() 

1210 elif event.GetEventType() != wx.wxEVT_TEXT_ENTER: # Was getting selected item from Tree 

1211 text = self.source_editor.GetSelectedText() 

1212 else: 

1213 text = '' 

1214 if (len(text) > 0 and text.lower() != self.search_field.GetValue().lower() and 

1215 event.GetEventType() != wx.wxEVT_TOOL): 

1216 # if a search string selected in text and CTRL+G is pressed 

1217 # put the string into the search_field 

1218 self.search_field.SelectAll() 

1219 self.search_field.Clear() 

1220 self.search_field.Update() 

1221 self.search_field.SetValue(text) 

1222 self.search_field.SelectAll() 

1223 self.search_field.Update() 

1224 if forward: 

1225 # and set the start position to the beginning of the editor 

1226 self.source_editor.SetAnchor(0) 

1227 self.source_editor.SetCurrentPos(0) 

1228 self.source_editor.Update() 

1229 self._find(forward) 

1230 

1231 def on_find_backwards(self, event): 

1232 if self.source_editor: 

1233 self.on_find(event, forward=False) 

1234 

1235 def _find(self, forward=True): 

1236 txt = self.search_field.GetValue().encode('utf-8') 

1237 position = self._find_text_position(forward, txt) 

1238 self._show_search_results(position, txt) 

1239 

1240 # DEBUG: This must be cleaned up 

1241 def _find_text_position(self, forward, txt): 

1242 file_end = len(self.source_editor.utf8_text) 1A

1243 search_end = file_end if forward else 0 1A

1244 anchor = self.source_editor.GetAnchor() 1A

1245 anchor += 1 if forward else 0 1A

1246 position = self.source_editor.FindText(anchor, search_end, txt, 0) 1A

1247 if position == -1: 1247 ↛ 1248line 1247 didn't jump to line 1248 because the condition on line 1247 was never true1A

1248 start, end = (0, file_end) if forward else (file_end - 1, 0) 

1249 position = self.source_editor.FindText(start, end, txt, 0) 

1250 return position 1A

1251 

1252 def _show_search_results(self, position, txt): 

1253 # if text is found start and end of the found text is returned, 

1254 # but we do need just starting position which is the first value 

1255 if type(position) is tuple: 1255 ↛ 1258line 1255 didn't jump to line 1258 because the condition on line 1255 was always true1A

1256 position = position[0] 1A

1257 

1258 if position != -1: 1258 ↛ 1264line 1258 didn't jump to line 1264 because the condition on line 1258 was always true1A

1259 self.source_editor.SetCurrentPos(position) 1A

1260 self.source_editor.SetSelection(position, position + len(txt)) 1A

1261 self.source_editor.ScrollToLine(self.source_editor.GetCurrentLine()) 1A

1262 self._search_field_notification.SetLabel('') 1A

1263 else: 

1264 self._search_field_notification.SetLabel(_('No matches found.')) 

1265 

1266 def locate_tree_item(self, item): 

1267 """ item is object received from message """ 

1268 if not self.plugin.jump: 

1269 self.plugin.jump = True 

1270 return 

1271 from wx.stc import STC_FIND_REGEXP 

1272 search_end = len(self.source_editor.utf8_text) 

1273 section_start = 0 

1274 name_to_locate = r'^'+item.name+r'.*$' 

1275 position = self.source_editor.FindText(section_start, search_end, name_to_locate, STC_FIND_REGEXP) 

1276 # print(f"DEBUG: TextEditor locate_tree_item name_to_locate={name_to_locate} position={position}\n" 

1277 # f"curpos={self._position} {self.is_saving=}") 

1278 if position[0] != -1: 

1279 # DEBUG: Make colours configurable? 

1280 self.source_editor.SetSelBackground(True, Colour('orange')) 

1281 self.source_editor.SetSelForeground(True, Colour('white')) 

1282 self.source_editor.SetFocus() 

1283 self.source_editor.GotoPos(position[1]+1) 

1284 self.source_editor.LineScrollUp() 

1285 self.source_editor.SetCurrentPos(position[1]) 

1286 self.source_editor.SetAnchor(position[0]) 

1287 self.source_editor.SetSelection(position[0], position[1]) 

1288 self.source_editor.SetFocusFromKbd() 

1289 self.source_editor_parent.SetFocus() 

1290 self.source_editor.Update() 

1291 else: # Text was not found, so it is the Test Suite name. Go to line zero. 

1292 self.source_editor.SetFocus() 

1293 self.source_editor.GotoPos(1) 

1294 self.source_editor.LineScrollUp() 

1295 self.source_editor.SetCurrentPos(1) 

1296 self.source_editor.SetAnchor(0) 

1297 self.source_editor.SetSelection(0, self.source_editor.GetLineEndPosition(0)) 

1298 self.source_editor.SetFocusFromKbd() 

1299 self.source_editor_parent.SetFocus() 

1300 self.source_editor.Update() 

1301 

1302 def words_cache(self, doc_size: int): 

1303 if doc_size != self.doc_size: # DEBUG The initial idea was to not update words list if no changes in doc 1ac

1304 words_list = self.collect_words(self.source_editor.GetText()) 1ac

1305 self._words_cache.update(words_list) 1ac

1306 self.doc_size = doc_size 1ac

1307 return sorted(self._words_cache) 1ac

1308 

1309 @staticmethod 

1310 def var_strip(txt:str): 

1311 for symb in '$&@%{[()]}': 1ac

1312 txt = txt.strip(symb) 1ac

1313 return txt 1ac

1314 

1315 def collect_words(self, text: str): 

1316 if not text: 1ac

1317 return [''] 

1318 words = set() 1ac

1319 words_ = list(text.replace('\r\n', ' ').replace('\n', ' ').split(' ')) 1ac

1320 for w in words_: 1ac

1321 wl = self.var_strip(w) 1ac

1322 if wl and wl[0].isalpha(): 1ac

1323 words.add(w) 1ac

1324 

1325 # print(f"DEBUG: texteditor.py SourceEditor collect_words returning {words=}") 

1326 return sorted(words) 1ac

1327 

1328 def on_content_assist(self, event): 

1329 """ 

1330 Produces an Auto Complete Suggestions list, from Scintilla. Based on selected content or nearby text. 

1331 Always add actual imported libraries and resources keywords and BuiltIn. 

1332 :param event: Not used 

1333 :return: 

1334 """ 

1335 if not (self.is_focused() and self.plugin.is_focused()): # DEBUG was typing text when at Grid Editor 1335 ↛ 1336line 1335 didn't jump to line 1336 because the condition on line 1335 was never true1c

1336 return 

1337 __ = event 1c

1338 """ 1c

1339 if self._showing_list: 

1340 self._showing_list = False # Avoid double calls 

1341 return 

1342 """ 

1343 self.store_position() 1c

1344 selected = self.source_editor.get_selected_or_near_text() 1c

1345 # print(f"DEBUG: texteditor.py SourceEditor SELECTION selected = {selected} is type={type(selected)}") 

1346 self.set_editor_caret_position() 1c

1347 # Next is for the unit tests when the did not used open to get data: 

1348 if not self._suggestions: 1348 ↛ 1349line 1348 didn't jump to line 1349 because the condition on line 1348 was never true1c

1349 self._controller_for_context = DummyController(self._data.wrapper_data, self._data.wrapper_data) 

1350 self._suggestions = SuggestionSource(self.plugin, self._controller_for_context) 

1351 self._suggestions.update_from_local(self.words_cache(self.source_editor.GetLineCount()), self.language) 1c

1352 sugs = set() 1c

1353 if selected: 1353 ↛ 1368line 1353 didn't jump to line 1368 because the condition on line 1353 was always true1c

1354 selected = list(selected) 1c

1355 selected = ([selected[0], selected[-1].split(' ')[-1]] if selected[0] != selected[-1].split(' ')[-1] 1c

1356 else [selected[0]]) 

1357 for start in selected: 1c

1358 found = [] 1c

1359 for s in self._suggestions.get_suggestions(start): 1c

1360 if hasattr(s, 'name'): 1360 ↛ 1361line 1360 didn't jump to line 1361 because the condition on line 1360 was never true1c

1361 found.append(s.name) 

1362 else: 

1363 found.append(s) 1c

1364 sugs.update(found) 1c

1365 # print(f"DEBUG: texteditor.py SourceEditor on_content_assist FIRST SUGGESTION suggestions = {sugs}\n") 

1366 # DEBUG: Here, if sugs is still [], then we can get all words from line and repeat suggestions 

1367 # In another evolution, we can use database of words by frequency (considering future by project db) 

1368 sel = [s for s in selected] if selected else [''] 1c

1369 entry_word = sel[0].split('.')[-1].strip() if '.' in sel[0] else sel[0] 1c

1370 length_entered = len(entry_word) # Because Libraries prefixed 1c

1371 # print(f"DEBUG: texteditor.py SourceEditor on_content_assist selection = {sel}") 

1372 # if sel[0] == '': 

1373 # The case when we call Ctl+Space in empty line o always add suggestions 

1374 # for start in sel: 

1375 # print(f"DEBUG: texteditor.py SourceEditor on_content_assist selection = {sel}") 

1376 if sel[0] == '': 1376 ↛ 1377line 1376 didn't jump to line 1377 because the condition on line 1376 was never true1c

1377 found = [] 

1378 for s in self._suggestions.get_suggestions(''): 

1379 if hasattr(s, 'name'): 

1380 found.append(s.name) 

1381 else: 

1382 found.append(s) 

1383 sugs.update(found) 

1384 if len(sel[0]) >= 2: # Search again with and without variable prefixes 1384 ↛ 1405line 1384 didn't jump to line 1405 because the condition on line 1384 was always true1c

1385 found = [] 1c

1386 for start in sel: 1c

1387 if start[0] in "$&@%{[()]}": 1387 ↛ 1388line 1387 didn't jump to line 1388 because the condition on line 1387 was never true1c

1388 text = self.var_strip(start) 

1389 for s in self._suggestions.get_suggestions(text): 

1390 if hasattr(s, 'name'): 

1391 found.append(s.name) 

1392 else: 

1393 found.append(s) 

1394 else: 

1395 for v in ['${', '&{', '@{', '$', '[', '(']: 1c

1396 text = f'{v}{start}' 1c

1397 for s in self._suggestions.get_suggestions(text): 1c

1398 if hasattr(s, 'name'): 1398 ↛ 1399line 1398 didn't jump to line 1399 because the condition on line 1398 was never true1c

1399 found.append(s.name) 

1400 else: 

1401 found.append(s) 1c

1402 sugs.update(found) 1c

1403 # print(f"DEBUG: texteditor.py SourceEditor on_content_assist VARIABLES SEARCH selection = {sel}\n" 

1404 # f"sugs={sugs}") 

1405 if len(sugs) > 0: 1405 ↛ 1409line 1405 didn't jump to line 1409 because the condition on line 1405 was always true1c

1406 # sugs = [s for s in sugs if s != ''] 

1407 if '' in sugs: 1407 ↛ 1408line 1407 didn't jump to line 1408 because the condition on line 1407 was never true1c

1408 sugs.remove('') 

1409 suggestions=";".join(sorted(sugs)) 1c

1410 # print(f"DEBUG: texteditor.py SourceEditor on_content_assist BEFORE SHOW LIST suggestions = {suggestions}\n" 

1411 # f" size={len(suggestions)} cache size={len(self._words_cache)}") 

1412 if len(suggestions) > 0: # Consider using contentassist as in Grid Editor 1412 ↛ 1424line 1412 didn't jump to line 1424 because the condition on line 1412 was always true1c

1413 self.source_editor.AutoCompSetDropRestOfWord(False) 1c

1414 self.source_editor.AutoCompSetFillUps('=') 1c

1415 self.source_editor.AutoCompSetIgnoreCase(True) 1c

1416 self.source_editor.AutoCompSetOrder(0) 1c

1417 self.source_editor.AutoCompSetSeparator(ord(';')) 1c

1418 self.source_editor.AutoCompShow(length_entered, suggestions) 1c

1419 self.autocomp_pos = self.source_editor.AutoCompPosStart() 1c

1420 self._showing_list = True 1c

1421 # DEBUG: self.set_editor_caret_position() 

1422 # Needs proper calculation of position to delete already suggestion. 

1423 # This will be done when selection is effective at on_key_down 

1424 """ 1c

1425 else: 

1426 # self.set_editor_caret_position() # Restore selected text and caret 

1427 self.source_editor.SetInsertionPoint(self._position) # We should know if list was canceled or value change 

1428 """ 

1429 

1430 def open(self, data): 

1431 self.reset() 1ac

1432 self._data = data 1ac

1433 if hasattr(self._data, '_doc_language') and self._data._doc_language is not None and len(self._data._doc_language) > 0: 1ac

1434 self.language = self._data._doc_language 

1435 elif hasattr(self._data, '_language') and self._data._language is not None and len(self._data._language) > 0: 1435 ↛ 1436line 1435 didn't jump to line 1436 because the condition on line 1435 was never true1ac

1436 self.language = self._data._language 

1437 else: 

1438 self.language = ['en'] 1ac

1439 # print(f"DEBUG: texteditor.py SourceEditor open ENTER language={self.language} curpos={self._position}") 

1440 try: 1ac

1441 if hasattr(self._data, 'wrapper_data'): 1ac

1442 if isinstance(self._data.wrapper_data, ResourceFileController): 1442 ↛ 1443line 1442 didn't jump to line 1443 because the condition on line 1442 was never true

1443 self._controller_for_context = DummyController(self._data.wrapper_data, self._data.wrapper_data) 

1444 else: 

1445 self._controller_for_context = self._data.wrapper_data.tests[0] 

1446 elif isinstance(self._data, ResourceFileController): 1446 ↛ 1447line 1446 didn't jump to line 1447 because the condition on line 1446 was never true1ac

1447 self._controller_for_context = self._data 

1448 else: 

1449 self._controller_for_context = self._data.tests[0] 1ac

1450 self._suggestions = SuggestionSource(self.plugin, self._controller_for_context) 1ac

1451 except IndexError: # It is a new project, no content yet 

1452 self._controller_for_context = DummyController(self._data.wrapper_data, self._data.wrapper_data) 

1453 self._suggestions = SuggestionSource(self.plugin, self._controller_for_context) 

1454 if hasattr(self.plugin, 'datafile') and self.plugin.datafile: 1454 ↛ 1455line 1454 didn't jump to line 1455 because the condition on line 1454 was never true1ac

1455 self.datafile = self.plugin.datafile 

1456 # else: 

1457 # print(f"DEBUG: Text Editor open NOT DATAFILE path={self.datafile_controller.source}") 

1458 if not self.source_editor: 1458 ↛ 1459line 1458 didn't jump to line 1459 because the condition on line 1458 was never true1ac

1459 self._stored_text = self._data.content 

1460 self._create_editor_text_control(text=self._stored_text, language=self.language) 

1461 else: 

1462 self.source_editor.set_language(self.language) 1ac

1463 if hasattr(self._data, 'content'): # Special case for unit test 1ac

1464 self.source_editor.set_text(self._data.content) 

1465 self.set_editor_caret_position() 1ac

1466 wx.CallAfter(self.plugin.statusbar_message, f'{_("Source: ")}{self._controller_for_context.source}', 4000) 1ac

1467 self._words_cache.clear() 1ac

1468 self._suggestions.update_from_local(self.words_cache(self.source_editor.GetLineCount()), self.language) 1ac

1469 

1470 def selected(self, data): 

1471 if not self.source_editor: 1471 ↛ 1472line 1471 didn't jump to line 1472 because the condition on line 1471 was never true

1472 self._create_editor_text_control(text=self._stored_text, language=self.language) 

1473 else: 

1474 self.source_editor.set_language(self.language) 

1475 if self._data == data: 1475 ↛ 1476line 1475 didn't jump to line 1476 because the condition on line 1475 was never true

1476 return 

1477 self.open(data) 

1478 

1479 def _add_auto_indent(self, line: str): 

1480 lenline = len(line) 

1481 linenum = self.source_editor.GetCurrentLine() 

1482 idx = 0 

1483 while idx < lenline and line[idx] == ' ': 

1484 idx += 1 

1485 tsize = idx // self.tab_size 

1486 block_line = line.strip().split(' ')[0] 

1487 if idx < lenline and (block_line in INDENTED_START): 

1488 tsize += 1 

1489 elif linenum > 0 and tsize == 0: # Advance if first task/test case or keyword 

1490 prevline = self.source_editor.GetLine(linenum - 1).lower() 

1491 if prevline.startswith("**") and not ("variables" in prevline or "settings" in prevline): 

1492 tsize = 1 

1493 elif line.strip().startswith("END"): 

1494 pos = self.source_editor.GetCurrentPos() 

1495 self.source_editor.SetCurrentPos(pos) 

1496 self.source_editor.SetSelection(pos, pos) 

1497 self.source_editor.NewLine() 

1498 while tsize > 0: 

1499 self.write_ident() 

1500 tsize -= 1 

1501 

1502 def auto_indent(self): 

1503 line, _ = self.source_editor.GetCurLine() 

1504 lenline = len(line) 

1505 if lenline > 0: 

1506 self._add_auto_indent(line) 

1507 else: 

1508 self.source_editor.NewLine() 

1509 pos = self.source_editor.GetCurrentLine() 

1510 self.source_editor.SetCurrentPos(self.source_editor.GetLineEndPosition(pos)) 

1511 self.store_position() 

1512 

1513 def deindent_block(self): 

1514 start, end = self.source_editor.GetSelection() 

1515 ini_line = self.source_editor.LineFromPosition(start) 

1516 end_line = self.source_editor.LineFromPosition(end) 

1517 self.source_editor.SelectNone() 

1518 line = ini_line 

1519 inconsistent = False 

1520 self.source_editor.BeginUndoAction() 

1521 while line <= end_line: 

1522 inconsistent = False 

1523 pos = self.source_editor.PositionFromLine(line) 

1524 self.source_editor.SetCurrentPos(pos) 

1525 self.source_editor.SetSelection(pos, pos) 

1526 self.source_editor.SetInsertionPoint(pos) 

1527 content = self.source_editor.GetRange(pos, pos + self.tab_size) 

1528 if content == (' ' * self.tab_size): 

1529 self.source_editor.DeleteRange(pos, self.tab_size) 

1530 line += 1 

1531 else: 

1532 inconsistent = True 

1533 break 

1534 self.source_editor.EndUndoAction() 

1535 if inconsistent: 

1536 self.source_editor.Undo() 

1537 return 

1538 tnew_start = self.source_editor.GetLineEndPosition(ini_line) - len(self.source_editor.GetLine(ini_line)) + 1 

1539 tnew_end = self.source_editor.GetLineEndPosition(end_line) 

1540 self.source_editor.SetSelection(tnew_start, tnew_end) 

1541 self.source_editor.SetCurrentPos(tnew_end) 

1542 self.source_editor.SetAnchor(tnew_start) 

1543 

1544 def _calc_indent_size(self, text: str): 

1545 lenline = len(text) 

1546 idx = 0 

1547 block_line = text.strip().split(' ')[0] 

1548 while idx < lenline and text[idx] == ' ': 

1549 idx += 1 

1550 tsize = idx // self.tab_size 

1551 if idx < lenline and (block_line in INDENTED_START): 

1552 tsize += 1 

1553 elif tsize == 0: 

1554 text = text.lower() 

1555 if text.startswith("**"): 

1556 if not ("variables" in text or "settings" in text): 

1557 tsize = 1 

1558 return tsize 

1559 

1560 def deindent_line(self, line): 

1561 self.indent_line(line, reverse=True) 

1562 

1563 def indent_line(self, line, reverse=False): 

1564 last_line = self.source_editor.GetLineCount() 1B

1565 if line > 0 and not reverse: 1565 ↛ 1566line 1565 didn't jump to line 1566 because the condition on line 1565 was never true1B

1566 pos = self.source_editor.PositionFromLine(line) 

1567 text = self.source_editor.GetLine(line) 

1568 lenline = len(text) 

1569 if lenline > 0: 

1570 self.source_editor.SetCurrentPos(pos) 

1571 self.source_editor.SetSelection(pos, pos) 

1572 self.source_editor.SetInsertionPoint(pos) 

1573 self.source_editor.InsertText(pos, ' ' * self.tab_size) 

1574 elif line < last_line and reverse: 1574 ↛ 1575line 1574 didn't jump to line 1575 because the condition on line 1574 was never true1B

1575 pos = self.source_editor.PositionFromLine(line) 

1576 text = self.source_editor.GetLine(line) 

1577 idx = self.first_non_space(text) 

1578 if idx >= self.tab_size: 

1579 self.source_editor.DeleteRange(pos, self.tab_size) 

1580 

1581 def indent_block(self): 

1582 # print(f"DEBUG: TextEditor SourceEdior ident_block focus={self.is_focused()}") 

1583 start, end = self.source_editor.GetSelection() 

1584 ini_line = self.source_editor.LineFromPosition(start) 

1585 end_line = self.source_editor.LineFromPosition(end) 

1586 self.source_editor.SelectNone() 

1587 line = ini_line 

1588 while line <= end_line: 

1589 pos = self.source_editor.PositionFromLine(line) 

1590 self.source_editor.SetCurrentPos(pos) 

1591 self.source_editor.SetSelection(pos, pos) 

1592 self.source_editor.SetInsertionPoint(pos) 

1593 self.write_ident() 

1594 # print(f"DEBUG: TextEditor SourceEdior ident_block loop line={line}") 

1595 line += 1 

1596 tnew_start = self.source_editor.GetLineEndPosition(ini_line) - len(self.source_editor.GetLine(ini_line)) + 1 

1597 tnew_end = self.source_editor.GetLineEndPosition(end_line) 

1598 self.source_editor.SetSelection(tnew_start, tnew_end) 

1599 self.source_editor.SetCurrentPos(tnew_end) 

1600 self.source_editor.SetAnchor(tnew_start) 

1601 

1602 def write_ident(self): 

1603 spaces = ' ' * self.tab_size 

1604 self.source_editor.WriteText(spaces) 

1605 

1606 def reset(self): 

1607 if self._data and (hasattr(self._data, 'wrapper_data') and not self._data.wrapper_data.is_dirty): 1ac

1608 self.mark_file_dirty(False) 1c

1609 

1610 def content_save(self, **args): 

1611 self.store_position() 

1612 # print(f"DEBUG: TextEditor.py SourceEditor content_save curpos={self._position}") 

1613 if self.dirty: 

1614 # print(f"DEBUG: TextEditor.py SourceEditor content_save content={self.source_editor.utf8_text}\n" 

1615 # f"self.language={self.language} data={self._data}" 

1616 # f" calling validate_and_update with lang={args['lang']}") 

1617 self.plugin.jump = False 

1618 if not self._data_validator.validate_and_update(self._data, self.source_editor.utf8_text, 

1619 lang=self.language): # args['lang'] 

1620 self.plugin.jump = True 

1621 return False 

1622 return True 

1623 

1624 """ 

1625 # DEBUG:  

1626 def direct_save(self, text): 

1627 print(f"DEBUG: direct_save path={self.datafile_controller.source}") 

1628 f = open(self.datafile_controller.source, "wb") 

1629 try: 

1630 f.write(text) 

1631 self._mark_file_dirty(False) 

1632 print(f"DEBUG: direct_save Content:\n{text}") 

1633 except Exception as e: 

1634 raise e 

1635 finally: 

1636 f.close() 

1637 """ 

1638 # Callbacks taken from __init__.py 

1639 def on_undo(self, event): 

1640 __ = event 

1641 self.undo() 

1642 

1643 def on_redo(self, event): 

1644 __ = event 

1645 self.redo() 

1646 

1647 def on_cut(self, event): 

1648 __ = event 

1649 self.cut() 

1650 

1651 def on_copy(self, event): 

1652 __ = event 

1653 self.copy() 

1654 

1655 def on_paste(self, event): 

1656 __ = event 

1657 self.paste() 

1658 

1659 @staticmethod 

1660 def on_insert(event): 

1661 __ = event 

1662 # print(f"DEBUG: TextEditor called on_insert event={event}\n TO BE IMPLEMENTED") 

1663 # self.insert_row() 

1664 

1665 @staticmethod 

1666 def on_delete(self, event=None): 

1667 """ Not used """ 

1668 

1669 def on_insert_cells(self, event): 

1670 self.insert_cell(event) 

1671 

1672 def on_delete_cells(self, event): 

1673 self.delete_cell(event) 

1674 

1675 def on_comment_rows(self, event): 

1676 self.execute_comment(event) 

1677 

1678 def on_uncomment_rows(self, event): 

1679 self.execute_uncomment(event) 

1680 

1681 def on_sharp_comment_rows(self, event): 

1682 self.execute_sharp_comment(event) 

1683 

1684 def on_sharp_uncomment_rows(self, event): 

1685 self.execute_sharp_uncomment(event) 

1686 

1687 def on_comment_cells(self, event): 

1688 self.execute_sharp_comment(event) 

1689 

1690 def on_uncomment_cells(self, event): 

1691 self.execute_sharp_uncomment(event) 

1692 

1693 def on_insert_rows(self, event): 

1694 self.insert_row(event) 

1695 

1696 def on_delete_rows(self, event): 

1697 wx.CallAfter(self.delete_row, event) 

1698 

1699 def on_move_rows_up(self, event): 

1700 self.move_row_up(event) 

1701 

1702 def on_move_rows_down(self, event): 

1703 self.move_row_down(event) 

1704 

1705 def on_content_assistance(self, event): 

1706 self.on_content_assist(event) 

1707 

1708 def on_key(self, *args): 

1709 """ Intentional override """ 

1710 pass 

1711 

1712 def cut(self): 

1713 self.source_editor.Cut() 

1714 self.mark_file_dirty(self.source_editor.GetModify()) 

1715 

1716 def copy(self): 

1717 self.source_editor.Copy() 

1718 

1719 def paste(self): 

1720 focus = wx.Window.FindFocus() 

1721 if focus == self.source_editor: 

1722 self.source_editor.Paste() 

1723 self.mark_file_dirty(self.source_editor.GetModify()) 

1724 

1725 def select_all(self): 

1726 self.source_editor.SelectAll() 

1727 

1728 def undo(self): 

1729 self.source_editor.Undo() 

1730 self.store_position() 

1731 self.mark_file_dirty(self.source_editor.GetModify()) # self._dirty == 1 and 

1732 

1733 def redo(self): 

1734 self.source_editor.Redo() 

1735 self.store_position() 

1736 self.mark_file_dirty(self.source_editor.GetModify()) 

1737 

1738 def remove_and_store_state(self): 

1739 if self.source_editor: 1739 ↛ exitline 1739 didn't return from function 'remove_and_store_state' because the condition on line 1739 was always true1D

1740 self.store_position() 1D

1741 self._stored_text = self.source_editor.GetText() 1D

1742 

1743 def _create_editor_text_control(self, text=None, language=None): 

1744 self.source_editor = RobotDataEditor(self, language=language) 

1745 self.Sizer.add_expanding(self.source_editor) 

1746 self.Sizer.Layout() 

1747 if text is not None: 1747 ↛ 1748line 1747 didn't jump to line 1748 because the condition on line 1747 was never true

1748 self.source_editor.set_text(text) 

1749 self._kw_doc_timer = wx.Timer(self.source_editor) 

1750 self.source_editor.Bind(wx.EVT_KEY_DOWN, self.on_key_down) 

1751 self.source_editor.Bind(wx.EVT_CHAR, self.on_char) 

1752 self.source_editor.Bind(wx.EVT_KEY_UP, self.on_editor_key) 

1753 self.source_editor.Bind(wx.EVT_MOTION, self.on_mouse_motion) 

1754 self.source_editor.Bind(wx.EVT_KILL_FOCUS, self.LeaveFocus) 

1755 self.source_editor.Bind(wx.EVT_SET_FOCUS, self.GetFocus) 

1756 self.source_editor.Bind(wx.EVT_MENU, self.on_menu) 

1757 # DEBUG: Add here binding for keyword help 

1758 

1759 def on_menu(self, event): 

1760 m_id=event.GetId() 

1761 if m_id in (12, 14): # Cut and Paste 

1762 self.mark_file_dirty(True) # DEBUG: Forcing dirty, even if it may not be 

1763 event.Skip() 

1764 

1765 def LeaveFocus(self, event): 

1766 __ = event 1ak

1767 self.source_editor.hide_kw_doc() 1ak

1768 self.source_editor.AcceptsFocusFromKeyboard() 1ak

1769 self.store_position() 1ak

1770 self.source_editor.SetCaretPeriod(0) 1ak

1771 

1772 def GetFocus(self, event): 

1773 self.source_editor.SetFocus() 

1774 self.source_editor.AcceptsFocusFromKeyboard() 

1775 self.source_editor.SetCaretPeriod(500) 

1776 if self._position: 1776 ↛ 1777line 1776 didn't jump to line 1777 because the condition on line 1776 was never true

1777 self.set_editor_caret_position() 

1778 if event: 1778 ↛ exitline 1778 didn't return from function 'GetFocus' because the condition on line 1778 was always true

1779 event.Skip() 

1780 

1781 def revert(self): 

1782 self.reset() 

1783 self.source_editor.Undo() 

1784 # self.source_editor.set_text(self._data.content) 

1785 

1786 def on_editor_key(self, event): 

1787 # print(f"DEBUG: TextEditor on_editor_key event={event} focus={self.is_focused()}") 

1788 if not self.is_focused(): 

1789 event.Skip() 

1790 return 

1791 keycode = event.GetKeyCode() 

1792 keyvalue = event.GetUnicodeKey() 

1793 if keycode in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: 

1794 self.mark_file_dirty(self.source_editor.GetModify()) 

1795 return 

1796 if keyvalue == wx.WXK_NONE and keycode in [wx.WXK_CONTROL, wx.WXK_RAW_CONTROL]: 

1797 self.source_editor.hide_kw_doc() 

1798 elif keycode == wx.WXK_DELETE or (keyvalue != wx.WXK_NONE and keycode > keyvalue): 

1799 # DEBUG on Windows we only get here, single Text Editor 

1800 selected = self.source_editor.GetSelection() 

1801 if selected[0] == selected[1]: 

1802 pos = self.source_editor.GetInsertionPoint() 

1803 if pos != self.source_editor.GetLastPosition(): 

1804 self.source_editor.DeleteRange(selected[0], 1) 

1805 else: 

1806 self.source_editor.DeleteRange(selected[0], selected[1] - selected[0]) 

1807 if self.is_focused(): # DEBUG and keycode != wx.WXK_CONTROL and keyvalue >= ord(' ') and self.dirty: 

1808 self.mark_file_dirty(self.source_editor.GetModify()) 

1809 event.Skip() 

1810 

1811 def on_key_down(self, event): 

1812 """ 

1813 Some events are also in registered actions, because they are caught by kweditor before reaching here. 

1814 When we use on the Text Editor (Grid Editor plugin disables), we need to catch these keys. This is the 

1815 case of Ctrl-(Shift)-3 and Ctrl-(Shift)-4. 

1816 

1817 :param event: 

1818 :return: 

1819 """ 

1820 # print(f"DEBUG: TextEditor on_key_down event={event} focus={self.is_focused()}") 

1821 if not self.is_focused(): 

1822 event.Skip() 

1823 return 

1824 keycode = event.GetUnicodeKey() 

1825 raw_key = event.GetKeyCode() 

1826 # print(f"DEBUG: TextEditor on_key_down event={event} raw_key={raw_key} wx.WXK_C ={wx.WXK_CONTROL}") 

1827 if event.GetKeyCode() == wx.WXK_DELETE: 

1828 self.mark_file_dirty(self.source_editor.GetModify()) 

1829 return 

1830 if raw_key != wx.WXK_CONTROL: # We need to clear doc as soon as possible 

1831 self.source_editor.hide_kw_doc() 

1832 if event.GetKeyCode() == wx.WXK_TAB and not event.ControlDown() and not event.ShiftDown(): 

1833 if self._showing_list: # Allows to use Tab for keyword selection 

1834 self._showing_list = False 

1835 # wx.CallAfter(self.write_ident) # DEBUG: Make this configurable? 

1836 event.Skip() 

1837 self.mark_file_dirty(self.source_editor.GetModify()) 

1838 return 

1839 selected = self.source_editor.GetSelection() 

1840 if selected[0] == selected[1]: 

1841 self.write_ident() 

1842 else: 

1843 self.indent_block() 

1844 self.mark_file_dirty(self.source_editor.GetModify()) 

1845 return 

1846 elif event.GetKeyCode() == wx.WXK_TAB and event.ShiftDown(): 

1847 selected = self.source_editor.GetSelection() 

1848 if selected[0] == selected[1]: 

1849 pos = self.source_editor.GetCurrentPos() 

1850 self.source_editor.SetCurrentPos(max(0, pos - self.tab_size)) 

1851 self.store_position() 

1852 if not event.ControlDown(): # No text selection 

1853 pos = self.source_editor.GetCurrentPos() 

1854 self.source_editor.SetSelection(pos, pos) 

1855 else: 

1856 self.deindent_block() 

1857 self.mark_file_dirty(self.source_editor.GetModify()) 

1858 return 

1859 elif event.GetKeyCode() in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: 

1860 if not self._showing_list: 

1861 self.auto_indent() 

1862 else: 

1863 self._showing_list = False 

1864 # wx.CallAfter(self.write_ident) # DEBUG: Make this configurable? 

1865 event.Skip() 

1866 self.mark_file_dirty(self.source_editor.GetModify()) 

1867 return 

1868 elif keycode in (ord('1'), ord('2'), ord('5')) and event.ControlDown(): 

1869 self.execute_variable_creator(list_variable=(keycode == ord('2')), 

1870 dict_variable=(keycode == ord('5'))) 

1871 self.store_position() 

1872 self.mark_file_dirty(self.source_editor.GetModify()) 

1873 elif ((not IS_WINDOWS and not IS_MAC and keycode in (ord('v'), ord('V')) 

1874 or keycode in (ord('d'), ord('D'))) and event.ControlDown() and not event.ShiftDown()): 

1875 # We need to ignore this in Linux, because it does double-action 

1876 # We need to ignore Ctl-D because Scintilla does Duplicate line 

1877 self.mark_file_dirty(self.source_editor.GetModify()) 

1878 return 

1879 elif keycode in (ord('g'), ord('G')) and event.ControlDown(): 

1880 if event.ShiftDown(): 

1881 wx.CallAfter(self.on_find_backwards, event) 

1882 else: 

1883 wx.CallAfter(self.on_find, event) 

1884 return 

1885 elif event.ControlDown() and raw_key == wx.WXK_CONTROL: 

1886 # This must be the last branch to activate actions before doc 

1887 # DEBUG: coords = self._get_screen_coordinates() 

1888 self.source_editor.show_kw_doc() 

1889 self.mark_file_dirty(self.source_editor.GetModify()) 

1890 event.Skip() 

1891 

1892 # These commands are duplicated by global actions 

1893 """ DEBUG 

1894 else: 

1895 self.source_editor.hide_kw_doc() 

1896 elif keycode == ord('D') and event.ControlDown(): 

1897 if event.ShiftDown(): 

1898 self.delete_cell(event) 

1899 else: 

1900 self.delete_row(event) 

1901 elif keycode == ord('3') and event.ControlDown(): 

1902 if event.ShiftDown(): 

1903 self.execute_sharp_comment(event) 

1904 else: 

1905 self.execute_comment(event) 

1906 elif keycode == ord('4') and event.ControlDown(): 

1907 if event.ShiftDown(): 

1908 self.execute_sharp_uncomment(event) 

1909 else: 

1910 self.execute_uncomment(event) 

1911 """ 

1912 

1913 @staticmethod 

1914 def _get_screen_coordinates(): 

1915 point = wx.GetMousePosition() 

1916 point.x += 25 

1917 point.y += 25 

1918 return point 

1919 

1920 def on_char(self, event): 

1921 if not self.is_focused(): 

1922 self.GetFocus(None) 

1923 keycode = event.GetUnicodeKey() 

1924 if chr(keycode) in ['[', '{', '(', "'", '\"', '`']: 

1925 self.execute_enclose_text(chr(keycode)) 

1926 self.store_position() 

1927 else: 

1928 event.Skip() 

1929 

1930 def on_mouse_motion(self, event): 

1931 if event.CmdDown(): 1931 ↛ 1932line 1931 didn't jump to line 1932 because the condition on line 1931 was never true1kG

1932 self._kw_doc_timer.Stop() 

1933 # self.source_editor.show_kw_doc() 

1934 else: 

1935 if self.old_information_popup != self.source_editor._information_popup: 1935 ↛ 1936line 1935 didn't jump to line 1936 because the condition on line 1935 was never true1kG

1936 self.source_editor.hide_kw_doc() 

1937 self.old_information_popup = self.source_editor._information_popup 

1938 self._start_kw_doc_timer() 

1939 event.Skip() 1kG

1940 

1941 def _start_kw_doc_timer(self): 

1942 self._kw_doc_timer.Start(1000, True) 

1943 

1944 def execute_variable_creator(self, list_variable=False, dict_variable=False): 

1945 from_, to_ = self.source_editor.GetSelection() 

1946 text = self.source_editor.GetSelectedText() 

1947 size = len(bytes(text, encoding='utf-8')) 

1948 to_ = from_ + size 

1949 if list_variable: 

1950 symbol = '@' 

1951 elif dict_variable: 

1952 symbol = '&' 

1953 else: 

1954 symbol = '$' 

1955 if size == 0: 

1956 self.source_editor.SetInsertionPoint(to_) 

1957 self.source_editor.InsertText(from_, self._variable_creator_value(symbol)) 

1958 self.source_editor.SetSelection(from_ + 2, from_ + 2,) 

1959 self.source_editor.SetInsertionPoint(from_ + 2) 

1960 else: 

1961 self.source_editor.DeleteRange(from_, size) 

1962 self.source_editor.SetInsertionPoint(from_) 

1963 self.source_editor.ReplaceSelection(self._variable_creator_value(symbol, text)) 

1964 self.source_editor.SetSelection(from_ + size + 2, from_ + size + 2) 

1965 self.source_editor.SetInsertionPoint(from_ + size + 2) 

1966 

1967 @staticmethod 

1968 def _variable_creator_value(symbol, value=''): 

1969 return symbol + '{' + value + '}' 

1970 

1971 def execute_enclose_text(self, keycode): 

1972 from_, to_ = self.source_editor.GetSelection() 

1973 text = self.source_editor.GetSelectedText() 

1974 size = len(bytes(text, encoding='utf-8')) 

1975 to_ = from_ + size 

1976 if size == 0: 

1977 self.source_editor.SetInsertionPoint(to_) 

1978 self.source_editor.InsertText(from_, self._enclose_text(keycode)) 

1979 pos = self.source_editor.GetCurrentPos() 

1980 self.source_editor.SetSelection(pos + 1, pos + 1) 

1981 else: 

1982 self.source_editor.DeleteRange(from_, size) 

1983 self.source_editor.SetInsertionPoint(from_) 

1984 self.source_editor.ReplaceSelection(self._enclose_text(keycode, text)) 

1985 self.source_editor.SetSelection(from_ + 1, from_ + size + 1) 

1986 

1987 @staticmethod 

1988 def _enclose_text(open_symbol, value=''): 

1989 if open_symbol == '[': 

1990 close_symbol = ']' 

1991 elif open_symbol == '{': 

1992 close_symbol = '}' 

1993 elif open_symbol == '(': 

1994 close_symbol = ')' 

1995 else: 

1996 close_symbol = open_symbol 

1997 return open_symbol + value + close_symbol 

1998 

1999 def _prepare_selection(self, start, end): 

2000 new_end_line = end_line = self.source_editor.LineFromPosition(end) 1q

2001 last_line = self.source_editor.GetLineCount() 1q

2002 # get the next row content 

2003 if end_line + 1 < last_line - 3: 2003 ↛ 2004line 2003 didn't jump to line 2004 because the condition on line 2003 was never true1q

2004 rowbelow = self.source_editor.GetLine(end_line + 1) 

2005 # exception if moving block is long assignemnt or arguments with continuation markers extend selection 

2006 is_marker = self.first_non_space_content(rowbelow) 

2007 if is_marker == '...': 

2008 new_end_line = self.find_initial_keyword(end_line, up=False) 

2009 if new_end_line != end_line: # Extend selection 2009 ↛ 2010line 2009 didn't jump to line 2010 because the condition on line 2009 was never true1q

2010 new_end_pos = self.source_editor.GetLineEndPosition(new_end_line - 1) 

2011 self.source_editor.SetSelection(start, new_end_pos - 1) 

2012 

2013 def _above_row_selection(self, new_ini_line, ini_line): 

2014 # get the previous row content 

2015 rowabove = self.source_editor.GetLine(ini_line - 1) 1q

2016 is_marker = self.first_non_space_content(rowabove) 1q

2017 if is_marker == '...': 2017 ↛ 2018line 2017 didn't jump to line 2018 because the condition on line 2017 was never true1q

2018 new_ini_line = max(1, self.find_initial_keyword(ini_line)) 

2019 self.source_editor.BeginUndoAction() 1q

2020 if new_ini_line != ini_line: 2020 ↛ 2022line 2020 didn't jump to line 2022 because the condition on line 2020 was never true1q

2021 # Move block to up new_ini_line 

2022 delta = ini_line - new_ini_line 

2023 for _ in range(0, delta): 

2024 self.source_editor.MoveSelectedLinesUp() 

2025 else: 

2026 self.source_editor.MoveSelectedLinesUp() 1q

2027 

2028 def move_row_up(self, event): 

2029 __ = event 1q

2030 start, end = self.source_editor.GetSelection() 1q

2031 new_ini_line = ini_line = self.source_editor.LineFromPosition(start) 1q

2032 if ini_line == 0: 2032 ↛ 2033line 2032 didn't jump to line 2033 because the condition on line 2032 was never true1q

2033 return 

2034 self._prepare_selection(start, end) 1q

2035 # exception if is long assignemnt or arguments with continuation markers get top line 

2036 self._above_row_selection(new_ini_line, ini_line) 1q

2037 self.source_editor.EndUndoAction() 1q

2038 # New selection 

2039 start, end = self.source_editor.GetSelection() 1q

2040 new_end_line = self.source_editor.LineFromPosition(end - 1) # One char before 1q

2041 nendpos = self.source_editor.GetLineEndPosition(new_end_line) 1q

2042 self.source_editor.SetAnchor(nendpos) 1q

2043 new_ini_line = self.source_editor.LineFromPosition(start) 1q

2044 # indentation after move 

2045 new_top = self.source_editor.GetLine(new_ini_line) 1q

2046 rowbelow = self.source_editor.GetLine(new_end_line + 1) 1q

2047 old_start = self.first_non_space(rowbelow) 1q

2048 new_start = self.first_non_space(new_top) 1q

2049 was_end_old = rowbelow[old_start:old_start+3] == 'END' 1q

2050 was_end_new = new_top[new_start:new_start+3] == 'END' 1q

2051 if new_start == old_start and was_end_old and was_end_new: 2051 ↛ 2052line 2051 didn't jump to line 2052 because the condition on line 2051 was never true1q

2052 self.deindent_line(new_end_line + 1) 

2053 self.indent_block() 

2054 elif new_start == old_start and was_end_old: 2054 ↛ 2055line 2054 didn't jump to line 2055 because the condition on line 2054 was never true1q

2055 self.indent_block() 

2056 start, end = self.source_editor.GetSelection() 1q

2057 if old_start > new_start and was_end_new: 2057 ↛ 2058line 2057 didn't jump to line 2058 because the condition on line 2057 was never true1q

2058 self.deindent_line(new_end_line + 1) 

2059 if was_end_old: 

2060 self.indent_block() 

2061 if new_start < old_start and not was_end_new: 2061 ↛ 2062line 2061 didn't jump to line 2062 because the condition on line 2061 was never true1q

2062 self.indent_block() 

2063 elif new_start > old_start: 2063 ↛ 2064line 2063 didn't jump to line 2064 because the condition on line 2063 was never true1q

2064 self.deindent_block() 

2065 

2066 def _find_up_kw(self, start:int) -> int: 

2067 if start <= 3: 

2068 return start 

2069 text = self.source_editor.GetLine(start) 

2070 is_marker = self.first_non_space_content(text) # check if selection starts with marker 

2071 if is_marker == '...': 

2072 return start 

2073 is_marker = '...' 

2074 found = False 

2075 line = start - 2 

2076 while is_marker == '...' and line > 2: 

2077 row = self.source_editor.GetLine(line) 

2078 is_marker = self.first_non_space_content(row) 

2079 if is_marker != '...': 

2080 found = True 

2081 break 

2082 line -= 1 

2083 return line if found else start - 1 

2084 

2085 def _find_down_kw(self, start:int) -> int: 

2086 last_line = self.source_editor.GetLineCount() 

2087 if start > last_line - 3: 

2088 return start 

2089 text = self.source_editor.GetLine(start) 

2090 is_marker = self.first_non_space_content(text) # check if selection starts with marker 

2091 if is_marker == '...': 

2092 return start 

2093 is_marker = '...' 

2094 found = False 

2095 line = start + 2 

2096 while is_marker == '...' and line < last_line - 2: 

2097 row = self.source_editor.GetLine(line) 

2098 is_marker = self.first_non_space_content(row) 

2099 if is_marker != '...': 

2100 found = True 

2101 break 

2102 line += 1 

2103 return line if found else start + 1 

2104 

2105 def find_initial_keyword(self, start: int, up=True): 

2106 if up: 

2107 return self._find_up_kw(start) 

2108 return self._find_down_kw(start) 

2109 

2110 def first_non_space_content(self, text): 

2111 start = self.first_non_space(text) 1lq

2112 end = self.last_non_space(text[start:]) 1lq

2113 return text[start:start+end] 1lq

2114 

2115 @staticmethod 

2116 def first_non_space(text): 

2117 idx = 0 1lq

2118 for idx in range(0, len(text)): 1lq

2119 if text[idx] != ' ': 2119 ↛ 2118line 2119 didn't jump to line 2118 because the condition on line 2119 was always true1lq

2120 break 1lq

2121 return idx 1lq

2122 

2123 @staticmethod 

2124 def last_non_space(text): 

2125 idx = 0 1lq

2126 for idx in range(0, len(text)): 1lq

2127 if text[idx] == ' ': 1lq

2128 break 1lq

2129 return idx 1lq

2130 

2131 def move_row_down(self, event): 

2132 __ = event 1l

2133 start, end = self.source_editor.GetSelection() 1l

2134 new_ini_line = ini_line = self.source_editor.LineFromPosition(start) 1l

2135 new_end_line = end_line = self.source_editor.LineFromPosition(end) 1l

2136 last_line = self.source_editor.GetLineCount() 1l

2137 # If line to move starts with ... move block without changes 

2138 top_content = self.source_editor.GetLine(ini_line) 1l

2139 # exception if is long assignement or arguments with continuation markers get below line 

2140 is_marker = self.first_non_space_content(top_content) 1l

2141 if is_marker != '...': 2141 ↛ 2143line 2141 didn't jump to line 2143 because the condition on line 2141 was always true1l

2142 new_ini_line = self._set_pos_by_marker(new_ini_line, new_end_line, end_line, last_line, start) 1l

2143 self.source_editor.BeginUndoAction() 1l

2144 if new_ini_line != ini_line: 2144 ↛ 2146line 2144 didn't jump to line 2146 because the condition on line 2144 was never true1l

2145 # Move block to down new_ini_line 

2146 delta = new_ini_line - ini_line - 1 

2147 for _ in range(0, delta): 

2148 self.source_editor.MoveSelectedLinesDown() 

2149 else: 

2150 self.source_editor.MoveSelectedLinesDown() 1l

2151 self.source_editor.EndUndoAction() 1l

2152 self.source_editor.EnsureCaretVisible() 1l

2153 # New selection 

2154 start, end = self.source_editor.GetSelection() 1l

2155 new_end_line = self.source_editor.LineFromPosition(end - 1) # One char before 1l

2156 new_ini_line = self.source_editor.LineFromPosition(start) 1l

2157 rowtop = self.source_editor.GetLine(new_ini_line - 1) # content before block 1l

2158 nendpos = self.source_editor.GetLineEndPosition(new_end_line) 1l

2159 self.source_editor.SetAnchor(nendpos) 1l

2160 # indentation after move 

2161 newstart = self.source_editor.GetLine(new_ini_line) 1l

2162 new_start = self.first_non_space(newstart) 1l

2163 old_start = self.first_non_space(rowtop) 1l

2164 was_end_old = rowtop[old_start:old_start + 3] == 'END' 1l

2165 old_size = self.last_non_space(rowtop[old_start:]) 1l

2166 was_indent_old = rowtop[old_start:old_start+old_size] in INDENTED_START 1l

2167 was_end_new = newstart[new_start:new_start + 3] == 'END' 1l

2168 if new_start == old_start and was_end_new: 2168 ↛ 2169line 2168 didn't jump to line 2169 because the condition on line 2168 was never true1l

2169 self.indent_line(new_ini_line - 1) 

2170 if new_start == old_start and was_indent_old: 2170 ↛ 2171line 2170 didn't jump to line 2171 because the condition on line 2170 was never true1l

2171 self.indent_block() 

2172 if new_start > old_start and not was_indent_old: 2172 ↛ 2173line 2172 didn't jump to line 2173 because the condition on line 2172 was never true1l

2173 self.deindent_block() 

2174 elif new_start < old_start and not was_end_new: 2174 ↛ 2175line 2174 didn't jump to line 2175 because the condition on line 2174 was never true1l

2175 self.indent_block() 

2176 if new_start > old_start and was_end_new and was_end_old: 2176 ↛ 2177line 2176 didn't jump to line 2177 because the condition on line 2176 was never true1l

2177 self.indent_line(new_ini_line - 1) 

2178 # New selection 

2179 self._set_new_selection(new_ini_line, new_end_line) 1l

2180 

2181 def _set_pos_by_marker(self, new_ini_line:int, new_end_line:int, end_line:int, last_line:int, start:int) -> int: 

2182 # get the next row content and length 

2183 rowbelow = self.source_editor.GetLine(end_line + 1) 1l

2184 if end_line + 2 < last_line - 1: 2184 ↛ 2186line 2184 didn't jump to line 2186 because the condition on line 2184 was never true1l

2185 # exception if is long assignement or arguments with continuation markers get below line 

2186 is_marker = self.first_non_space_content(rowbelow) 

2187 if is_marker == '...': 

2188 new_end_line = self.find_initial_keyword(end_line, up=False) 

2189 if new_end_line != end_line: # Extend selection 2189 ↛ 2190line 2189 didn't jump to line 2190 because the condition on line 2189 was never true1l

2190 new_end_pos = self.source_editor.GetLineEndPosition(new_end_line - 1) 

2191 self._select_anchor(start, new_end_pos - 1) 

2192 # exception if target is long assignemnt or arguments with continuation markers get end line 

2193 # get the after below row content 

2194 rowafterbelow = self.source_editor.GetLine(new_end_line + 2) # Checking if is continuation arguments 1l

2195 is_marker = self.first_non_space_content(rowafterbelow) 1l

2196 if is_marker == '...': 2196 ↛ 2197line 2196 didn't jump to line 2197 because the condition on line 2196 was never true1l

2197 new_ini_line = self.find_initial_keyword(new_end_line + 1, up=False) 

2198 return new_ini_line 1l

2199 

2200 def _set_new_selection(self, new_ini_line: int, new_end_line: int) -> None: 

2201 nstartpos = self.source_editor.PositionFromLine(new_ini_line) 1l

2202 nendpos = self.source_editor.GetLineEndPosition(new_end_line) 1l

2203 self._select_anchor(nstartpos, nendpos) 1l

2204 

2205 def _select_anchor(self, start:int, end:int) -> None: 

2206 self.source_editor.SetSelection(start, end) 1l

2207 self.source_editor.SetAnchor(start) 1l

2208 

2209 def delete_row(self, event): 

2210 __ = event 1C

2211 start, end = self.source_editor.GetSelection() 1C

2212 ini_line = self.source_editor.LineFromPosition(start) 1C

2213 self.source_editor.SelectNone() 1C

2214 if start == end: 2214 ↛ 2217line 2214 didn't jump to line 2217 because the condition on line 2214 was always true1C

2215 end_line = ini_line 1C

2216 else: 

2217 end_line = self.source_editor.LineFromPosition(end) 

2218 for _ in range(ini_line, end_line + 1): 1C

2219 self.source_editor.GotoLine(ini_line) 1C

2220 self.source_editor.LineDelete() 1C

2221 self.store_position() 1C

2222 

2223 def insert_row(self, event): 

2224 __ = event 1B

2225 start, end = self.source_editor.GetSelection() 1B

2226 ini_line = self.source_editor.LineFromPosition(start) 1B

2227 end_line = self.source_editor.LineFromPosition(end) 1B

2228 delta = end_line - ini_line 1B

2229 positionfromline = self.source_editor.PositionFromLine(ini_line) 1B

2230 self.source_editor.SelectNone() 1B

2231 self.source_editor.InsertText(positionfromline, '\n') 1B

2232 for nl in range(delta): 2232 ↛ 2233line 2232 didn't jump to line 2233 because the loop on line 2232 never started1B

2233 self.source_editor.InsertText(positionfromline + nl, '\n') 

2234 self.source_editor.SetCurrentPos(positionfromline) 1B

2235 self.source_editor.SetAnchor(positionfromline) 1B

2236 self.source_editor.GotoLine(ini_line) 1B

2237 self.indent_line(ini_line) 1B

2238 self.store_position() 1B

2239 

2240 def execute_comment(self, event): 

2241 __ = event 1x

2242 start, end = self.source_editor.GetSelection() 1x

2243 cursor = self.source_editor.GetCurrentPos() 1x

2244 ini_line = self.source_editor.LineFromPosition(start) 1x

2245 end_line = self.source_editor.LineFromPosition(end) 1x

2246 spaces = ' ' * self.tab_size 1x

2247 comment = 'Comment' + spaces 1x

2248 count = 0 1x

2249 self.source_editor.SelectNone() 1x

2250 row = ini_line 1x

2251 while row <= end_line: 1x

2252 pos = self.source_editor.PositionFromLine(row) 1x

2253 self.source_editor.SetCurrentPos(pos) 1x

2254 self.source_editor.SetSelection(pos, pos) 1x

2255 self.source_editor.SetInsertionPoint(pos) 1x

2256 line = self.source_editor.GetLine(row) 1x

2257 lenline = len(line) 1x

2258 if lenline > 0: 2258 ↛ 2263line 2258 didn't jump to line 2263 because the condition on line 2258 was always true1x

2259 idx = 0 1x

2260 while idx < lenline and line[idx] == ' ': 2260 ↛ 2261line 2260 didn't jump to line 2261 because the condition on line 2260 was never true1x

2261 idx += 1 

2262 self.source_editor.InsertText(pos + idx, comment) 1x

2263 count += 1 1x

2264 row += 1 1x

2265 new_start = start 1x

2266 new_end = end + (count * len(comment)) 1x

2267 if cursor == start: 2267 ↛ 2268line 2267 didn't jump to line 2268 because the condition on line 2267 was never true1x

2268 ini = new_start 

2269 fini = new_end 

2270 else: 

2271 ini = new_end 1x

2272 fini = new_start 1x

2273 self.source_editor.SetSelection(new_start, new_end) 1x

2274 self.source_editor.SetCurrentPos(ini) 1x

2275 self.source_editor.SetAnchor(fini) 1x

2276 self.store_position() 1x

2277 

2278 def execute_uncomment(self, event): 

2279 __ = event 1t

2280 start, end = self.source_editor.GetSelection() 1t

2281 cursor = self.source_editor.GetCurrentPos() 1t

2282 ini_line = self.source_editor.LineFromPosition(start) 1t

2283 end_line = self.source_editor.LineFromPosition(end) 1t

2284 spaces = ' ' * self.tab_size 1t

2285 comment = 'Comment' + spaces 1t

2286 commentlong = 'BuiltIn.Comment' + spaces 1t

2287 self.source_editor.SelectNone() 1t

2288 count = 0 1t

2289 row = ini_line 1t

2290 while row <= end_line: 1t

2291 pos = self.source_editor.PositionFromLine(row) 1t

2292 self.source_editor.SetCurrentPos(pos) 1t

2293 self.source_editor.SetSelection(pos, pos) 1t

2294 self.source_editor.SetInsertionPoint(pos) 1t

2295 line = self.source_editor.GetLine(row) 1t

2296 lenline = len(line) 1t

2297 if lenline > 0: 2297 ↛ 2305line 2297 didn't jump to line 2305 because the condition on line 2297 was always true1t

2298 idx = 0 1t

2299 while idx < lenline and line[idx] == ' ': 2299 ↛ 2300line 2299 didn't jump to line 2300 because the condition on line 2299 was never true1t

2300 idx += 1 

2301 if (line[idx:len(comment) + idx]).lower() == comment.lower(): 2301 ↛ 2303line 2301 didn't jump to line 2303 because the condition on line 2301 was always true1t

2302 self.source_editor.DeleteRange(pos + idx, len(comment)) 1t

2303 if (line[idx:len(commentlong) + idx]).lower() == commentlong.lower(): 2303 ↛ 2304line 2303 didn't jump to line 2304 because the condition on line 2303 was never true1t

2304 self.source_editor.DeleteRange(pos + idx, len(commentlong)) 

2305 count += 1 1t

2306 row += 1 1t

2307 new_start = start 1t

2308 new_end = end - (count * len(comment)) 1t

2309 if cursor == start: 2309 ↛ 2310line 2309 didn't jump to line 2310 because the condition on line 2309 was never true1t

2310 ini = new_start 

2311 fini = new_end 

2312 else: 

2313 ini = new_end 1t

2314 fini = new_start 1t

2315 self.source_editor.SetSelection(new_start, new_end) 1t

2316 self.source_editor.SetCurrentPos(ini) 1t

2317 self.source_editor.SetAnchor(fini) 1t

2318 self.store_position() 1t

2319 

2320 def insert_cell(self, event): 

2321 __ = event 1nmo

2322 start, end = self.source_editor.GetSelection() 1nmo

2323 ini_line = self.source_editor.LineFromPosition(start) 1nmo

2324 end_line = self.source_editor.LineFromPosition(end) 1nmo

2325 begpos = self.source_editor.PositionFromLine(ini_line) 1nmo

2326 endpos = self.source_editor.PositionFromLine(end_line + 1) 1nmo

2327 cell_no_beg = self._get_cell_no(begpos, endpos, start) 1nmo

2328 cell_pos_beg = self._get_position_of_cell(begpos, endpos, cell_no_beg) 1nmo

2329 # if there is a selection subtract 1 from endpos to circumvent cursor being on end of cell 

2330 # --> otherwise no will be next cell no 

2331 if start != end: 1nmo

2332 cell_no_end = self._get_cell_no(begpos, endpos, end - 1) 1mo

2333 else: 

2334 cell_no_end = cell_no_beg 1n

2335 # print(f"DEBUG: cell range to handle beg={cell_no_beg} end={cell_no_end}") 

2336 celltab = ' ' * self.tab_size 1nmo

2337 # If the selection spans more than one line: 

2338 if ini_line < end_line: # DEBUG: do inserts in such a way that they can be undone in 1 undo 1nmo

2339 new_start = cell_pos_beg 1m

2340 for line in range(ini_line, end_line + 1): 1m

2341 begthis = self.source_editor.PositionFromLine(line) 1m

2342 endthis = self.source_editor.PositionFromLine(line + 1) 1m

2343 cell_pos_beg = self._get_position_of_cell(begthis, endthis, cell_no_beg) 1m

2344 self.source_editor.InsertText(cell_pos_beg, celltab) 1m

2345 new_end = cell_pos_beg + (len(celltab.encode('utf-8'))) 1m

2346 elif start == end: # On a single row, no selection 1no

2347 self.source_editor.InsertText(cell_pos_beg, celltab) 1n

2348 new_start = cell_pos_beg 1n

2349 new_end = cell_pos_beg + len(celltab.encode('utf-8')) 1n

2350 else: # On a single row, with selection 

2351 cells_to_insert = cell_no_end - cell_no_beg + 1 1o

2352 # insert at once so undo handles it correct 

2353 self.source_editor.InsertText(cell_pos_beg, celltab * cells_to_insert) 1o

2354 new_start = cell_pos_beg 1o

2355 new_end = cell_pos_beg + (len(celltab.encode('utf-8')) * cells_to_insert) 1o

2356 # SetSelection and SetCurrentPos + Store_position overrule each other so only use one of them 

2357 self.source_editor.SetSelection(new_start, new_end) 1nmo

2358 # @Helio: SetAnchor overrules the SetSelection if it specifies a different start than 

2359 # SetSelection (but I left your code for now) 

2360 self.source_editor.SetAnchor(new_end) 1nmo

2361 

2362 def delete_cell(self, event): 

2363 __ = event 1p

2364 start, end = self.source_editor.GetSelection() 1p

2365 ini_line = self.source_editor.LineFromPosition(start) 1p

2366 end_line = self.source_editor.LineFromPosition(end) 1p

2367 begpos = self.source_editor.PositionFromLine(ini_line) 1p

2368 endpos = self.source_editor.PositionFromLine(end_line + 1) 1p

2369 cell_no_beg = self._get_cell_no(begpos, endpos, start) 1p

2370 cell_pos_beg = self._get_position_of_cell(begpos, endpos, cell_no_beg) 1p

2371 # if there is a selection subtract 1 from endpos to circumvent cursor being on end of cell 

2372 # --> otherwise no will be next cell no 

2373 if start != end: 2373 ↛ 2376line 2373 didn't jump to line 2376 because the condition on line 2373 was always true1p

2374 cell_no_end = self._get_cell_no(begpos, endpos, end - 1) 1p

2375 else: 

2376 cell_no_end = cell_no_beg 

2377 cell_pos_end = self._get_position_of_cell(begpos, endpos, cell_no_end + 1) 1p

2378 self.source_editor.Remove(cell_pos_beg, cell_pos_end) 1p

2379 new_start = cell_pos_beg 1p

2380 new_end = new_start + (end - start) 1p

2381 # SetSelection and SetCurrentPos + Store_position overrule each other so only use one of them 

2382 self.source_editor.SetSelection(new_start, new_end) 1p

2383 # @Helio: SetAnchor overrules the SetSelection if it specifies a different start than SetSelection 

2384 # I am not sure what any selection should be after deleting big ranges 

2385 self.source_editor.SetAnchor(new_start) 1p

2386 

2387 def _get_cell_no(self, begpos, endpos, findpos): 

2388 # get cell number from range begpos-endpos using findpos  

2389 cell_no = 0 1pnmo

2390 celltot = self._get_number_of_cells(begpos, endpos) 1pnmo

2391 while cell_no < celltot: 1pnmo

2392 cell_no += 1 1pnmo

2393 cellpos = self._get_position_of_cell(begpos, endpos, cell_no) 1pnmo

2394 if cellpos > findpos: 1pnmo

2395 cell_no -= 1 1pnm

2396 break 1pnm

2397 return cell_no 1pnmo

2398 

2399 def _get_number_of_cells(self, begpos, endpos): 

2400 # get number of cells in range begpos-endpos 

2401 # Warning! GetStringSelection does not work properly if there are diacritics in the content above (!) 

2402 # the selected range 

2403 the_content = self.source_editor.GetTextRange(begpos, endpos) 1pnmo

2404 celltab = ' ' * self.tab_size 1pnmo

2405 return the_content.count(celltab) 1pnmo

2406 

2407 def calc_cellpos(self, begpos, endpos, cell_no): 

2408 _cellpos = 0 1pnmo

2409 celltab = ' ' * self.tab_size 1pnmo

2410 cellencode = celltab.encode('utf-8') 1pnmo

2411 # Warning! GetStringSelection does not work properly if there are diacritics in 

2412 # the content above (!) the selected range 

2413 textrange = self.source_editor.GetTextRange(begpos, endpos) 1pnmo

2414 textencode = textrange.encode('utf-8') 1pnmo

2415 fndcnt = 1 # begpos is always in a cell 1pnmo

2416 fndidx = 0 1pnmo

2417 while fndidx != -1: 2417 ↛ 2427line 2417 didn't jump to line 2427 because the condition on line 2417 was always true1pnmo

2418 fndidx = textencode.find(cellencode, fndidx) 1pnmo

2419 if fndidx != -1: 2419 ↛ 2417line 2419 didn't jump to line 2417 because the condition on line 2419 was always true1pnmo

2420 if fndcnt == 1 and fndidx == 0: # check if begpos is at the beginning of a cell 1pnmo

2421 fndcnt -= 1 1pnmo

2422 fndcnt += 1 1pnmo

2423 if cell_no == fndcnt: 1pnmo

2424 _cellpos = begpos + fndidx 1pnmo

2425 break 1pnmo

2426 fndidx += 1 # for next search 1pnmo

2427 return _cellpos 1pnmo

2428 

2429 def _get_position_of_cell(self, begpos, endpos, cell_no): 

2430 # get position of cell number within range begpos-endpos  

2431 # DEBUG: this does not work correctly if first cell within the range is totally empty (so not as \ sanitized) 

2432 cellcnt = self._get_number_of_cells(begpos, endpos) 1pnmo

2433 if cell_no <= cellcnt: # encode is needed for finding correct position when there are special characters 2433 ↛ 2437line 2433 didn't jump to line 2437 because the condition on line 2433 was always true1pnmo

2434 # in the content 

2435 cellpos = self.calc_cellpos(begpos, endpos, cell_no) 1pnmo

2436 else: # cell_no does not exist -- return endpos-1 

2437 cellpos = endpos - 1 

2438 return cellpos 1pnmo

2439 

2440 def execute_sharp_comment(self, event): 

2441 __ = event 1zuy

2442 start, end = self.source_editor.GetSelection() 1zuy

2443 cursor = self.source_editor.GetCurrentPos() 1zuy

2444 ini_line = self.source_editor.LineFromPosition(start) 1zuy

2445 end_line = self.source_editor.LineFromPosition(end) 1zuy

2446 spaces = ' ' * self.tab_size 1zuy

2447 count = 0 1zuy

2448 maxsize = self.source_editor.GetLineCount() 1zuy

2449 # If the selection spans on more than one line: 

2450 if ini_line < end_line: 1zuy

2451 for line in range(ini_line, end_line + 1): 1u

2452 count += 1 1u

2453 if line < maxsize: 2453 ↛ 2456line 2453 didn't jump to line 2456 because the condition on line 2453 was always true1u

2454 self.source_editor.GotoLine(line) 1u

2455 else: 

2456 self.source_editor.GotoLine(maxsize) 

2457 pos = self.source_editor.PositionFromLine(line) 1u

2458 self.source_editor.SetCurrentPos(pos) 1u

2459 self.source_editor.SetSelection(pos, pos) 1u

2460 self.source_editor.SetInsertionPoint(pos) 1u

2461 row = self.source_editor.GetLine(line) 1u

2462 lenline = len(row) 1u

2463 if lenline > 0: 2463 ↛ 2451line 2463 didn't jump to line 2451 because the condition on line 2463 was always true1u

2464 idx = 0 1u

2465 while idx < lenline and row[idx] == ' ': 1u

2466 idx += 1 1u

2467 self.source_editor.InsertText(pos + idx, '# ') 1u

2468 elif start == end: # On a single row, no selection 1zy

2469 count += 1 1z

2470 pos = self.source_editor.PositionFromLine(ini_line) 1z

2471 row = self.source_editor.GetLine(ini_line) 1z

2472 lenline = len(row) 1z

2473 if lenline > 0: 2473 ↛ 2492line 2473 didn't jump to line 2492 because the condition on line 2473 was always true1z

2474 idx = 0 1z

2475 while idx < lenline and row[idx] == ' ': 1z

2476 idx += 1 1z

2477 self.source_editor.InsertText(pos + idx, '# ') 1z

2478 else: # On a single row, with selection 

2479 count += 1 1y

2480 pos = self.source_editor.PositionFromLine(ini_line) 1y

2481 row = self.source_editor.GetLine(ini_line) 1y

2482 if cursor > pos: 2482 ↛ 2492line 2482 didn't jump to line 2492 because the condition on line 2482 was always true1y

2483 idx = cursor - pos 1y

2484 while idx >= len(spaces): 2484 ↛ 2489line 2484 didn't jump to line 2489 because the condition on line 2484 was always true1y

2485 if row[idx - len(spaces):idx] != spaces: 1y

2486 idx -= 1 1y

2487 else: 

2488 break 1y

2489 if idx < len(spaces): 2489 ↛ 2490line 2489 didn't jump to line 2490 because the condition on line 2489 was never true1y

2490 idx = 0 

2491 self.source_editor.InsertText(pos + idx, '# ') 1y

2492 new_start = start 1zuy

2493 new_end = end + (count * 2) 1zuy

2494 if cursor == start: 1zuy

2495 ini = new_start 1z

2496 fini = new_end 1z

2497 else: 

2498 ini = new_end 1uy

2499 fini = new_start 1uy

2500 self.source_editor.SetSelection(new_start, new_end) # DEBUG: For some reason the selection is not restored! 1zuy

2501 self.source_editor.SetCurrentPos(ini) 1zuy

2502 self.source_editor.SetAnchor(fini) 1zuy

2503 self.source_editor.SetCurrentPos(cursor + count * 2) 1zuy

2504 self.store_position() 1zuy

2505 

2506 def execute_sharp_uncomment(self, event): 

2507 __ = event 1vws

2508 start, end = self.source_editor.GetSelection() 1vws

2509 cursor = self.source_editor.GetCurrentPos() 1vws

2510 ini_line = self.source_editor.LineFromPosition(start) 1vws

2511 end_line = self.source_editor.LineFromPosition(end) 1vws

2512 spaces = ' ' * self.tab_size 1vws

2513 # self.source_editor.SelectNone() 

2514 count = 0 1vws

2515 # maxsize = self.source_editor.GetLineCount() 

2516 # If the selection spans on more than one line: 

2517 if ini_line < end_line: 1vws

2518 for line in range(ini_line, end_line + 1): 1w

2519 pos = self.source_editor.PositionFromLine(line) 1w

2520 row = self.source_editor.GetLine(line) 1w

2521 lenline = len(row) 1w

2522 if lenline > 0: 2522 ↛ 2518line 2522 didn't jump to line 2518 because the condition on line 2522 was always true1w

2523 idx = 0 1w

2524 while idx < lenline and row[idx] == ' ': 1w

2525 idx += 1 1w

2526 size = 1 1w

2527 if idx + 1 < lenline and row[idx:idx + 1] == '#': 2527 ↛ 2518line 2527 didn't jump to line 2518 because the condition on line 2527 was always true1w

2528 if idx + 2 < lenline and row[idx + 1:idx + 2] == ' ': 2528 ↛ 2531line 2528 didn't jump to line 2531 because the condition on line 2528 was always true1w

2529 size = 2 1w

2530 # Here we clean up escaped spaces from Apply 

2531 if idx + size < lenline: 2531 ↛ 2537line 2531 didn't jump to line 2537 because the condition on line 2531 was always true1w

2532 newrow = row[idx + size:] 1w

2533 newrow = newrow.replace('\\ ', ' ') 1w

2534 size += len(row[idx:]) - len(newrow) - size 1w

2535 self.source_editor.DeleteRange(pos + idx, len(newrow) + size) 1w

2536 self.source_editor.InsertText(pos + idx, newrow) 1w

2537 count += size 1w

2538 elif start == end: # On a single row, no selection 1vs

2539 pos = self.source_editor.PositionFromLine(ini_line) 1v

2540 row = self.source_editor.GetLine(ini_line) 1v

2541 lenline = len(row) 1v

2542 if lenline > 0: 2542 ↛ 2589line 2542 didn't jump to line 2589 because the condition on line 2542 was always true1v

2543 idx = 0 1v

2544 while idx < lenline and row[idx] == ' ': 1v

2545 idx += 1 1v

2546 while count == 0 and idx < lenline: 1v

2547 size = 1 1v

2548 if idx + 1 < lenline and row[idx:idx + 1] == '#': 2548 ↛ 2560line 2548 didn't jump to line 2560 because the condition on line 2548 was always true1v

2549 if idx + 2 < lenline and row[idx + 1:idx + 2] == ' ': 2549 ↛ 2552line 2549 didn't jump to line 2552 because the condition on line 2549 was always true1v

2550 size = 2 1v

2551 # Here we clean up escaped spaces from Apply 

2552 if idx + size < lenline: 2552 ↛ 2558line 2552 didn't jump to line 2558 because the condition on line 2552 was always true1v

2553 newrow = row[idx + size:] 1v

2554 newrow = newrow.replace('\\ ', ' ') 1v

2555 size += len(row[idx:]) - len(newrow) - size 1v

2556 self.source_editor.DeleteRange(pos + idx, len(newrow) + size) 1v

2557 self.source_editor.InsertText(pos + idx, newrow) 1v

2558 count += size 1v

2559 else: 

2560 idx += 1 

2561 else: # On a single row, with selection 

2562 pos = self.source_editor.PositionFromLine(ini_line) 1s

2563 row = self.source_editor.GetLine(ini_line) 1s

2564 lenline = len(row) 1s

2565 if cursor > pos: 2565 ↛ 2589line 2565 didn't jump to line 2589 because the condition on line 2565 was always true1s

2566 idx = cursor - pos 1s

2567 while idx >= len(spaces): 2567 ↛ 2572line 2567 didn't jump to line 2572 because the condition on line 2567 was always true1s

2568 if row[idx - len(spaces):idx] != spaces: 1s

2569 idx -= 1 1s

2570 else: 

2571 break 1s

2572 if idx < len(spaces): 2572 ↛ 2573line 2572 didn't jump to line 2573 because the condition on line 2572 was never true1s

2573 idx = 0 

2574 while count == 0 and idx > 0: 1s

2575 size = 1 1s

2576 if idx + 1 < lenline and row[idx:idx + 1] == '#': 2576 ↛ 2588line 2576 didn't jump to line 2588 because the condition on line 2576 was always true1s

2577 if idx + 2 < lenline and row[idx + 1:idx + 2] == ' ': 2577 ↛ 2580line 2577 didn't jump to line 2580 because the condition on line 2577 was always true1s

2578 size = 2 1s

2579 # Here we clean up escaped spaces from Apply 

2580 if idx + size < lenline: 2580 ↛ 2586line 2580 didn't jump to line 2586 because the condition on line 2580 was always true1s

2581 newrow = row[idx + size:] 1s

2582 newrow = newrow.replace('\\ ', ' ') 1s

2583 size += len(row[idx:]) - len(newrow) - size 1s

2584 self.source_editor.DeleteRange(pos + idx, len(newrow) + size) 1s

2585 self.source_editor.InsertText(pos + idx, newrow) 1s

2586 count += size 1s

2587 else: 

2588 idx -= 1 

2589 if count == 0: 2589 ↛ 2590line 2589 didn't jump to line 2590 because the condition on line 2589 was never true1vws

2590 return 

2591 new_start = start 1vws

2592 new_end = end - count 1vws

2593 self.source_editor.SetSelection(new_start, new_end) # DEBUG: For some reason the selection is not restored! 1vws

2594 self.source_editor.SetCurrentPos(cursor - count) 1vws

2595 self.store_position() 1vws

2596 

2597 def on_settings_changed(self, message): 

2598 """Update tab size if txt spaces size setting is modified""" 

2599 _, setting = message.keys 

2600 if setting == TXT_NUM_SPACES: 2600 ↛ 2601line 2600 didn't jump to line 2601 because the condition on line 2600 was never true

2601 self.tab_size = self.source_editor_parent.app.settings.get(TXT_NUM_SPACES, 4) 

2602 if setting == 'reformat': 2602 ↛ 2603line 2602 didn't jump to line 2603 because the condition on line 2602 was never true

2603 self.reformat = self.source_editor_parent.app.settings.get('reformat', False) 

2604 

2605 def mark_file_dirty(self, dirty=True): 

2606 if not self.is_focused(): # DEBUG: Was marking file clean from Grid Editor 2606 ↛ 2607line 2606 didn't jump to line 2607 because the condition on line 2606 was never true1c

2607 return 

2608 if self._data: # and self._dirty == 1: 2608 ↛ exitline 2608 didn't return from function 'mark_file_dirty' because the condition on line 2608 was always true1c

2609 if dirty: 2609 ↛ 2610line 2609 didn't jump to line 2610 because the condition on line 2609 was never true1c

2610 self._data.mark_data_dirty() 

2611 else: 

2612 self._data.mark_data_pristine() 1c

2613 

2614 

2615class RobotDataEditor(PythonSTC): 

2616 margin = 1 

2617 

2618 def __init__(self, parent, readonly=False, language=None, style=wx.BORDER_NONE): 

2619 # stc.StyledTextCtrl.__init__(self, parent) 

2620 self.parent = parent 

2621 self.language = language 

2622 self._plugin = parent.plugin 

2623 self._settings = parent.source_editor_parent.app.settings 

2624 self.tab_markers = self._settings[PLUGIN_NAME].get('tab markers', True) 

2625 self.fold_symbols = self._settings[PLUGIN_NAME].get('fold symbols', 2) 

2626 PythonSTC.__init__(self, parent, -1, options={'tab markers':self.tab_markers, 'fold symbols':self.fold_symbols}, 

2627 style=style) 

2628 self._information_popup = None 

2629 self._old_details = None 

2630 self.readonly = readonly 

2631 # self.SetMarginType(self.margin, stc.STC_MARGIN_NUMBER) 

2632 self.SetLexer(stc.STC_LEX_CONTAINER) 

2633 self.SetReadOnly(True) 

2634 self.SetUseTabs(False) 

2635 caret_colour = self._settings[PLUGIN_NAME].get('setting', 'black') 

2636 self._background = self._settings[PLUGIN_NAME].get('background', 'white') 

2637 caret_colour = self.get_visible_color(caret_colour) 

2638 caret_style = self._settings[PLUGIN_NAME].get('caret style', 'block') 

2639 caret_style = stc.STC_CARETSTYLE_BLOCK if caret_style.lower() == 'block' else stc.STC_CARETSTYLE_LINE 

2640 self.SetCaretStyle(caret_style) 

2641 margin_background = self._settings['General'].get_without_default('secondary background') 

2642 margin_foreground = self._settings['General'].get_without_default('secondary foreground') 

2643 self.SetUpEditor(tab_size=parent.tab_size, tab_markers=self.tab_markers, 

2644 m_bg=margin_background, m_fg=margin_foreground, caret_fg=caret_colour) 

2645 self.Bind(stc.EVT_STC_STYLENEEDED, self.on_style) 

2646 self.Bind(stc.EVT_STC_ZOOM, self.on_zoom) 

2647 self.Bind(stc.EVT_STC_UPDATEUI, self.on_update_ui) 

2648 self.Bind(stc.EVT_STC_MARGINCLICK, self.on_margin_click) 

2649 # DEBUG: 

2650 self.Bind(wx.EVT_KEY_DOWN, self.on_key_pressed) 

2651 # Only set, after language: self.stylizer = RobotStylizer(self, self._settings, self.readonly) 

2652 self.stylizer = None 

2653 self.key_trigger = 0 

2654 self.autocomplete = self._settings[PLUGIN_NAME].get(AUTO_SUGGESTIONS, False) 

2655 # register some images for use in the AutoComplete box. 

2656 # self.RegisterImage(1, Smiles.GetBitmap()) # DEBUG was images. 

2657 self.RegisterImage(1, wx.ArtProvider.GetBitmap(wx.ART_FLOPPY, size=(16, 16))) 

2658 self.RegisterImage(2, wx.ArtProvider.GetBitmap(wx.ART_NEW, size=(16, 16))) 

2659 self.RegisterImage(3, wx.ArtProvider.GetBitmap(wx.ART_COPY, size=(16, 16))) 

2660 

2661 def show_kw_doc(self, coords=None): 

2662 # print(f"DEBUG: TextEditor RobotDataEditor show_kw_doc ENTER self.AutoCompActive()=={self.AutoCompActive()}") 

2663 if self.AutoCompActive(): 

2664 selected = [self.AutoCompGetCurrentText()] 

2665 else: 

2666 selected = self.get_selected_or_near_text(keep_cursor_pos=True) 

2667 # print(f"DEBUG: TextEditor RobotDataEditor show_kw_doc selected=={selected}") 

2668 if selected: 

2669 for kw in selected: 

2670 self._show_keyword_details(kw, coords) 

2671 

2672 def hide_kw_doc(self): 

2673 list_of_popups = self.parent.GetChildren() 1ak

2674 for popup in list_of_popups: 1ak

2675 if isinstance(popup, HtmlPopupWindow): 2675 ↛ 2676line 2675 didn't jump to line 2676 because the condition on line 2675 was never true1ak

2676 popup.hide() 

2677 self._old_details = None 

2678 

2679 def on_key_pressed(self, event): 

2680 if self.CallTipActive(): 

2681 self.CallTipCancel() 

2682 key = event.GetKeyCode() 

2683 if key == 32 and event.ControlDown(): 

2684 # Tips 

2685 if event.ShiftDown(): 

2686 self.show_kw_doc() 

2687 # Code completion 

2688 else: 

2689 self.parent.on_content_assist(event) 

2690 self.key_trigger = 0 

2691 else: 

2692 # print(f"DEBUG: texteditor.py RobotDataEditor on_key_pressed calling try_autocomplete key={key}") 

2693 self._try_autocomplete(key, event) 

2694 event.Skip() 

2695 

2696 def _try_autocomplete(self, key: int, event: wx.KeyEvent) -> None: 

2697 if self.autocomplete and not event.ControlDown(): 

2698 if 32 < key < 309 and self.key_trigger > -1: 

2699 if self.key_trigger < 2: 

2700 self.key_trigger += 1 

2701 else: 

2702 self.key_trigger = -1 

2703 self.parent.on_content_assist(event) 

2704 else: 

2705 self.key_trigger = 0 

2706 

2707 def set_text(self, text): 

2708 self.SetReadOnly(False) 1aApCxzuyvwstinmoBklqrcbjefdgh

2709 self.SetText(text) 1aApCxzuyvwstinmoBklqrcbjefdgh

2710 self.set_language(self.language) 1aApCxzuyvwstinmoBklqrcbjefdgh

2711 self.stylizer.stylize() 1aApCxzuyvwstinmoBklqrcbjefdgh

2712 self.EmptyUndoBuffer() 1aApCxzuyvwstinmoBklqrcbjefdgh

2713 self.SetMarginWidth(self.margin, self.calc_margin_width()) 1aApCxzuyvwstinmoBklqrcbjefdgh

2714 self.SetMarginWidth(2, self.TextWidth(stc.STC_STYLE_DEFAULT, "MM")) 1aApCxzuyvwstinmoBklqrcbjefdgh

2715 self.Update() 1aApCxzuyvwstinmoBklqrcbjefdgh

2716 

2717 def set_language(self, dlanguage): 

2718 content = self.GetTextRaw() 1aApCxzuyvwstinmoBklqrcbjefdgh

2719 # print(f"DEBUG: set_language content={content}\nset_language={dlanguage}") 

2720 if content and b"Language: " in content: # We need to recheck the language setting 1aApCxzuyvwstinmoBklqrcbjefdgh

2721 try: 1rcbjefdgh

2722 self.language = obtain_language(dlanguage, content) 1rcbjefdgh

2723 except ValueError: 1rcb

2724 self.language = 'English' 1rcb

2725 else: 

2726 self.language = 'English' 1aApCxzuyvwstinmoBklqrcjd

2727 self.stylizer = RobotStylizer(self, self._settings, self.readonly, self.language) 1aApCxzuyvwstinmoBklqrcbjefdgh

2728 

2729 @property 

2730 def utf8_text(self): 

2731 return self.GetText().encode('UTF-8') 1A

2732 

2733 def on_style(self, event): 

2734 __ = event 1D

2735 self.stylizer.stylize() 1D

2736 

2737 def on_zoom(self, event): 

2738 __ = event 1aApCxzuyvwstinmoBklqDrcbjefdgh

2739 self.SetMarginWidth(self.margin, self.calc_margin_width()) 1aApCxzuyvwstinmoBklqDrcbjefdgh

2740 self.SetMarginWidth(2, self.TextWidth(stc.STC_STYLE_FOLDDISPLAYTEXT, "MM")) 1aApCxzuyvwstinmoBklqDrcbjefdgh

2741 self._set_zoom() 1aApCxzuyvwstinmoBklqDrcbjefdgh

2742 

2743 def _set_zoom(self): 

2744 new = self.GetZoom() 1aApCxzuyvwstinmoBklqDrcbjefdgh

2745 old = self._settings[PLUGIN_NAME].get(ZOOM_FACTOR, 0) 1aApCxzuyvwstinmoBklqDrcbjefdgh

2746 if new != old: 2746 ↛ 2747line 2746 didn't jump to line 2747 because the condition on line 2746 was never true1aApCxzuyvwstinmoBklqDrcbjefdgh

2747 self._settings[PLUGIN_NAME].set(ZOOM_FACTOR, new) 

2748 

2749 def calc_margin_width(self): 

2750 style = stc.STC_STYLE_LINENUMBER 1aApCxzuyvwstinmoBklqDrcbjefdgh

2751 width = self.TextWidth(style, str(self.GetLineCount())) 1aApCxzuyvwstinmoBklqDrcbjefdgh

2752 return width + self.TextWidth(style, "M") 1aApCxzuyvwstinmoBklqDrcbjefdgh

2753 

2754 def get_selected_or_near_text(self, keep_cursor_pos=False): 

2755 content = set() 1ic

2756 if keep_cursor_pos: 1ic

2757 restore_cursor_pos = self.GetInsertionPoint() 1i

2758 else: 

2759 restore_cursor_pos = None 1ic

2760 # First get selected text 

2761 selected = self.GetSelectedText() 1ic

2762 restore_start_pos = self.GetSelectionStart() 1ic

2763 restore_end_pos = self.GetSelectionEnd() 1ic

2764 # print(f"DEBUG: TextEditor RobotDataEditor get_selected_or_near_text, restore_cursor={restore_cursor_pos} " 

2765 # f"restore_start_pos={restore_start_pos} restore_end_pos={restore_end_pos} anchor={self.GetAnchor()}") 

2766 if selected: 1ic

2767 start_pos = self.GetSelectionStart() 1i

2768 if selected.endswith('.'): # Special cases for libraries prefix 2768 ↛ 2769line 2768 didn't jump to line 2769 because the condition on line 2768 was never true1i

2769 if restore_cursor_pos: 

2770 self.SetInsertionPoint(restore_cursor_pos) 

2771 else: 

2772 self.SetInsertionPoint(start_pos + len(selected)) 

2773 elif len(selected.split('.')) > 1: 2773 ↛ 2774line 2773 didn't jump to line 2774 because the condition on line 2773 was never true1i

2774 parts = selected.split('.') 

2775 self.SetSelectionStart(start_pos + len(parts[0]) + 1) 

2776 self.SetSelectionEnd(start_pos + len(selected)) 

2777 if restore_cursor_pos: 

2778 self.SetInsertionPoint(restore_cursor_pos) 

2779 else: 

2780 self.SetInsertionPoint(start_pos + len(parts[0]) + 1) 

2781 else: 

2782 self.SetSelectionStart(start_pos) 1i

2783 self.SetSelectionEnd(start_pos + len(selected)) 1i

2784 if restore_cursor_pos: 2784 ↛ 2785line 2784 didn't jump to line 2785 because the condition on line 2784 was never true1i

2785 self.SetInsertionPoint(restore_cursor_pos) 

2786 else: 

2787 self.SetInsertionPoint(start_pos + len(selected)) 1i

2788 content.add(selected.strip()) 1i

2789 # Next get text on the left 

2790 text = self.GetCurLine()[0] 1ic

2791 start_pos = self.GetInsertionPoint() 1ic

2792 line = self.GetCurrentLine() 1ic

2793 line_end = self.GetLineEndPosition(line) 1ic

2794 size = self.GetLineLength(line) 1ic

2795 star = self.GetLineRaw(line) 1ic

2796 sz_star = len(star) - 1 1ic

2797 min_pos = line_end - sz_star 1ic

2798 pos_in_line = start_pos - min_pos 1ic

2799 # print(f"DEBUG: line={text}\nstar={star}\nmin_pos={min_pos} start_pos={start_pos}" 

2800 # f" line_end={line_end}\nline={line}" 

2801 # f" pos_in_line={pos_in_line} size={size} sz_star={sz_star} lentext={len(text)}") 

2802 # if pos_in_line > 0: 

2803 start_chr = end_chr = None 1ic

2804 try: 1ic

2805 for i in range(max(1, min(size, pos_in_line-1)), 1, -1): 1ic

2806 if text[i] == ' ' and text[i-1] == ' ': 1ic

2807 start_chr = i + 1 1ic

2808 break 1ic

2809 if pos_in_line >= 0: 1ic

2810 for i in range(pos_in_line, size): 1ic

2811 if text[i] == ' ' and text[i+1] == ' ': 1ic

2812 end_chr = i 1i

2813 break 1i

2814 except IndexError: 

2815 pass 

2816 # print(f"DEBUG: TextEditor RobotDataEditor get_selected_or_near_text, get text on the left {start_chr=}" 

2817 # f" {end_chr=} {pos_in_line=}") 

2818 value = None 1ic

2819 if start_chr is not None: 1ic

2820 if end_chr is not None: 1ic

2821 value = text[start_chr:end_chr] 1i

2822 else: 

2823 value = text[start_chr:].strip() 1ic

2824 elif end_chr is not None: 1i

2825 value = text[pos_in_line:end_chr] 1i

2826 if value: 1ic

2827 # print(f"DEBUG: TextEditor RobotDataEditor get_selected_or_near_text, get text on the left {value=} ") 

2828 if start_chr: 2828 ↛ 2831line 2828 didn't jump to line 2831 because the condition on line 2828 was always true1ic

2829 start_pos = min_pos + start_chr 1ic

2830 else: 

2831 start_pos = min_pos + pos_in_line 

2832 if value.endswith('.'): # Special cases for libraries prefix 2832 ↛ 2833line 2832 didn't jump to line 2833 because the condition on line 2832 was never true1ic

2833 if restore_cursor_pos: 

2834 self.SetInsertionPoint(restore_cursor_pos) 

2835 else: 

2836 self.SetInsertionPoint(start_pos + len(value)) 

2837 elif len(value.split('.')) > 1: 2837 ↛ 2838line 2837 didn't jump to line 2838 because the condition on line 2837 was never true1ic

2838 if restore_cursor_pos: 

2839 self.SetInsertionPoint(restore_cursor_pos) 

2840 else: 

2841 parts = value.split('.') 

2842 self.SetSelectionStart(start_pos + len(parts[0]) + 1) 

2843 self.SetSelectionEnd(start_pos + len(value)) 

2844 self.SetInsertionPoint(start_pos + len(parts[0]) + 1) 

2845 else: 

2846 if restore_cursor_pos: 1ic

2847 self.SetInsertionPoint(restore_cursor_pos) 1i

2848 else: 

2849 self.SetSelectionStart(start_pos) 1ic

2850 self.SetSelectionEnd(start_pos + len(value)) 1ic

2851 self.SetInsertionPoint(start_pos) 1ic

2852 content.add(value) 1ic

2853 else: 

2854 for bit in text.strip().strip('*').split(' '): 1i

2855 content.add(bit) 1i

2856 try: 1i

2857 content.remove('') 1i

2858 except KeyError: 

2859 pass 

2860 # print(f"DEBUG: TextEditor RobotDataEditor get_selected_or_near_text, content={content} ") 

2861 if restore_cursor_pos: 1ic

2862 self.SetAnchor(restore_cursor_pos) 1i

2863 self.SetInsertionPoint(restore_cursor_pos) 1i

2864 if restore_start_pos: 1ic

2865 self.SetSelection(restore_start_pos, restore_end_pos) 1ic

2866 return content if content else [''] 1ic

2867 

2868 def on_update_ui(self, evt): 

2869 _ = evt 1k

2870 # check for matching braces 

2871 brace_at_caret = -1 1k

2872 brace_opposite = -1 1k

2873 char_before = None 1k

2874 caret_pos = self.GetCurrentPos() 1k

2875 

2876 if caret_pos > 0: 2876 ↛ 2880line 2876 didn't jump to line 2880 because the condition on line 2876 was always true1k

2877 char_before = self.GetCharAt(caret_pos - 1) 1k

2878 

2879 # check before 

2880 if char_before and chr(char_before) in "[]{}()": 2880 ↛ 2881line 2880 didn't jump to line 2881 because the condition on line 2880 was never true1k

2881 brace_at_caret = caret_pos - 1 

2882 

2883 # check after 

2884 if brace_at_caret < 0: 2884 ↛ 2890line 2884 didn't jump to line 2890 because the condition on line 2884 was always true1k

2885 char_after = self.GetCharAt(caret_pos) 1k

2886 

2887 if char_after and chr(char_after) in "[]{}()": 2887 ↛ 2888line 2887 didn't jump to line 2888 because the condition on line 2887 was never true1k

2888 brace_at_caret = caret_pos 

2889 

2890 if brace_at_caret >= 0: 2890 ↛ 2891line 2890 didn't jump to line 2891 because the condition on line 2890 was never true1k

2891 brace_opposite = self.BraceMatch(brace_at_caret) 

2892 

2893 if brace_at_caret != -1 and brace_opposite == -1: 2893 ↛ 2894line 2893 didn't jump to line 2894 because the condition on line 2893 was never true1k

2894 self.BraceBadLight(brace_at_caret) 

2895 else: 

2896 self.BraceHighlight(brace_at_caret, brace_opposite) 1k

2897 self.stylizer.stylize() 1k

2898 

2899 def _show_keyword_details(self, value, coords=None): 

2900 """ 

2901 Shows the keyword documentation in value at coordinates, coords. 

2902 :param value: The content to show in detacheable window 

2903 :param coords: If None they will be mouse pointer coordinates 

2904 """ 

2905 details = self._plugin.get_keyword_details(value) 

2906 if details and details != self._old_details: # This is because on Windows keys are sent in repeat 

2907 if not coords: 

2908 position = wx.GetMousePosition() 

2909 else: 

2910 position = coords 

2911 self._information_popup = HtmlPopupWindow(self.parent, (450, 300)) 

2912 self._information_popup.set_content(details, value) 

2913 self._information_popup.show_at(position) 

2914 self._old_details = details 

2915 

2916 def get_visible_color(self, colour): 

2917 color_diff1 = abs(Colour.GetRGBA(Colour(colour)) - Colour.GetRGBA(Colour(self._background))) 

2918 color_diff2 = abs(Colour.GetRGBA(Colour('gray')) - Colour.GetRGBA(Colour(self._background))) 

2919 color_diff3 = abs(Colour.GetRGBA(Colour(colour)) - Colour.GetRGBA(Colour('gray'))) 

2920 if color_diff1 > color_diff2: 

2921 return Colour(colour) 

2922 elif color_diff2 > color_diff3: 2922 ↛ 2924line 2922 didn't jump to line 2924 because the condition on line 2922 was always true

2923 return Colour('gray') 

2924 elif color_diff1 < color_diff3: 

2925 return Colour(colour) 

2926 return Colour('black') 

2927 

2928 def SetUpEditor(self, tab_size=4, tab_markers=True, m_bg='', m_fg='', caret_fg: Colour = 'BLUE'): 

2929 """ 

2930 This method carries out the work of setting up the Code editor. 

2931 It's seperate so as not to clutter up the init code. 

2932 """ 

2933 import keyword 

2934 

2935 self.SetLexer(stc.STC_LEX_PYTHON) 

2936 self.SetKeyWords(0, " ".join(keyword.kwlist)) 

2937 

2938 # Enable folding 

2939 self.SetProperty("fold", "1") 

2940 

2941 # Highlight tab/space mixing (shouldn't be any) 

2942 self.SetProperty("tab.timmy.whinge.level", "1") 

2943 

2944 # Set left and right margins 

2945 self.SetMargins(2, 2) 

2946 

2947 self.SetMarginBackground(1, m_bg) 

2948 # Set up the numbers in the margin for margin #1 

2949 self.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER) 

2950 # Reasonable value for, say, 4-5 digits using a mono font (40 pix) 

2951 # self.SetMarginWidth(1, 40) 

2952 

2953 # Indentation and tab stuff 

2954 self.SetIndent(tab_size) # Proscribed indent size for wx 

2955 self.SetIndentationGuides(tab_markers) # Show indent guides 

2956 self.SetBackSpaceUnIndents(True) # Backspace unindents rather than delete 1 space 

2957 self.SetTabIndents(True) # Tab key indents 

2958 self.SetTabWidth(tab_size) # Proscribed tab size for wx 

2959 self.SetUseTabs(False) # Use spaces rather than tabs, or TabTimmy will complain! 

2960 # White space 

2961 self.SetViewWhiteSpace(False) # Don't view white space 

2962 

2963 # EOL: Since we are loading/saving ourselves, and the 

2964 # strings will always have \n's in them, set the STC to 

2965 # edit them that way. 

2966 self.SetEOLMode(wx.stc.STC_EOL_LF) 

2967 self.SetViewEOL(False) 

2968 

2969 # No right-edge mode indicator 

2970 self.SetEdgeMode(stc.STC_EDGE_NONE) 

2971 

2972 # Set up a margin to hold fold markers 

2973 self.SetMarginType(2, stc.STC_MARGIN_SYMBOL) 

2974 self.SetMarginMask(2, stc.STC_MASK_FOLDERS) 

2975 self.SetMarginSensitive(2, True) 

2976 self.SetMarginBackground(2, m_bg) 

2977 self.SetFoldMarginColour(True, m_bg) 

2978 self.SetFoldMarginHiColour(True, m_bg) 

2979 self.SetMarginWidth(2, self.TextWidth(stc.STC_STYLE_FOLDDISPLAYTEXT, "MM")) 

2980 

2981 # Global default style 

2982 if wx.Platform == '__WXMSW__': 2982 ↛ 2983line 2982 didn't jump to line 2983 because the condition on line 2982 was never true

2983 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, 'fore:#000000,back:#FFFFFF,face:Space Mono') # Courier New 

2984 elif wx.Platform == '__WXMAC__': 2984 ↛ 2991line 2984 didn't jump to line 2991 because the condition on line 2984 was always true

2985 # DEBUG: if this looks fine on Linux too, remove the Mac-specific case 

2986 # and use this whenever OS != MSW. 

2987 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, 

2988 'fore:#000000,back:#FFFFFF,face:Monaco') 

2989 else: 

2990 # print("DEBUG: Setup on Linux") 

2991 defsize = wx.SystemSettings.GetFont(wx.SYS_ANSI_FIXED_FONT).GetPointSize() 

2992 # Courier, Space Mono, Source Pro Mono, 

2993 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, 'fore:#000000,back:#FFFFFF,face:Hack,size:%d' % defsize) 

2994 """ 

2995 self.StyleSetBackground(stc.STC_STYLE_DEFAULT, Colour(200, 222, 40)) 

2996 self.StyleSetForeground(stc.STC_STYLE_DEFAULT, Colour(7, 0, 70)) 

2997 """ 

2998 # Clear styles and revert to default. 

2999 self.StyleClearAll() 

3000 

3001 # Following style specs only indicate differences from default. 

3002 # The rest remains unchanged. 

3003 

3004 # Line numbers in margin 

3005 self.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER, f'fore:{m_fg},back:{m_bg}') 

3006 # Highlighted brace 

3007 self.StyleSetSpec(wx.stc.STC_STYLE_BRACELIGHT, 'fore:#00009D,back:#FFFF00') 

3008 # Unmatched brace 

3009 self.StyleSetSpec(wx.stc.STC_STYLE_BRACEBAD, 'fore:#00009D,back:#FF0000') 

3010 # Indentation guide 

3011 if tab_markers: 3011 ↛ 3015line 3011 didn't jump to line 3015 because the condition on line 3011 was always true

3012 self.StyleSetSpec(wx.stc.STC_STYLE_INDENTGUIDE, "fore:#CDCDCD") 

3013 

3014 # Caret color 

3015 self.SetCaretForeground(Colour(caret_fg)) 

3016 # Selection background 

3017 # self.SetSelBackground(1, '#66CCFF') 

3018 """ 

3019 self.SetBackgroundColour(Colour(200, 222, 40)) 

3020 self.SetForegroundColour(Colour(7, 0, 70)) 

3021 """ 

3022 

3023 self.SetSelBackground(True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)) 

3024 self.SetSelForeground(True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT)) 

3025 

3026 

3027class FromStringIOPopulator(robotapi.populators.FromFilePopulator): 

3028 

3029 def populate(self, content: [str, BytesIO], tab_size: int): 

3030 try: 

3031 if not self._language: 

3032 set_lang = shared_memory.ShareableList(name="language") 

3033 language = [set_lang[0]] 

3034 else: 

3035 language = self._language 

3036 except AttributeError: 

3037 language = ['en'] 

3038 robotapi.RobotReader(spaces=tab_size, lang=language).read(content, self) 

3039 

3040 

3041class RobotStylizer(object): 

3042 def __init__(self, editor, settings, readonly=False, language=None): 

3043 self.tokens = {} 1aApCxzuyvwstinmoBklqrcbjefdgh

3044 self.editor = editor 1aApCxzuyvwstinmoBklqrcbjefdgh

3045 self.lexer = None 1aApCxzuyvwstinmoBklqrcbjefdgh

3046 self.settings = settings 1aApCxzuyvwstinmoBklqrcbjefdgh

3047 self._readonly = readonly 1aApCxzuyvwstinmoBklqrcbjefdgh

3048 self._ensure_default_font_is_valid() 1aApCxzuyvwstinmoBklqrcbjefdgh

3049 if language: 3049 ↛ 3055line 3049 didn't jump to line 3055 because the condition on line 3049 was always true1aApCxzuyvwstinmoBklqrcbjefdgh

3050 if isinstance(language, list): 1aApCxzuyvwstinmoBklqrcbjefdgh

3051 self.language = language[0] 1rcbjefdgh

3052 else: 

3053 self.language = [language] 1aApCxzuyvwstinmoBklqrcbjd

3054 else: 

3055 self.language = ['En'] 

3056 options = {'language': self.language} 1aApCxzuyvwstinmoBklqrcbjefdgh

3057 # print(f"DEBUG: texteditor.py RobotStylizer _init_ language={self.language}\n") 

3058 if robotframeworklexer: 3058 ↛ 3061line 3058 didn't jump to line 3061 because the condition on line 3058 was always true1aApCxzuyvwstinmoBklqrcbjefdgh

3059 self.lexer = robotframeworklexer.RobotFrameworkLexer(**options) 1aApCxzuyvwstinmoBklqrcbjefdgh

3060 else: 

3061 self.editor.GetParent().create_syntax_colorization_help() 

3062 self.set_styles(self._readonly) 1aApCxzuyvwstinmoBklqrcbjefdgh

3063 PUBLISHER.subscribe(self.on_settings_changed, RideSettingsChanged) 1aApCxzuyvwstinmoBklqrcbjefdgh

3064 

3065 def on_settings_changed(self, message): 

3066 """Redraw the colors if the color settings are modified""" 

3067 section, _ = message.keys 

3068 if section == PLUGIN_NAME: 

3069 self.set_styles(self._readonly) # DEBUG: When on read-only file changing background color ignores flag 

3070 self.editor.autocomplete = self.settings[PLUGIN_NAME].get(AUTO_SUGGESTIONS, False) 

3071 caret_colour = self.settings[PLUGIN_NAME].get('setting', 'black') 

3072 caret_colour = self.editor.get_visible_color(caret_colour) 

3073 self.editor.SetCaretForeground(Colour(caret_colour)) 

3074 caret_style = self.settings[PLUGIN_NAME].get('caret style', 'block') 

3075 caret_style = stc.STC_CARETSTYLE_BLOCK if caret_style.lower() == 'block' else stc.STC_CARETSTYLE_LINE 

3076 self.editor.SetCaretStyle(caret_style) 

3077 

3078 def _font_size(self): 

3079 return self.settings[PLUGIN_NAME].get('font size', 10) 1aApCxzuyvwstinmoBklqDrcbjefdgh

3080 

3081 def _font_face(self): 

3082 return self.settings[PLUGIN_NAME].get('font face', 'Courier New') 1aApCxzuyvwstinmoBklqDrcbjefdgh

3083 

3084 def _zoom_factor(self): 

3085 return self.settings[PLUGIN_NAME].get(ZOOM_FACTOR, 0) 1aApCxzuyvwstinmoBklqDrcbjefdgh

3086 

3087 def set_styles(self, readonly=False): 

3088 color_settings = self.settings.get_without_default(PLUGIN_NAME) 1aApCxzuyvwstinmoBklqDrcbjefdgh

3089 background = color_settings.get('background', '#FFFFFF') 1aApCxzuyvwstinmoBklqDrcbjefdgh

3090 if readonly: 1aApCxzuyvwstinmoBklqDrcbjefdgh

3091 h = background.lstrip('#') 1k

3092 if h.upper() == background.upper(): 3092 ↛ 3098line 3092 didn't jump to line 3098 because the condition on line 3092 was always true1k

3093 from wx import ColourDatabase 1k

3094 cdb = ColourDatabase() 1k

3095 bkng = cdb.Find(h.upper()) 1k

3096 bkg = (bkng[0], bkng[1], bkng[2]) 1k

3097 else: 

3098 bkg = tuple(int(h[i:i + 2], 16) for i in (0, 2, 4)) 

3099 if bkg >= (180, 180, 180): 3099 ↛ 3103line 3099 didn't jump to line 3103 because the condition on line 3099 was always true1k

3100 bkg = (max(160, bkg[0] - 80), max(160, bkg[1] - 80), 1k

3101 max(160, bkg[2] - 80)) 

3102 else: 

3103 bkg = (min(255, bkg[0] + 180), min(255, bkg[1] + 180), 

3104 min(255, bkg[2] + 180)) 

3105 background = '#%02X%02X%02X' % bkg 1k

3106 if robotframeworklexer: 3106 ↛ 3154line 3106 didn't jump to line 3154 because the condition on line 3106 was always true1aApCxzuyvwstinmoBklqDrcbjefdgh

3107 styles = { 1aApCxzuyvwstinmoBklqDrcbjefdgh

3108 robotframeworklexer.ARGUMENT: { 

3109 'fore': color_settings.get('argument', '#bb8844') 

3110 }, 

3111 robotframeworklexer.COMMENT: { 

3112 'fore': color_settings.get('comment', 'black') 

3113 }, 

3114 robotframeworklexer.ERROR: { 

3115 'fore': color_settings.get('error', 'black') 

3116 }, 

3117 robotframeworklexer.GHERKIN: { 

3118 'fore': color_settings.get('gherkin', 'black') 

3119 }, 

3120 robotframeworklexer.HEADING: { 

3121 'fore': color_settings.get('heading', '#999999'), 

3122 'bold': 'true' 

3123 }, 

3124 robotframeworklexer.IMPORT: { 

3125 'fore': color_settings.get('import', '#555555') 

3126 }, 

3127 robotframeworklexer.KEYWORD: { 

3128 'fore': color_settings.get('keyword', '#990000'), 

3129 'bold': 'true' 

3130 }, 

3131 robotframeworklexer.SEPARATOR: { 

3132 'fore': color_settings.get('separator', 'black') 

3133 }, 

3134 robotframeworklexer.SETTING: { 

3135 'fore': color_settings.get('setting', 'black'), 

3136 'bold': 'true' 

3137 }, 

3138 robotframeworklexer.SYNTAX: { 

3139 'fore': color_settings.get('syntax', 'black') 

3140 }, 

3141 robotframeworklexer.TC_KW_NAME: { 

3142 'fore': color_settings.get('tc_kw_name', '#aaaaaa') 

3143 }, 

3144 robotframeworklexer.VARIABLE: { 

3145 'fore': color_settings.get('variable', '#008080') 

3146 } 

3147 } 

3148 for index, token in enumerate(styles): 1aApCxzuyvwstinmoBklqDrcbjefdgh

3149 self.tokens[token] = index 1aApCxzuyvwstinmoBklqDrcbjefdgh

3150 self.editor.StyleSetSpec(index, 1aApCxzuyvwstinmoBklqDrcbjefdgh

3151 self._get_style_string(back=background, 

3152 **styles[token])) 

3153 else: 

3154 foreground = color_settings.get('setting', 'black') 

3155 self.editor.StyleSetSpec(0, self._get_style_string(back=background, 

3156 fore=foreground)) 

3157 self.editor.StyleSetBackground(wx.stc.STC_STYLE_DEFAULT, background) 1aApCxzuyvwstinmoBklqDrcbjefdgh

3158 self.editor.SetZoom(self._zoom_factor()) 1aApCxzuyvwstinmoBklqDrcbjefdgh

3159 self.editor.Refresh() 1aApCxzuyvwstinmoBklqDrcbjefdgh

3160 

3161 def _get_word_and_length(self, current_position): 

3162 word = self.editor.GetTextRange(current_position, 

3163 self.editor.WordEndPosition( 

3164 current_position, 

3165 False)) 

3166 return word, len(word) 

3167 

3168 def _get_style_string(self, back='#FFFFFF', fore='#000000', bold='', underline=''): 

3169 settings = locals() 1aApCxzuyvwstinmoBklqDrcbjefdgh

3170 settings.update(size=self._font_size()) 1aApCxzuyvwstinmoBklqDrcbjefdgh

3171 settings.update(face=self._font_face()) 1aApCxzuyvwstinmoBklqDrcbjefdgh

3172 return ','.join('%s:%s' % (name, value) 1aApCxzuyvwstinmoBklqDrcbjefdgh

3173 for name, value in settings.items() if value) 

3174 

3175 def _ensure_default_font_is_valid(self): 

3176 """Checks if default font is installed""" 

3177 default_font = self._font_face() 1aApCxzuyvwstinmoBklqrcbjefdgh

3178 if default_font not in read_fonts(): 3178 ↛ 3179line 3178 didn't jump to line 3179 because the condition on line 3178 was never true1aApCxzuyvwstinmoBklqrcbjefdgh

3179 sys_font = wx.SystemSettings.GetFont(wx.SYS_ANSI_FIXED_FONT) 

3180 self.settings[PLUGIN_NAME]['font face'] = sys_font.GetFaceName() 

3181 

3182 def stylize(self): 

3183 # print(f"DEBUG: texteditor.py RobotStylizer stylize ENTER lexer={self.lexer}") 

3184 if not self.lexer: 3184 ↛ 3185line 3184 didn't jump to line 3185 because the condition on line 3184 was never true1aApCxzuyvwstinmoBklqDrcbjefdgh

3185 return 

3186 self.editor.ConvertEOLs(2) 1aApCxzuyvwstinmoBklqDrcbjefdgh

3187 shift = 0 1aApCxzuyvwstinmoBklqDrcbjefdgh

3188 for position, token, value in self.lexer.get_tokens_unprocessed(self.editor.GetText()): 1aApCxzuyvwstinmoBklqDrcbjefdgh

3189 # print(f"DEBUG: texteditor.py RobotStylizer stylize token={token} value={value}") 

3190 if wx.VERSION < (4, 1, 0): 3190 ↛ 3191line 3190 didn't jump to line 3191 because the condition on line 3190 was never true1aApCxzuyvwstinmoBklqrcbjefdgh

3191 self.editor.StartStyling(position + shift, 31) 

3192 else: 

3193 self.editor.StartStyling(position + shift) 1aApCxzuyvwstinmoBklqrcbjefdgh

3194 try: 1aApCxzuyvwstinmoBklqrcbjefdgh

3195 self.editor.SetStyling(len(value.encode('utf-8')), self.tokens[token]) 1aApCxzuyvwstinmoBklqrcbjefdgh

3196 shift += len(value.encode('utf-8')) - len(value) 1aApCxzuyvwstinmoBklqrcbjefdgh

3197 except UnicodeEncodeError: 

3198 self.editor.SetStyling(len(value), self.tokens[token]) 

3199 shift += len(value) - len(value)