Coverage for C:\Repos\leo-editor\leo\commands\killBufferCommands.py: 53%

288 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-24 10:21 -0500

1# -*- coding: utf-8 -*- 

2#@+leo-ver=5-thin 

3#@+node:ekr.20150514040142.1: * @file ../commands/killBufferCommands.py 

4#@@first 

5"""Leo's kill-buffer commands.""" 

6#@+<< imports >> 

7#@+node:ekr.20150514050411.1: ** << imports >> (killBufferCommands.py) 

8from leo.core import leoGlobals as g 

9from leo.commands.baseCommands import BaseEditCommandsClass 

10#@-<< imports >> 

11 

12def cmd(name): 

13 """Command decorator for the KillBufferCommandsClass class.""" 

14 return g.new_cmd_decorator(name, ['c', 'killBufferCommands',]) 

15 

16#@+others 

17#@+node:ekr.20160514120919.1: ** class KillBufferCommandsClass 

18class KillBufferCommandsClass(BaseEditCommandsClass): 

19 """A class to manage the kill buffer.""" 

20 #@+others 

21 #@+node:ekr.20150514063305.409: *3* kill.ctor & reloadSettings 

22 def __init__(self, c): 

23 """Ctor for KillBufferCommandsClass class.""" 

24 # pylint: disable=super-init-not-called 

25 self.c = c 

26 self.kbiterator = self.iterateKillBuffer() 

27 # For interacting with system clipboard. 

28 self.last_clipboard = None 

29 # Position of the last item returned by iterateKillBuffer. 

30 self.lastYankP = None 

31 # The index of the next item to be returned in 

32 # g.app.globalKillBuffer by iterateKillBuffer. 

33 self.reset = None 

34 self.reloadSettings() 

35 

36 def reloadSettings(self): 

37 """KillBufferCommandsClass.reloadSettings.""" 

38 c = self.c 

39 self.addWsToKillRing = c.config.getBool('add-ws-to-kill-ring') 

40 #@+node:ekr.20150514063305.411: *3* addToKillBuffer 

41 def addToKillBuffer(self, text): 

42 """ 

43 Insert the text into the kill buffer if force is True or 

44 the text contains something other than whitespace. 

45 """ 

46 if self.addWsToKillRing or text.strip(): 

47 g.app.globalKillBuffer = [z for z in g.app.globalKillBuffer if z != text] 

48 g.app.globalKillBuffer.insert(0, text) 

49 #@+node:ekr.20150514063305.412: *3* backwardKillSentence 

50 @cmd('backward-kill-sentence') 

51 def backwardKillSentence(self, event): 

52 """Kill the previous sentence.""" 

53 w = self.editWidget(event) 

54 if not w: 

55 return 

56 s = w.getAllText() 

57 ins = w.getInsertPoint() 

58 i = s.rfind('.', ins) 

59 if i == -1: 

60 return 

61 undoType = 'backward-kill-sentence' 

62 self.beginCommand(w, undoType=undoType) 

63 i2 = s.rfind('.', 0, i) + 1 

64 self.killHelper(event, i2, i + 1, w, undoType=undoType) 

65 w.setInsertPoint(i2) 

66 self.endCommand(changed=True, setLabel=True) 

67 #@+node:ekr.20150514063305.413: *3* backwardKillWord & killWord 

68 @cmd('backward-kill-word') 

69 def backwardKillWord(self, event): 

70 """Kill the previous word.""" 

71 c = self.c 

72 w = self.editWidget(event) 

73 if w: 

74 self.beginCommand(w, undoType='backward-kill-word') 

75 c.editCommands.backwardWord(event) 

76 self.killWordHelper(event) 

77 

78 @cmd('kill-word') 

79 def killWord(self, event): 

80 """Kill the word containing the cursor.""" 

81 w = self.editWidget(event) 

82 if w: 

83 self.beginCommand(w, undoType='kill-word') 

84 self.killWordHelper(event) 

85 

86 def killWordHelper(self, event): 

87 c = self.c 

