Coverage for src/robotide/editor/pythoneditor.py: 26%

185 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-06 10:40 +0100

1# Copyright 2025- Robot Framework Foundation 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15import keyword 1ab

16import wx 1ab

17from wx import stc 1ab

18 

19 

20if wx.Platform == '__WXMSW__': 20 ↛ 21line 20 didn't jump to line 21 because the condition on line 20 was never true1ab

21 faces = {'times': 'Times New Roman', 

22 'mono': 'Courier New', 

23 'helv': 'Arial', 

24 'other': 'Comic Sans MS', 

25 'size': 10, 

26 'size2': 8, 

27 } 

28elif wx.Platform == '__WXMAC__': 28 ↛ 37line 28 didn't jump to line 37 because the condition on line 28 was always true1ab

29 faces = {'times': 'Times New Roman', 1ab

30 'mono': 'Monaco', 

31 'helv': 'Arial', 

32 'other': 'Comic Sans MS', 

33 'size': 12, 

34 'size2': 10, 

35 } 

36else: 

37 faces = {'times': 'Times', 

38 'mono': 'Courier', 

39 'helv': 'Helvetica', 

40 'other': 'new century schoolbook', 

41 'size': 12, 

42 'size2': 10, 

43 } 

44 

45 

46class PythonSTC(stc.StyledTextCtrl): 1ab

47 

48 def __init__(self, parent, idd, options: dict, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0): 1ab

49 stc.StyledTextCtrl.__init__(self, parent=parent, id=idd, pos=pos, size=size, style=style) 

50 

51 # self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN) 

52 # self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT) 

53 # self.tab_markers = options['tab markers'] 

54 self.fold_symbols = options['fold symbols'] 

55 self.SetLexer(stc.STC_LEX_PYTHON) 

56 self.SetKeyWords(0, " ".join(keyword.kwlist)) 

57 

58 self.SetProperty("fold", "1") 

59 self.SetProperty("tab.timmy.whinge.level", "1") 

60 self.SetMargins(2, 2) 

61 

62 self.SetViewWhiteSpace(False) 

63 self.SetEdgeMode(stc.STC_EDGE_BACKGROUND) 

64 self.SetEdgeColumn(78) 

65 

66 # Set up a margin to hold fold markers 

67 # self.SetFoldFlags(16) ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER? 

68 self.SetMarginType(2, stc.STC_MARGIN_SYMBOL) 

69 self.SetMarginMask(2, stc.STC_MASK_FOLDERS) 

70 self.SetMarginSensitive(2, True) 

71 self.SetMarginWidth(2, 24) 

72 

73 if self.fold_symbols == 0: 73 ↛ 75line 73 didn't jump to line 75 because the condition on line 73 was never true

74 # Arrow pointing right for contracted folders, arrow pointing down for expanded 

75 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_ARROWDOWN, "black", "black") 

76 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_ARROW, "black", "black") 

77 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "black", "black") 

78 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "black", "black") 

79 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black") 

80 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black") 

81 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black") 

82 

83 elif self.fold_symbols == 1: 83 ↛ 85line 83 didn't jump to line 85 because the condition on line 83 was never true

84 # Plus for contracted folders, minus for expanded 

85 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_MINUS, "white", "black") 

86 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_PLUS, "white", "black") 

87 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "white", "black") 

88 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "white", "black") 

89 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black") 

90 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black") 

91 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black") 

92 

93 elif self.fold_symbols == 2: 93 ↛ 103line 93 didn't jump to line 103 because the condition on line 93 was always true

94 # Like a flattened tree control using circular headers and curved joins 

95 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_CIRCLEMINUS, "white", "#404040") 

96 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_CIRCLEPLUS, "white", "#404040") 

97 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#404040") 

98 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNERCURVE, "white", "#404040") 

99 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_CIRCLEPLUSCONNECTED, "white", "#404040") 

100 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040") 

101 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNERCURVE, "white", "#404040") 

102 

103 elif self.fold_symbols == 3: 

104 # Like a flattened tree control using square headers 

105 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, "white", "#808080") 

106 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, "white", "#808080") 

107 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#808080") 

108 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, "white", "#808080") 

109 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080") 

110 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080") 

111 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER, "white", "#808080") 

112 

113 """ 

114 self.Bind(stc.EVT_STC_UPDATEUI, self.on_update_ui) 

115 self.Bind(stc.EVT_STC_MARGINCLICK, self.on_margin_click) 

116 self.Bind(wx.EVT_KEY_DOWN, self.on_key_pressed) 

117 """ 

118 

119 # Make some styles, The lexer defines what each style is used for, we 

120 # just have to define what each style looks like. This set is adapted from 

121 # Scintilla sample property files. 

122 

