Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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 self.last_clipboard = None 

28 # For interacting with system clipboard. 

29 self.lastYankP = None 

30 # Position of the last item returned by iterateKillBuffer. 

31 self.reset = None 

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

33 # g.app.globalKillBuffer by iterateKillBuffer. 

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

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, undoType=None): 

149 """ 

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

151 """ 

152 w = self.editWidget(event) 

153 if not w: 

154 return 

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

156 i, j = w.getSelectionRange() 

157 s = w.get(i, j) 

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

159 frm, to = i, j 

160 s = w.get(frm, to) 

161 if undoType: 

162 self.beginCommand(w, undoType=undoType) 

163 self.addToKillBuffer(s) 

164 g.app.gui.replaceClipboardWith(s) 

165 w.delete(frm, to) 

166 w.setInsertPoint(frm) 

167 if undoType: 

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

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

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

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

172 w = self.editWidget(event) 

173 if not w: 

174 return 

175 s = w.get(frm, to) 

176 if undoType: 

177 self.beginCommand(w, undoType=undoType) 

178 self.addToKillBuffer(s) 

179 g.app.gui.replaceClipboardWith(s) 

180 w.delete(frm, to) 

181 w.setInsertPoint(frm) 

182 if undoType: 

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

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

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

186 def killToEndOfLine(self, event): 

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

188 w = self.editWidget(event) 

189 if not w: 

190 return 

191 s = w.getAllText() 

192 ins = w.getInsertPoint() 

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

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

195 # Kill the trailing newline of the body text. 

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

197 j = len(s) 

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

199 # Kill the line, but not the newline. 

200 i, j = ins, j - 1 

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

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

203 else: 

204 i = j 

205 if i < j: 

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

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

208 @cmd('kill-line') 

209 def killLine(self, event): 

210 """Kill the line containing the cursor.""" 

211 w = self.editWidget(event) 

212 if not w: 

213 return 

214 s = w.getAllText() 

215 ins = w.getInsertPoint() 

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

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

218 # Kill the trailing newline of the body text. 

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

220 j = len(s) 

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

222 # Kill the line, but not the newline. 

223 j -= 1 

224 else: 

225 pass # Kill the newline in the present line. 

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

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

228 @cmd('kill-region') 

229 def killRegion(self, event): 

230 """Kill the text selection.""" 

231 w = self.editWidget(event) 

232 if not w: 

233 return 

234 i, j = w.getSelectionRange() 

235 if i == j: 

236 return 

237 s = w.getSelectedText() 

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

239 w.delete(i, j) 

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

241 self.addToKillBuffer(s) 

242 g.app.gui.replaceClipboardWith(s) 

243 

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

245 def killRegionSave(self, event): 

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

247 w = self.editWidget(event) 

248 if not w: 

249 return 

250 i, j = w.getSelectionRange() 

251 if i == j: 

252 return 

253 s = w.getSelectedText() 

254 self.addToKillBuffer(s) 

255 g.app.gui.replaceClipboardWith(s) 

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

257 @cmd('kill-sentence') 

258 def killSentence(self, event): 

259 """Kill the sentence containing the cursor.""" 

260 w = self.editWidget(event) 

261 if not w: 

262 return 

263 s = w.getAllText() 

264 ins = w.getInsertPoint() 

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

266 if i == -1: 

267 return 

268 undoType = 'kill-sentence' 

269 self.beginCommand(w, undoType=undoType) 

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

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

272 w.setInsertPoint(i2) 

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

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

275 @cmd('kill-ws') 

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

277 """Kill whitespace.""" 

278 ws = '' 

279 w = self.editWidget(event) 

280 if not w: 

281 return 

282 s = w.getAllText() 

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

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

285 i -= 1 

286 if i < ins: 

287 i += 1 

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

289 j += 1 

290 if j > i: 

291 ws = s[i:j] 

292 w.delete(i, j) 

293 if undoType: 

294 self.beginCommand(w, undoType=undoType) 

295 if self.addWsToKillRing: 

296 self.addToKillBuffer(ws) 

297 if undoType: 

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

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

300 @cmd('yank') 

301 @cmd('yank') 

302 def yank(self, event=None): 

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

304 self.yankHelper(event, pop=False) 

305 

306 @cmd('yank-pop') 

307 def yankPop(self, event=None): 

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

309 self.yankHelper(event, pop=True) 

310 

311 def yankHelper(self, event, pop): 

312 """ 

313 Helper for yank and yank-pop: 

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

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

316 """ 

317 c = self.c 

318 w = self.editWidget(event) 

319 if not w: 

320 return 

321 current = c.p 

322 if not current: 

323 return 

324 text = w.getAllText() 

325 i, j = w.getSelectionRange() 

326 clip_text = self.getClipboard() 

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

328 return 

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

330 self.beginCommand(w, undoType=undoType) 

331 try: 

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

333 self.reset = 0 

334 s = self.kbiterator.__next__() 

335 if s is None: 

336 s = clip_text or '' 

337 if i != j: 

338 w.deleteTextSelection() 

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

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

341 k = g.skip_ws(text, i2) 

342 if i2 < i <= k: 

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

344 w.delete(i2, k) 

345 i = i2 

346 w.insert(i, s) 

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

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

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

350 self.lastYankP = current.copy() 

351 finally: 

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

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

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

355 def zapToCharacter(self, event): 

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

357 k = self.c.k 

358 w = self.editWidget(event) 

359 if not w: 

360 return 

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

362 if state == 0: 

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

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

365 else: 

366 ch = event.char if event else ' ' 

367 k.resetLabel() 

368 k.clearState() 

369 s = w.getAllText() 

370 ins = w.getInsertPoint() 

371 i = s.find(ch, ins) 

372 if i == -1: 

373 return 

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

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

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

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

378 w.setInsertPoint(ins) 

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

380 #@-others 

381#@-others 

382#@-leo