88 e = c.editCommands 

89 w = e.editWidget(event) 

90 if w: 

91 # self.killWs(event) 

92 e.extendToWord(event) 

93 i, j = w.getSelectionRange() 

94 self.killHelper(event, i, j, w) 

95 self.endCommand(changed=True, setLabel=True) 

96 #@+node:ekr.20150514063305.414: *3* clearKillRing 

97 @cmd('clear-kill-ring') 

98 def clearKillRing(self, event=None): 

99 """Clear the kill ring.""" 

100 g.app.globalKillbuffer = [] 

101 #@+node:ekr.20150514063305.415: *3* getClipboard 

102 def getClipboard(self): 

103 """Return the contents of the clipboard.""" 

104 try: 

105 ctxt = g.app.gui.getTextFromClipboard() 

106 if not g.app.globalKillBuffer or ctxt != self.last_clipboard: 

107 self.last_clipboard = ctxt 

108 if not g.app.globalKillBuffer or g.app.globalKillBuffer[0] != ctxt: 

109 return ctxt 

110 except Exception: 

111 g.es_exception() 

112 return None 

113 #@+node:ekr.20150514063305.416: *3* class iterateKillBuffer 

114 class KillBufferIterClass: 

115 """Returns a list of positions in a subtree, possibly including the root of the subtree.""" 

116 #@+others 

117 #@+node:ekr.20150514063305.417: *4* __init__ & __iter__ (iterateKillBuffer) 

118 def __init__(self, c): 

119 """Ctor for KillBufferIterClass class.""" 

120 self.c = c 

121 self.index = 0 # The index of the next item to be returned. 

122 

123 def __iter__(self): 

124 return self 

125 #@+node:ekr.20150514063305.418: *4* __next__ 

126 def __next__(self): 

127 commands = self.c.killBufferCommands 

128 aList = g.app.globalKillBuffer # commands.killBuffer 

129 if not aList: 

130 self.index = 0 

131 return None 

132 if commands.reset is None: 

133 i = self.index 

134 else: 

135 i = commands.reset 

136 commands.reset = None 

137 if i < 0 or i >= len(aList): 

138 i = 0 

139 val = aList[i] 

140 self.index = i + 1 

141 return val 

142 

143 #@-others 

144 

145 def iterateKillBuffer(self): 

146 return self.KillBufferIterClass(self.c) 

147 #@+node:ekr.20150514063305.419: *3* ec.killHelper 

148 def killHelper(self, event, frm, to, w, undoType=None): 

149 """ 

150 A helper method for all kill commands except kill-paragraph commands. 

151 """ 

152 c = self.c 

153 w = self.editWidget(event) 

154 if not w: 

155 return 

156 # Extend (frm, to) if it spans a line. 

157 i, j = w.getSelectionRange() 

158 s = w.get(i, j) 

159 if s.find('\n') > -1: 

160 frm, to = i, j 

161 s = w.get(frm, to) 

162 if undoType: 

163 self.beginCommand(w, undoType=undoType) 

164 self.addToKillBuffer(s) 

165 g.app.gui.replaceClipboardWith(s) 

166 w.delete(frm, to) 

167 w.setInsertPoint(frm) 

168 if undoType: 

169 self.endCommand(changed=True, setLabel=True) 

170 g.app.gui.set_focus(c, w) # 2607 

171 #@+node:ekr.20220121073752.1: *3* ec.killParagraphHelper 

172 def killParagraphHelper(self, event, frm, to, undoType=None): 

173 """A helper method for kill-paragraph commands.""" 

174 w = self.editWidget(event) 

175 if not w: 

176 return 

177 s = w.get(frm, to) 

178 if undoType: 

179 self.beginCommand(w, undoType=undoType) 

180 self.addToKillBuffer(s) 

181 g.app.gui.replaceClipboardWith(s) 

182 w.delete(frm, to) 

183 w.setInsertPoint(frm) 

184 if undoType: 

185 self.endCommand(changed=True, setLabel=True) 

