ยปCore Development>Code coverage>Lib/curses/textpad.py

Python code coverage for Lib/curses/textpad.py

#countcontent
1n/a"""Simple textbox editing widget with Emacs-like keybindings."""
2n/a
3n/aimport curses
4n/aimport curses.ascii
5n/a
6n/adef rectangle(win, uly, ulx, lry, lrx):
7n/a """Draw a rectangle with corners at the provided upper-left
8n/a and lower-right coordinates.
9n/a """
10n/a win.vline(uly+1, ulx, curses.ACS_VLINE, lry - uly - 1)
11n/a win.hline(uly, ulx+1, curses.ACS_HLINE, lrx - ulx - 1)
12n/a win.hline(lry, ulx+1, curses.ACS_HLINE, lrx - ulx - 1)
13n/a win.vline(uly+1, lrx, curses.ACS_VLINE, lry - uly - 1)
14n/a win.addch(uly, ulx, curses.ACS_ULCORNER)
15n/a win.addch(uly, lrx, curses.ACS_URCORNER)
16n/a win.addch(lry, lrx, curses.ACS_LRCORNER)
17n/a win.addch(lry, ulx, curses.ACS_LLCORNER)
18n/a
19n/aclass Textbox:
20n/a """Editing widget using the interior of a window object.
21n/a Supports the following Emacs-like key bindings:
22n/a
23n/a Ctrl-A Go to left edge of window.
24n/a Ctrl-B Cursor left, wrapping to previous line if appropriate.
25n/a Ctrl-D Delete character under cursor.
26n/a Ctrl-E Go to right edge (stripspaces off) or end of line (stripspaces on).
27n/a Ctrl-F Cursor right, wrapping to next line when appropriate.
28n/a Ctrl-G Terminate, returning the window contents.
29n/a Ctrl-H Delete character backward.
30n/a Ctrl-J Terminate if the window is 1 line, otherwise insert newline.
31n/a Ctrl-K If line is blank, delete it, otherwise clear to end of line.
32n/a Ctrl-L Refresh screen.
33n/a Ctrl-N Cursor down; move down one line.
34n/a Ctrl-O Insert a blank line at cursor location.
35n/a Ctrl-P Cursor up; move up one line.
36n/a
37n/a Move operations do nothing if the cursor is at an edge where the movement
38n/a is not possible. The following synonyms are supported where possible:
39n/a
40n/a KEY_LEFT = Ctrl-B, KEY_RIGHT = Ctrl-F, KEY_UP = Ctrl-P, KEY_DOWN = Ctrl-N
41n/a KEY_BACKSPACE = Ctrl-h
42n/a """
43n/a def __init__(self, win, insert_mode=False):
44n/a self.win = win
45n/a self.insert_mode = insert_mode
46n/a self._update_max_yx()
47n/a self.stripspaces = 1
48n/a self.lastcmd = None
49n/a win.keypad(1)
50n/a
51n/a def _update_max_yx(self):
52n/a maxy, maxx = self.win.getmaxyx()
53n/a self.maxy = maxy - 1
54n/a self.maxx = maxx - 1
55n/a
56n/a def _end_of_line(self, y):
57n/a """Go to the location of the first blank on the given line,
58n/a returning the index of the last non-blank character."""
59n/a self._update_max_yx()
60n/a last = self.maxx
61n/a while True:
62n/a if curses.ascii.ascii(self.win.inch(y, last)) != curses.ascii.SP:
63n/a last = min(self.maxx, last+1)
64n/a break
65n/a elif last == 0:
66n/a break
67n/a last = last - 1
68n/a return last
69n/a
70n/a def _insert_printable_char(self, ch):
71n/a self._update_max_yx()
72n/a (y, x) = self.win.getyx()
73n/a backyx = None
74n/a while y < self.maxy or x < self.maxx:
75n/a if self.insert_mode:
76n/a oldch = self.win.inch()
77n/a # The try-catch ignores the error we trigger from some curses
78n/a # versions by trying to write into the lowest-rightmost spot
79n/a # in the window.
80n/a try:
81n/a self.win.addch(ch)
82n/a except curses.error:
83n/a pass
84n/a if not self.insert_mode or not curses.ascii.isprint(oldch):
85n/a break
86n/a ch = oldch
87n/a (y, x) = self.win.getyx()
88n/a # Remember where to put the cursor back since we are in insert_mode
89n/a if backyx is None:
90n/a backyx = y, x
91n/a
92n/a if backyx is not None:
93n/a self.win.move(*backyx)
94n/a
95n/a def do_command(self, ch):
96n/a "Process a single editing command."
97n/a self._update_max_yx()
98n/a (y, x) = self.win.getyx()
99n/a self.lastcmd = ch
100n/a if curses.ascii.isprint(ch):
101n/a if y < self.maxy or x < self.maxx:
102n/a self._insert_printable_char(ch)
103n/a elif ch == curses.ascii.SOH: # ^a
104n/a self.win.move(y, 0)
105n/a elif ch in (curses.ascii.STX,curses.KEY_LEFT, curses.ascii.BS,curses.KEY_BACKSPACE):
106n/a if x > 0:
107n/a self.win.move(y, x-1)
108n/a elif y == 0:
109n/a pass
110n/a elif self.stripspaces:
111n/a self.win.move(y-1, self._end_of_line(y-1))
112n/a else:
113n/a self.win.move(y-1, self.maxx)
114n/a if ch in (curses.ascii.BS, curses.KEY_BACKSPACE):
115n/a self.win.delch()
116n/a elif ch == curses.ascii.EOT: # ^d
117n/a self.win.delch()
118n/a elif ch == curses.ascii.ENQ: # ^e
119n/a if self.stripspaces:
120n/a self.win.move(y, self._end_of_line(y))
121n/a else:
122n/a self.win.move(y, self.maxx)
123n/a elif ch in (curses.ascii.ACK, curses.KEY_RIGHT): # ^f
124n/a if x < self.maxx:
125n/a self.win.move(y, x+1)
126n/a elif y == self.maxy:
127n/a pass
128n/a else:
129n/a self.win.move(y+1, 0)
130n/a elif ch == curses.ascii.BEL: # ^g
131n/a return 0
132n/a elif ch == curses.ascii.NL: # ^j
133n/a if self.maxy == 0:
134n/a return 0
135n/a elif y < self.maxy:
136n/a self.win.move(y+1, 0)
137n/a elif ch == curses.ascii.VT: # ^k
138n/a if x == 0 and self._end_of_line(y) == 0:
139n/a self.win.deleteln()
140n/a else:
141n/a # first undo the effect of self._end_of_line
142n/a self.win.move(y, x)
143n/a self.win.clrtoeol()
144n/a elif ch == curses.ascii.FF: # ^l
145n/a self.win.refresh()
146n/a elif ch in (curses.ascii.SO, curses.KEY_DOWN): # ^n
147n/a if y < self.maxy:
148n/a self.win.move(y+1, x)
149n/a if x > self._end_of_line(y+1):
150n/a self.win.move(y+1, self._end_of_line(y+1))
151n/a elif ch == curses.ascii.SI: # ^o
152n/a self.win.insertln()
153n/a elif ch in (curses.ascii.DLE, curses.KEY_UP): # ^p
154n/a if y > 0:
155n/a self.win.move(y-1, x)
156n/a if x > self._end_of_line(y-1):
157n/a self.win.move(y-1, self._end_of_line(y-1))
158n/a return 1
159n/a
160n/a def gather(self):
161n/a "Collect and return the contents of the window."
162n/a result = ""
163n/a self._update_max_yx()
164n/a for y in range(self.maxy+1):
165n/a self.win.move(y, 0)
166n/a stop = self._end_of_line(y)
167n/a if stop == 0 and self.stripspaces:
168n/a continue
169n/a for x in range(self.maxx+1):
170n/a if self.stripspaces and x > stop:
171n/a break
172n/a result = result + chr(curses.ascii.ascii(self.win.inch(y, x)))
173n/a if self.maxy > 0:
174n/a result = result + "\n"
175n/a return result
176n/a
177n/a def edit(self, validate=None):
178n/a "Edit in the widget window and collect the results."
179n/a while 1:
180n/a ch = self.win.getch()
181n/a if validate:
182n/a ch = validate(ch)
183n/a if not ch:
184n/a continue
185n/a if not self.do_command(ch):
186n/a break
187n/a self.win.refresh()
188n/a return self.gather()
189n/a
190n/aif __name__ == '__main__':
191n/a def test_editbox(stdscr):
192n/a ncols, nlines = 9, 4
193n/a uly, ulx = 15, 20
194n/a stdscr.addstr(uly-2, ulx, "Use Ctrl-G to end editing.")
195n/a win = curses.newwin(nlines, ncols, uly, ulx)
196n/a rectangle(stdscr, uly-1, ulx-1, uly + nlines, ulx + ncols)
197n/a stdscr.refresh()
198n/a return Textbox(win).edit()
199n/a
200n/a str = curses.wrapper(test_editbox)
201n/a print('Contents of text box:', repr(str))