root/edit.py

Revision 332:08f97445caca, 7.3 KB (checked in by Ian Ward <ian@…>, 4 days ago)

update examples for unhandled_input change

  • Property exe set to *
Line 
1#!/usr/bin/python
2#
3# Urwid example lazy text editor suitable for tabbed and format=flowed text
4#    Copyright (C) 2004-2009  Ian Ward
5#
6#    This library is free software; you can redistribute it and/or
7#    modify it under the terms of the GNU Lesser General Public
8#    License as published by the Free Software Foundation; either
9#    version 2.1 of the License, or (at your option) any later version.
10#
11#    This library is distributed in the hope that it will be useful,
12#    but WITHOUT ANY WARRANTY; without even the implied warranty of
13#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14#    Lesser General Public License for more details.
15#
16#    You should have received a copy of the GNU Lesser General Public
17#    License along with this library; if not, write to the Free Software
18#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19#
20# Urwid web site: http://excess.org/urwid/
21
22"""
23Urwid example lazy text editor suitable for tabbed and flowing text
24
25Features:
26- custom list walker for lazily loading text file
27
28Usage:
29edit.py <filename>
30
31"""
32
33import sys
34
35import urwid
36
37
38class LineWalker(urwid.ListWalker):
39    """ListWalker-compatible class for lazily reading file contents."""
40   
41    def __init__(self, name):
42        self.file = open(name)
43        self.lines = []
44        self.focus = 0
45   
46    def get_focus(self): 
47        return self._get_at_pos(self.focus)
48   
49    def set_focus(self, focus):
50        self.focus = focus
51        self._modified()
52   
53    def get_next(self, start_from):
54        return self._get_at_pos(start_from + 1)
55   
56    def get_prev(self, start_from):
57        return self._get_at_pos(start_from - 1)
58
59    def read_next_line(self):
60        """Read another line from the file."""
61       
62        next_line = self.file.readline()
63       
64        if not next_line or next_line[-1:] != '\n':
65            # no newline on last line of file
66            self.file = None
67        else:
68            # trim newline characters
69            next_line = next_line[:-1]
70
71        expanded = next_line.expandtabs()
72       
73        edit = urwid.Edit("", expanded, allow_tab=True)
74        edit.set_edit_pos(0)
75        edit.original_text = next_line
76        self.lines.append(edit)
77
78        return next_line
79       
80   
81    def _get_at_pos(self, pos):
82        """Return a widget for the line number passed."""
83       
84        if pos < 0:
85            # line 0 is the start of the file, no more above
86            return None, None
87           
88        if len(self.lines) > pos:
89            # we have that line so return it
90            return self.lines[pos], pos
91
92        if self.file is None:
93            # file is closed, so there are no more lines
94            return None, None
95
96        assert pos == len(self.lines), "out of order request?"
97
98        self.read_next_line()
99       
100        return self.lines[-1], pos
101   
102    def split_focus(self):
103        """Divide the focus edit widget at the cursor location."""
104       
105        focus = self.lines[self.focus]
106        pos = focus.edit_pos
107        edit = urwid.Edit("",focus.edit_text[pos:], allow_tab=True)
108        edit.original_text = ""
109        focus.set_edit_text(focus.edit_text[:pos])
110        edit.set_edit_pos(0)
111        self.lines.insert(self.focus+1, edit)
112
113    def combine_focus_with_prev(self):
114        """Combine the focus edit widget with the one above."""
115
116        above, ignore = self.get_prev(self.focus)
117        if above is None:
118            # already at the top
119            return
120       
121        focus = self.lines[self.focus]
122        above.set_edit_pos(len(above.edit_text))
123        above.set_edit_text(above.edit_text + focus.edit_text)
124        del self.lines[self.focus]
125        self.focus -= 1
126
127    def combine_focus_with_next(self):
128        """Combine the focus edit widget with the one below."""
129
130        below, ignore = self.get_next(self.focus)
131        if below is None:
132            # already at bottom
133            return
134       
135        focus = self.lines[self.focus]
136        focus.set_edit_text(focus.edit_text + below.edit_text)
137        del self.lines[self.focus+1]
138
139
140class EditDisplay:
141    palette = [
142        ('body','default', 'default'),
143        ('foot','dark cyan', 'dark blue', 'bold'),
144        ('key','light cyan', 'dark blue', 'underline'),
145        ]
146       
147    footer_text = ('foot', [
148        "Text Editor    ",
149        ('key', "F5"), " save  ",
150        ('key', "F8"), " quit",
151        ])
152   
153    def __init__(self, name):
154        self.save_name = name
155        self.walker = LineWalker(name) 
156        self.listbox = urwid.ListBox(self.walker)
157        self.footer = urwid.AttrWrap(urwid.Text(self.footer_text),
158            "foot")
159        self.view = urwid.Frame(urwid.AttrWrap(self.listbox, 'body'),
160            footer=self.footer)
161
162    def main(self):
163        self.loop = urwid.MainLoop(self.view, self.palette,
164            unhandled_input=self.unhandled_keypress)
165        self.loop.run()
166   
167    def unhandled_keypress(self, k):
168        """Last resort for keypresses."""
169
170        if k == "f5":
171            self.save_file()
172        elif k == "f8":
173            raise urwid.ExitMainLoop()
174        elif k == "delete":
175            # delete at end of line
176            self.walker.combine_focus_with_next()
177        elif k == "backspace":
178            # backspace at beginning of line
179            self.walker.combine_focus_with_prev()
180        elif k == "enter":
181            # start new line
182            self.walker.split_focus()
183            # move the cursor to the new line and reset pref_col
184            self.loop.process_input(["down", "home"])
185        elif k == "right":
186            w, pos = self.walker.get_focus()
187            w, pos = self.walker.get_next(pos)
188            if w:
189                self.listbox.set_focus(pos, 'above')
190                self.loop.process_input(["home"])
191        elif k == "left":
192            w, pos = self.walker.get_focus()
193            w, pos = self.walker.get_prev(pos)
194            if w:
195                self.listbox.set_focus(pos, 'below')
196                self.loop.process_input(["end"])
197        else:
198            return
199        return True
200           
201
202    def save_file(self):
203        """Write the file out to disk."""
204       
205        l = []
206        walk = self.walker
207        for edit in walk.lines:
208            # collect the text already stored in edit widgets
209            if edit.original_text.expandtabs() == edit.edit_text:
210                l.append(edit.original_text)
211            else:
212                l.append(re_tab(edit.edit_text))
213       
214        # then the rest
215        while walk.file is not None:
216            l.append(walk.read_next_line())
217           
218        # write back to disk
219        outfile = open(self.save_name, "w")
220       
221        prefix = ""
222        for line in l:
223            outfile.write(prefix + line)
224            prefix = "\n"
225
226def re_tab(s):
227    """Return a tabbed string from an expanded one."""
228    l = []
229    p = 0
230    for i in range(8, len(s), 8):
231        if s[i-2:i] == "  ":
232            # collapse two or more spaces into a tab
233            l.append(s[p:i].rstrip() + "\t")
234            p = i
235
236    if p == 0:
237        return s
238    else:
239        l.append(s[p:])
240        return "".join(l)
241
242
243
244def main():
245    try:
246        name = sys.argv[1]
247        assert open(name, "a")
248    except:
249        sys.stderr.write(__doc__)
250        return
251    EditDisplay(name).main()
252   
253
254if __name__=="__main__": 
255    main()
Note: See TracBrowser for help on using the browser.