123 # Global default styles for all languages 

124 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(helv)s,size:%(size)d" % faces) 

125 self.StyleClearAll() # Reset all to be like the default 

126 

127 # Global default styles for all languages 

128 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(helv)s,size:%(size)d" % faces) 

129 self.StyleSetSpec(stc.STC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(helv)s,size:%(size2)d" % faces) 

130 self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "face:%(other)s" % faces) 

131 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, "fore:#FFFFFF,back:#0000FF,bold") 

132 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, "fore:#000000,back:#FF0000,bold") 

133 

134 # Python styles 

135 # Default 

136 self.StyleSetSpec(stc.STC_P_DEFAULT, "fore:#000000,face:%(helv)s,size:%(size)d" % faces) 

137 # Comments 

138 self.StyleSetSpec(stc.STC_P_COMMENTLINE, "fore:#007F00,face:%(other)s,size:%(size)d" % faces) 

139 # Number 

140 self.StyleSetSpec(stc.STC_P_NUMBER, "fore:#007F7F,size:%(size)d" % faces) 

141 # String 

142 self.StyleSetSpec(stc.STC_P_STRING, "fore:#7F007F,face:%(helv)s,size:%(size)d" % faces) 

143 # Single quoted string 

144 self.StyleSetSpec(stc.STC_P_CHARACTER, "fore:#7F007F,face:%(helv)s,size:%(size)d" % faces) 

145 # Keyword 

146 self.StyleSetSpec(stc.STC_P_WORD, "fore:#00007F,bold,size:%(size)d" % faces) 

147 # Triple quotes 

148 self.StyleSetSpec(stc.STC_P_TRIPLE, "fore:#7F0000,size:%(size)d" % faces) 

149 # Triple double quotes 

150 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, "fore:#7F0000,size:%(size)d" % faces) 

151 # Class name definition 

152 self.StyleSetSpec(stc.STC_P_CLASSNAME, "fore:#0000FF,bold,underline,size:%(size)d" % faces) 

153 # Function or method name definition 

154 self.StyleSetSpec(stc.STC_P_DEFNAME, "fore:#007F7F,bold,size:%(size)d" % faces) 

155 # Operators 

156 self.StyleSetSpec(stc.STC_P_OPERATOR, "bold,size:%(size)d" % faces) 

157 # Identifiers 

158 self.StyleSetSpec(stc.STC_P_IDENTIFIER, "fore:#000000,face:%(helv)s,size:%(size)d" % faces) 

159 # Comment-blocks 

160 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, "fore:#7F7F7F,size:%(size)d" % faces) 

161 # End of line where string is not closed 

