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
« 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 >>
12def cmd(name):
13 """Command decorator for the KillBufferCommandsClass class."""
14 return g.new_cmd_decorator(name, ['c', 'killBufferCommands',])
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()
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)
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)
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.
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
143 #@-others
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)
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)
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)
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