186 #@+node:ekr.20150514063305.420: *3* ec.killToEndOfLine 

187 @cmd('kill-to-end-of-line') 

188 def killToEndOfLine(self, event): 

189 """Kill from the cursor to end of the line.""" 

190 w = self.editWidget(event) 

191 if not w: 

192 return 

193 s = w.getAllText() 

194 ins = w.getInsertPoint() 

195 i, j = g.getLine(s, ins) 

196 if ins >= len(s) and g.match(s, j - 1, '\n'): 

197 # Kill the trailing newline of the body text. 

198 i = max(0, len(s) - 1) 

199 j = len(s) 

200 elif ins + 1 < j and s[ins : j - 1].strip() and g.match(s, j - 1, '\n'): 

201 # Kill the line, but not the newline. 

202 i, j = ins, j - 1 

203 elif g.match(s, j - 1, '\n'): 

204 i = ins # Kill the newline in the present line. 

205 else: 

206 i = j 

207 if i < j: 

208 self.killHelper(event, i, j, w, undoType='kill-to-end-of-line') 

209 #@+node:ekr.20150514063305.421: *3* ec.killLine 

210 @cmd('kill-line') 

211 def killLine(self, event): 

212 """Kill the line containing the cursor.""" 

213 w = self.editWidget(event) 

214 if not w: 

215 return 

216 s = w.getAllText() 

217 ins = w.getInsertPoint() 

218 i, j = g.getLine(s, ins) 

219 if ins >= len(s) and g.match(s, j - 1, '\n'): 

220 # Kill the trailing newline of the body text. 

221 i = max(0, len(s) - 1) 

222 j = len(s) 

223 elif j > i + 1 and g.match(s, j - 1, '\n'): 

224 # Kill the line, but not the newline. 

225 j -= 1 

226 else: 

227 pass # Kill the newline in the present line. 

228 self.killHelper(event, i, j, w, undoType='kill-line') 

229 #@+node:ekr.20150514063305.422: *3* killRegion & killRegionSave 

230 @cmd('kill-region') 

231 def killRegion(self, event): 

232 """Kill the text selection.""" 

233 w = self.editWidget(event) 

234 if not w: 

235 return 

236 i, j = w.getSelectionRange() 

237 if i == j: 

238 return 

239 s = w.getSelectedText() 

240 self.beginCommand(w, undoType='kill-region') 

241 w.delete(i, j) 

242 self.endCommand(changed=True, setLabel=True) 

243 self.addToKillBuffer(s) 

244 g.app.gui.replaceClipboardWith(s) 

245 

246 @cmd('kill-region-save') 

247 def killRegionSave(self, event): 

248 """Add the selected text to the kill ring, but do not delete it.""" 

249 w = self.editWidget(event) 

250 if not w: 

251 return 

252 i, j = w.getSelectionRange() 

253 if i == j: 

254 return 

255 s = w.getSelectedText() 

256 self.addToKillBuffer(s) 

257 g.app.gui.replaceClipboardWith(s) 

258 #@+node:ekr.20150514063305.423: *3* ec.killSentence 

259 @cmd('kill-sentence') 

260 def killSentence(self, event): 

261 """Kill the sentence containing the cursor.""" 

262 w = self.editWidget(event) 

263 if not w: 

264 return 

265 s = w.getAllText() 

266 ins = w.getInsertPoint() 

267 i = s.find('.', ins) 

268 if i == -1: 

269 return 

270 undoType = 'kill-sentence' 

271 self.beginCommand(w, undoType=undoType) 

272 i2 = s.rfind('.', 0, ins) + 1 

273 self.killHelper(event, i2, i + 1, w, undoType=undoType) 

274 w.setInsertPoint(i2) 

275 self.endCommand(changed=True, setLabel=True) 

276 #@+node:ekr.20150514063305.424: *3* killWs 

277 @cmd('kill-ws') 

278 def killWs(self, event, undoType='kill-ws'): 

279 """Kill whitespace.""" 

280 ws = '' 

281 w = self.editWidget(event) 

282 if not w: 