162 self.StyleSetSpec(stc.STC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eol,size:%(size)d" % faces) 

163 

164 # self.SetCaretForeground("BLUE") 

165 

166 # register some images for use in the AutoComplete box. 

167 # self.RegisterImage(1, Smiles.GetBitmap()) # DEBUG was images. 

168 self.RegisterImage(1, wx.ArtProvider.GetBitmap(wx.ART_FLOPPY, size=(16, 16))) 

169 self.RegisterImage(2, wx.ArtProvider.GetBitmap(wx.ART_NEW, size=(16, 16))) 

170 self.RegisterImage(3, wx.ArtProvider.GetBitmap(wx.ART_COPY, size=(16, 16))) 

171 

172 def on_key_pressed(self, event): 1ab

173 if self.CallTipActive(): 

174 self.CallTipCancel() 

175 key = event.GetKeyCode() 

176 

177 if key == 32 and event.ControlDown(): 

178 pos = self.GetCurrentPos() 

179 

180 # Tips 

181 if event.ShiftDown(): 

182 self.CallTipSetBackground("yellow") 

183 self.CallTipShow(pos, 'lots of of text: blah, blah, blah\n\n' 

184 'show some suff, maybe parameters..\n\n' 

185 'fubar(param1, param2)') 

186 # Code completion 

187 else: 

188 kw = list(keyword.kwlist[:]) 

189 kw.append("zzzzzz?2") 

190 kw.append("aaaaa?2") 

191 kw.append("__init__?3") 

192 kw.append("zzaaaaa?2") 

193 kw.append("zzbaaaa?2") 

194 kw.append("this_is_a_longer_value") 

195 # kw.append("this_is_a_much_much_much_much_much_much_much_longer_value") 

196 

197 kw.sort() # Python sorts are case-sensitive 

198 self.AutoCompSetIgnoreCase(False) # so this needs to match 

199 

200 # Images are specified with an appended "?type" 

201 for i in range(len(kw)): 

202 if kw[i] in keyword.kwlist: 

203 kw[i] = kw[i] + "?1" 

204 

205 self.AutoCompShow(0, " ".join(kw)) 

206 else: 

207 event.Skip() 

208 

209 def on_update_ui(self, evt): 1ab

210 _ = evt 

211 # check for matching braces 

212 brace_at_caret = -1 

213 brace_opposite = -1 

214 char_before = style_before = None 

215 caret_pos = self.GetCurrentPos() 

216 

217 if caret_pos > 0: 

218 char_before = self.GetCharAt(caret_pos - 1) 

219 style_before = self.GetStyleAt(caret_pos - 1) 

220 

221 # check before 

222 if char_before and chr(char_before) in "[]{}()" and style_before == stc.STC_P_OPERATOR: 

223 brace_at_caret = caret_pos - 1 

224 

225 # check after 

226 if brace_at_caret < 0: 

227 char_after = self.GetCharAt(caret_pos) 

228 style_after = self.GetStyleAt(caret_pos) 

229 

230 if char_after and chr(char_after) in "[]{}()" and style_after == stc.STC_P_OPERATOR: 

231 brace_at_caret = caret_pos 

232 

233 if brace_at_caret >= 0: 

234 brace_opposite = self.BraceMatch(brace_at_caret) 

235 

236 if brace_at_caret != -1 and brace_opposite == -1: 

237 self.BraceBadLight(brace_at_caret) 

238 else: 

239 self.BraceHighlight(brace_at_caret, brace_opposite) 

240 

241 def on_margin_click(self, evt): 1ab

242 mod = evt.GetModificationType() 

243 # print(f"DEBUG: pythoneditor.py PythonSTC on_margin_click mod={mod} Margin={evt.GetMargin()}") 

244 # fold and unfold as needed 

245 if evt.GetMargin() == 2: 

246 if evt.GetShift() and evt.GetControl(): 

247 self.FoldAll() 

248 else: 

249 line_clicked = self.LineFromPosition(evt.GetPosition()) 

250 

251 if self.GetFoldLevel(line_clicked) & stc.STC_FOLDLEVELHEADERFLAG: 

252 if evt.GetShift(): 

253 self.SetFoldExpanded(line_clicked, True) 

254 self.Expand(line_clicked, True, True, 1) 

255 elif evt.GetControl(): 

256 if self.GetFoldExpanded(line_clicked): 

257 self.SetFoldExpanded(line_clicked, False) 

258 self.Expand(line_clicked, False, True, 0) 

259 else: 

260 self.SetFoldExpanded(line_clicked, True) 

261 self.Expand(line_clicked, True, True, 100) 

262 else: 

263 self.ToggleFold(line_clicked) 

264 

265 def FoldAll(self, action=None): 1ab

266 line_count = self.GetLineCount() 

267 expanding = True 

268 

269 # find out if we are folding or unfolding 

270 for line_num in range(line_count): 

271 if self.GetFoldLevel(line_num) & stc.STC_FOLDLEVELHEADERFLAG: 

272 expanding = not self.GetFoldExpanded(line_num) 

273 break 

274 

275 line_num = 0 

276 

277 while line_num < line_count: 

278 level = self.GetFoldLevel(line_num) 

279 if level & stc.STC_FOLDLEVELHEADERFLAG and \ 

280 (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE: 

281 

282 if expanding: 

283 self.SetFoldExpanded(line_num, True) 

284 line_num = self.Expand(line_num, True) 

285 line_num = line_num - 1 

286 else: 

287 last_child = self.GetLastChild(line_num, -1) 

288 self.SetFoldExpanded(line_num, False) 

289 

290 if last_child > line_num: 

291 self.HideLines(line_num+1, last_child) 

292 

293 line_num = line_num + 1 

294 

295 def Expand(self, line, do_expand, force=False, vis_levels=0, level=-1): 1ab

296 last_child = self.GetLastChild(line, level) 

297 line = line + 1 

298 

299 while line <= last_child: 

300 if force: 

301 if vis_levels > 0: 

302 self.ShowLines(line, line) 

303 else: 

304 self.HideLines(line, line) 

305 else: 

306 if do_expand: 

307 self.ShowLines(line, line) 

308 

309 if level == -1: 

310 level = self.GetFoldLevel(line) 

311 

312 if level & stc.STC_FOLDLEVELHEADERFLAG: 

313 if force: 

314 if vis_levels > 1: 

315 self.SetFoldExpanded(line, True) 

316 else: 

317 self.SetFoldExpanded(line, False) 

318 

319 line = self.Expand(line, do_expand, force, vis_levels - 1) 

320 

321 else: 

322 if do_expand and self.GetFoldExpanded(line): 

323 line = self.Expand(line, True, force, vis_levels - 1) 

324 else: 

325 line = self.Expand(line, False, force, vis_levels - 1) 

326 else: 

327 line = line + 1 

328 

329 return line