1 """Simple textbox editing widget with Emacs-like keybindings."""
6 def rectangle(win
, uly
, ulx
, lry
, lrx
):
7 """Draw a rectangle with corners at the provided upper-left
8 and lower-right coordinates.
10 win
.vline(uly
+1, ulx
, curses
.ACS_VLINE
, lry
- uly
- 1)
11 win
.hline(uly
, ulx
+1, curses
.ACS_HLINE
, lrx
- ulx
- 1)
12 win
.hline(lry
, ulx
+1, curses
.ACS_HLINE
, lrx
- ulx
- 1)
13 win
.vline(uly
+1, lrx
, curses
.ACS_VLINE
, lry
- uly
- 1)
14 win
.addch(uly
, ulx
, curses
.ACS_ULCORNER
)
15 win
.addch(uly
, lrx
, curses
.ACS_URCORNER
)
16 win
.addch(lry
, lrx
, curses
.ACS_LRCORNER
)
17 win
.addch(lry
, ulx
, curses
.ACS_LLCORNER
)
20 """Editing widget using the interior of a window object.
21 Supports the following Emacs-like key bindings:
23 Ctrl-A Go to left edge of window.
24 Ctrl-B Cursor left, wrapping to previous line if appropriate.
25 Ctrl-D Delete character under cursor.
26 Ctrl-E Go to right edge (stripspaces off) or end of line (stripspaces on).
27 Ctrl-F Cursor right, wrapping to next line when appropriate.
28 Ctrl-G Terminate, returning the window contents.
29 Ctrl-H Delete character backward.
30 Ctrl-J Terminate if the window is 1 line, otherwise insert newline.
31 Ctrl-K If line is blank, delete it, otherwise clear to end of line.
32 Ctrl-L Refresh screen.
33 Ctrl-N Cursor down; move down one line.
34 Ctrl-O Insert a blank line at cursor location.
35 Ctrl-P Cursor up; move up one line.
37 Move operations do nothing if the cursor is at an edge where the movement
38 is not possible. The following synonyms are supported where possible:
40 KEY_LEFT = Ctrl-B, KEY_RIGHT = Ctrl-F, KEY_UP = Ctrl-P, KEY_DOWN = Ctrl-N
41 KEY_BACKSPACE = Ctrl-h
43 def __init__(self
, win
, insert_mode
=False):
45 self
.insert_mode
= insert_mode
46 (self
.maxy
, self
.maxx
) = win
.getmaxyx()
47 self
.maxy
= self
.maxy
- 1
48 self
.maxx
= self
.maxx
- 1
53 def _end_of_line(self
, y
):
54 """Go to the location of the first blank on the given line,
55 returning the index of the last non-blank character."""
58 if curses
.ascii
.ascii(self
.win
.inch(y
, last
)) != curses
.ascii
.SP
:
59 last
= min(self
.maxx
, last
+1)
66 def _insert_printable_char(self
, ch
):
67 (y
, x
) = self
.win
.getyx()
68 if y
< self
.maxy
or x
< self
.maxx
:
70 oldch
= self
.win
.inch()
71 # The try-catch ignores the error we trigger from some curses
72 # versions by trying to write into the lowest-rightmost spot
79 (backy
, backx
) = self
.win
.getyx()
80 if curses
.ascii
.isprint(oldch
):
81 self
._insert
_printable
_char
(oldch
)
82 self
.win
.move(backy
, backx
)
84 def do_command(self
, ch
):
85 "Process a single editing command."
86 (y
, x
) = self
.win
.getyx()
88 if curses
.ascii
.isprint(ch
):
89 if y
< self
.maxy
or x
< self
.maxx
:
90 self
._insert
_printable
_char
(ch
)
91 elif ch
== curses
.ascii
.SOH
: # ^a
93 elif ch
in (curses
.ascii
.STX
,curses
.KEY_LEFT
, curses
.ascii
.BS
,curses
.KEY_BACKSPACE
):
98 elif self
.stripspaces
:
99 self
.win
.move(y
-1, self
._end
_of
_line
(y
-1))
101 self
.win
.move(y
-1, self
.maxx
)
102 if ch
in (curses
.ascii
.BS
, curses
.KEY_BACKSPACE
):
104 elif ch
== curses
.ascii
.EOT
: # ^d
106 elif ch
== curses
.ascii
.ENQ
: # ^e
108 self
.win
.move(y
, self
._end
_of
_line
(y
))
110 self
.win
.move(y
, self
.maxx
)
111 elif ch
in (curses
.ascii
.ACK
, curses
.KEY_RIGHT
): # ^f
113 self
.win
.move(y
, x
+1)
117 self
.win
.move(y
+1, 0)
118 elif ch
== curses
.ascii
.BEL
: # ^g
120 elif ch
== curses
.ascii
.NL
: # ^j
124 self
.win
.move(y
+1, 0)
125 elif ch
== curses
.ascii
.VT
: # ^k
126 if x
== 0 and self
._end
_of
_line
(y
) == 0:
129 # first undo the effect of self._end_of_line
132 elif ch
== curses
.ascii
.FF
: # ^l
134 elif ch
in (curses
.ascii
.SO
, curses
.KEY_DOWN
): # ^n
136 self
.win
.move(y
+1, x
)
137 if x
> self
._end
_of
_line
(y
+1):
138 self
.win
.move(y
+1, self
._end
_of
_line
(y
+1))
139 elif ch
== curses
.ascii
.SI
: # ^o
141 elif ch
in (curses
.ascii
.DLE
, curses
.KEY_UP
): # ^p
143 self
.win
.move(y
-1, x
)
144 if x
> self
._end
_of
_line
(y
-1):
145 self
.win
.move(y
-1, self
._end
_of
_line
(y
-1))
149 "Collect and return the contents of the window."
151 for y
in range(self
.maxy
+1):
153 stop
= self
._end
_of
_line
(y
)
154 if stop
== 0 and self
.stripspaces
:
156 for x
in range(self
.maxx
+1):
157 if self
.stripspaces
and x
> stop
:
159 result
= result
+ chr(curses
.ascii
.ascii(self
.win
.inch(y
, x
)))
161 result
= result
+ "\n"
164 def edit(self
, validate
=None):
165 "Edit in the widget window and collect the results."
167 ch
= self
.win
.getch()
172 if not self
.do_command(ch
):
177 if __name__
== '__main__':
178 def test_editbox(stdscr
):
181 stdscr
.addstr(uly
-2, ulx
, "Use Ctrl-G to end editing.")
182 win
= curses
.newwin(nlines
, ncols
, uly
, ulx
)
183 rectangle(stdscr
, uly
-1, ulx
-1, uly
+ nlines
, ulx
+ ncols
)
185 return Textbox(win
).edit()
187 str = curses
.wrapper(test_editbox
)
188 print 'Contents of text box:', repr(str)