283 return 

284 s = w.getAllText() 

285 i = j = ins = w.getInsertPoint() 

286 while i >= 0 and s[i] in (' ', '\t'): 

287 i -= 1 

288 if i < ins: 

289 i += 1 

290 while j < len(s) and s[j] in (' ', '\t'): 

291 j += 1 

292 if j > i: 

293 ws = s[i:j] 

294 w.delete(i, j) 

295 if undoType: 

296 self.beginCommand(w, undoType=undoType) 

297 if self.addWsToKillRing: 

298 self.addToKillBuffer(ws) 

299 if undoType: 

300 self.endCommand(changed=True, setLabel=True) 

301 #@+node:ekr.20150514063305.425: *3* yank & yankPop 

302 @cmd('yank') 

303 @cmd('yank') 

304 def yank(self, event=None): 

305 """Insert the next entry of the kill ring.""" 

306 self.yankHelper(event, pop=False) 

307 

308 @cmd('yank-pop') 

309 def yankPop(self, event=None): 

310 """Insert the first entry of the kill ring.""" 

311 self.yankHelper(event, pop=True) 

312 

313 def yankHelper(self, event, pop): 

314 """ 

315 Helper for yank and yank-pop: 

316 pop = False: insert the first entry of the kill ring. 

317 pop = True: insert the next entry of the kill ring. 

318 """ 

319 c = self.c 

320 w = self.editWidget(event) 

321 if not w: 

322 return 

323 current = c.p 

324 if not current: 

325 return 

326 text = w.getAllText() 

327 i, j = w.getSelectionRange() 

328 clip_text = self.getClipboard() 

329 if not g.app.globalKillBuffer and not clip_text: 

330 return 

331 undoType = 'yank-pop' if pop else 'yank' 

332 self.beginCommand(w, undoType=undoType) 

333 try: 

334 if not pop or self.lastYankP and self.lastYankP != current: 

335 self.reset = 0 

336 s = self.kbiterator.__next__() 

337 if s is None: 

338 s = clip_text or '' 

339 if i != j: 

340 w.deleteTextSelection() 

341 if s != s.lstrip(): # s contains leading whitespace. 

342 i2, j2 = g.getLine(text, i) 

343 k = g.skip_ws(text, i2) 

344 if i2 < i <= k: 

345 # Replace the line's leading whitespace by s's leading whitespace. 

346 w.delete(i2, k) 

347 i = i2 

348 w.insert(i, s) 

349 # Fix bug 1099035: Leo yank and kill behaviour not quite the same as emacs. 

350 # w.setSelectionRange(i,i+len(s),insert=i+len(s)) 

351 w.setInsertPoint(i + len(s)) 

352 self.lastYankP = current.copy() 

353 finally: 

354 self.endCommand(changed=True, setLabel=True) 

355 #@+node:ekr.20150514063305.427: *3* zapToCharacter 

356 @cmd('zap-to-character') 

357 def zapToCharacter(self, event): 

358 """Kill characters from the insertion point to a given character.""" 

359 k = self.c.k 

360 w = self.editWidget(event) 

361 if not w: 

362 return 

363 state = k.getState('zap-to-char') 

364 if state == 0: 

365 k.setLabelBlue('Zap To Character: ') 

366 k.setState('zap-to-char', 1, handler=self.zapToCharacter) 

367 else: 

368 ch = event.char if event else ' ' 

369 k.resetLabel() 

370 k.clearState() 

371 s = w.getAllText() 

372 ins = w.getInsertPoint() 

373 i = s.find(ch, ins) 

374 if i == -1: 

375 return 

376 self.beginCommand(w, undoType='zap-to-char') 

377 self.addToKillBuffer(s[ins:i]) 

378 g.app.gui.replaceClipboardWith(s[ins:i]) # Support for proper yank. 

379 w.setAllText(s[:ins] + s[i:]) 

380 w.setInsertPoint(ins) 

381 self.endCommand(changed=True, setLabel=True) 

382 #@-others 

383#@-others 

384#@-leo