~ubuntu-branches/ubuntu/karmic/pypy/karmic

« back to all changes in this revision

Viewing changes to lib-python/2.4.1/idlelib/IOBinding.py

  • Committer: Bazaar Package Importer
  • Author(s): Alexandre Fayolle
  • Date: 2007-04-13 09:33:09 UTC
  • Revision ID: james.westby@ubuntu.com-20070413093309-yoojh4jcoocu2krz
Tags: upstream-1.0.0
ImportĀ upstreamĀ versionĀ 1.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# changes by dscherer@cmu.edu
 
2
#   - IOBinding.open() replaces the current window with the opened file,
 
3
#     if the current window is both unmodified and unnamed
 
4
#   - IOBinding.loadfile() interprets Windows, UNIX, and Macintosh
 
5
#     end-of-line conventions, instead of relying on the standard library,
 
6
#     which will only understand the local convention.
 
7
 
 
8
import os
 
9
import types
 
10
import sys
 
11
import codecs
 
12
import tempfile
 
13
import tkFileDialog
 
14
import tkMessageBox
 
15
import re
 
16
from Tkinter import *
 
17
from SimpleDialog import SimpleDialog
 
18
 
 
19
from configHandler import idleConf
 
20
 
 
21
try:
 
22
    from codecs import BOM_UTF8
 
23
except ImportError:
 
24
    # only available since Python 2.3
 
25
    BOM_UTF8 = '\xef\xbb\xbf'
 
26
 
 
27
# Try setting the locale, so that we can find out
 
28
# what encoding to use
 
29
try:
 
30
    import locale
 
31
    locale.setlocale(locale.LC_CTYPE, "")
 
32
except (ImportError, locale.Error):
 
33
    pass
 
34
 
 
35
encoding = "ascii"
 
36
if sys.platform == 'win32':
 
37
    # On Windows, we could use "mbcs". However, to give the user
 
38
    # a portable encoding name, we need to find the code page
 
39
    try:
 
40
        encoding = locale.getdefaultlocale()[1]
 
41
        codecs.lookup(encoding)
 
42
    except LookupError:
 
43
        pass
 
44
else:
 
45
    try:
 
46
        # Different things can fail here: the locale module may not be
 
47
        # loaded, it may not offer nl_langinfo, or CODESET, or the
 
48
        # resulting codeset may be unknown to Python. We ignore all
 
49
        # these problems, falling back to ASCII
 
50
        encoding = locale.nl_langinfo(locale.CODESET)
 
51
        if encoding is None or encoding is '':
 
52
            # situation occurs on Mac OS X
 
53
            encoding = 'ascii'
 
54
        codecs.lookup(encoding)
 
55
    except (NameError, AttributeError, LookupError):
 
56
        # Try getdefaultlocale well: it parses environment variables,
 
57
        # which may give a clue. Unfortunately, getdefaultlocale has
 
58
        # bugs that can cause ValueError.
 
59
        try:
 
60
            encoding = locale.getdefaultlocale()[1]
 
61
            if encoding is None or encoding is '':
 
62
                # situation occurs on Mac OS X
 
63
                encoding = 'ascii'
 
64
            codecs.lookup(encoding)
 
65
        except (ValueError, LookupError):
 
66
            pass
 
67
 
 
68
encoding = encoding.lower()
 
69
 
 
70
coding_re = re.compile("coding[:=]\s*([-\w_.]+)")
 
71
 
 
72
class EncodingMessage(SimpleDialog):
 
73
    "Inform user that an encoding declaration is needed."
 
74
    def __init__(self, master, enc):
 
75
        self.should_edit = False
 
76
 
 
77
        self.root = top = Toplevel(master)
 
78
        top.bind("<Return>", self.return_event)
 
79
        top.bind("<Escape>", self.do_ok)
 
80
        top.protocol("WM_DELETE_WINDOW", self.wm_delete_window)
 
81
        top.wm_title("I/O Warning")
 
82
        top.wm_iconname("I/O Warning")
 
83
        self.top = top
 
84
 
 
85
        l1 = Label(top,
 
86
            text="Non-ASCII found, yet no encoding declared. Add a line like")
 
87
        l1.pack(side=TOP, anchor=W)
 
88
        l2 = Entry(top, font="courier")
 
89
        l2.insert(0, "# -*- coding: %s -*-" % enc)
 
90
        # For some reason, the text is not selectable anymore if the
 
91
        # widget is disabled.
 
92
        # l2['state'] = DISABLED
 
93
        l2.pack(side=TOP, anchor = W, fill=X)
 
94
        l3 = Label(top, text="to your file\n"
 
95
                   "Choose OK to save this file as %s\n"
 
96
                   "Edit your general options to silence this warning" % enc)
 
97
        l3.pack(side=TOP, anchor = W)
 
98
 
 
99
        buttons = Frame(top)
 
100
        buttons.pack(side=TOP, fill=X)
 
101
        # Both return and cancel mean the same thing: do nothing
 
102
        self.default = self.cancel = 0
 
103
        b1 = Button(buttons, text="Ok", default="active",
 
104
                    command=self.do_ok)
 
105
        b1.pack(side=LEFT, fill=BOTH, expand=1)
 
106
        b2 = Button(buttons, text="Edit my file",
 
107
                    command=self.do_edit)
 
108
        b2.pack(side=LEFT, fill=BOTH, expand=1)
 
109
 
 
110
        self._set_transient(master)
 
111
 
 
112
    def do_ok(self):
 
113
        self.done(0)
 
114
 
 
115
    def do_edit(self):
 
116
        self.done(1)
 
117
 
 
118
def coding_spec(str):
 
119
    """Return the encoding declaration according to PEP 263.
 
120
 
 
121
    Raise LookupError if the encoding is declared but unknown.
 
122
    """
 
123
    # Only consider the first two lines
 
124
    str = str.split("\n")[:2]
 
125
    str = "\n".join(str)
 
126
 
 
127
    match = coding_re.search(str)
 
128
    if not match:
 
129
        return None
 
130
    name = match.group(1)
 
131
    # Check whether the encoding is known
 
132
    import codecs
 
133
    try:
 
134
        codecs.lookup(name)
 
135
    except LookupError:
 
136
        # The standard encoding error does not indicate the encoding
 
137
        raise LookupError, "Unknown encoding "+name
 
138
    return name
 
139
 
 
140
 
 
141
class IOBinding:
 
142
 
 
143
    def __init__(self, editwin):
 
144
        self.editwin = editwin
 
145
        self.text = editwin.text
 
146
        self.__id_open = self.text.bind("<<open-window-from-file>>", self.open)
 
147
        self.__id_save = self.text.bind("<<save-window>>", self.save)
 
148
        self.__id_saveas = self.text.bind("<<save-window-as-file>>",
 
149
                                          self.save_as)
 
150
        self.__id_savecopy = self.text.bind("<<save-copy-of-window-as-file>>",
 
151
                                            self.save_a_copy)
 
152
        self.fileencoding = None
 
153
        self.__id_print = self.text.bind("<<print-window>>", self.print_window)
 
154
 
 
155
    def close(self):
 
156
        # Undo command bindings
 
157
        self.text.unbind("<<open-window-from-file>>", self.__id_open)
 
158
        self.text.unbind("<<save-window>>", self.__id_save)
 
159
        self.text.unbind("<<save-window-as-file>>",self.__id_saveas)
 
160
        self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy)
 
161
        self.text.unbind("<<print-window>>", self.__id_print)
 
162
        # Break cycles
 
163
        self.editwin = None
 
164
        self.text = None
 
165
        self.filename_change_hook = None
 
166
 
 
167
    def get_saved(self):
 
168
        return self.editwin.get_saved()
 
169
 
 
170
    def set_saved(self, flag):
 
171
        self.editwin.set_saved(flag)
 
172
 
 
173
    def reset_undo(self):
 
174
        self.editwin.reset_undo()
 
175
 
 
176
    filename_change_hook = None
 
177
 
 
178
    def set_filename_change_hook(self, hook):
 
179
        self.filename_change_hook = hook
 
180
 
 
181
    filename = None
 
182
    dirname = None
 
183
 
 
184
    def set_filename(self, filename):
 
185
        if filename and os.path.isdir(filename):
 
186
            self.filename = None
 
187
            self.dirname = filename
 
188
        else:
 
189
            self.filename = filename
 
190
            self.dirname = None
 
191
            self.set_saved(1)
 
192
            if self.filename_change_hook:
 
193
                self.filename_change_hook()
 
194
 
 
195
    def open(self, event=None, editFile=None):
 
196
        if self.editwin.flist:
 
197
            if not editFile:
 
198
                filename = self.askopenfile()
 
199
            else:
 
200
                filename=editFile
 
201
            if filename:
 
202
                # If the current window has no filename and hasn't been
 
203
                # modified, we replace its contents (no loss).  Otherwise
 
204
                # we open a new window.  But we won't replace the
 
205
                # shell window (which has an interp(reter) attribute), which
 
206
                # gets set to "not modified" at every new prompt.
 
207
                try:
 
208
                    interp = self.editwin.interp
 
209
                except:
 
210
                    interp = None
 
211
                if not self.filename and self.get_saved() and not interp:
 
212
                    self.editwin.flist.open(filename, self.loadfile)
 
213
                else:
 
214
                    self.editwin.flist.open(filename)
 
215
            else:
 
216
                self.text.focus_set()
 
217
            return "break"
 
218
        #
 
219
        # Code for use outside IDLE:
 
220
        if self.get_saved():
 
221
            reply = self.maybesave()
 
222
            if reply == "cancel":
 
223
                self.text.focus_set()
 
224
                return "break"
 
225
        if not editFile:
 
226
            filename = self.askopenfile()
 
227
        else:
 
228
            filename=editFile
 
229
        if filename:
 
230
            self.loadfile(filename)
 
231
        else:
 
232
            self.text.focus_set()
 
233
        return "break"
 
234
 
 
235
    eol = r"(\r\n)|\n|\r"  # \r\n (Windows), \n (UNIX), or \r (Mac)
 
236
    eol_re = re.compile(eol)
 
237
    eol_convention = os.linesep # Default
 
238
 
 
239
    def loadfile(self, filename):
 
240
        try:
 
241
            # open the file in binary mode so that we can handle
 
242
            #   end-of-line convention ourselves.
 
243
            f = open(filename,'rb')
 
244
            chars = f.read()
 
245
            f.close()
 
246
        except IOError, msg:
 
247
            tkMessageBox.showerror("I/O Error", str(msg), master=self.text)
 
248
            return False
 
249
 
 
250
        chars = self.decode(chars)
 
251
        # We now convert all end-of-lines to '\n's
 
252
        firsteol = self.eol_re.search(chars)
 
253
        if firsteol:
 
254
            self.eol_convention = firsteol.group(0)
 
255
            if isinstance(self.eol_convention, unicode):
 
256
                # Make sure it is an ASCII string
 
257
                self.eol_convention = self.eol_convention.encode("ascii")
 
258
            chars = self.eol_re.sub(r"\n", chars)
 
259
 
 
260
        self.text.delete("1.0", "end")
 
261
        self.set_filename(None)
 
262
        self.text.insert("1.0", chars)
 
263
        self.reset_undo()
 
264
        self.set_filename(filename)
 
265
        self.text.mark_set("insert", "1.0")
 
266
        self.text.see("insert")
 
267
        self.updaterecentfileslist(filename)
 
268
        return True
 
269
 
 
270
    def decode(self, chars):
 
271
        """Create a Unicode string
 
272
 
 
273
        If that fails, let Tcl try its best
 
274
        """
 
275
        # Check presence of a UTF-8 signature first
 
276
        if chars.startswith(BOM_UTF8):
 
277
            try:
 
278
                chars = chars[3:].decode("utf-8")
 
279
            except UnicodeError:
 
280
                # has UTF-8 signature, but fails to decode...
 
281
                return chars
 
282
            else:
 
283
                # Indicates that this file originally had a BOM
 
284
                self.fileencoding = BOM_UTF8
 
285
                return chars
 
286
        # Next look for coding specification
 
287
        try:
 
288
            enc = coding_spec(chars)
 
289
        except LookupError, name:
 
290
            tkMessageBox.showerror(
 
291
                title="Error loading the file",
 
292
                message="The encoding '%s' is not known to this Python "\
 
293
                "installation. The file may not display correctly" % name,
 
294
                master = self.text)
 
295
            enc = None
 
296
        if enc:
 
297
            try:
 
298
                return unicode(chars, enc)
 
299
            except UnicodeError:
 
300
                pass
 
301
        # If it is ASCII, we need not to record anything
 
302
        try:
 
303
            return unicode(chars, 'ascii')
 
304
        except UnicodeError:
 
305
            pass
 
306
        # Finally, try the locale's encoding. This is deprecated;
 
307
        # the user should declare a non-ASCII encoding
 
308
        try:
 
309
            chars = unicode(chars, encoding)
 
310
            self.fileencoding = encoding
 
311
        except UnicodeError:
 
312
            pass
 
313
        return chars
 
314
 
 
315
    def maybesave(self):
 
316
        if self.get_saved():
 
317
            return "yes"
 
318
        message = "Do you want to save %s before closing?" % (
 
319
            self.filename or "this untitled document")
 
320
        m = tkMessageBox.Message(
 
321
            title="Save On Close",
 
322
            message=message,
 
323
            icon=tkMessageBox.QUESTION,
 
324
            type=tkMessageBox.YESNOCANCEL,
 
325
            master=self.text)
 
326
        reply = m.show()
 
327
        if reply == "yes":
 
328
            self.save(None)
 
329
            if not self.get_saved():
 
330
                reply = "cancel"
 
331
        self.text.focus_set()
 
332
        return reply
 
333
 
 
334
    def save(self, event):
 
335
        if not self.filename:
 
336
            self.save_as(event)
 
337
        else:
 
338
            if self.writefile(self.filename):
 
339
                self.set_saved(1)
 
340
                try:
 
341
                    self.editwin.store_file_breaks()
 
342
                except AttributeError:  # may be a PyShell
 
343
                    pass
 
344
        self.text.focus_set()
 
345
        return "break"
 
346
 
 
347
    def save_as(self, event):
 
348
        filename = self.asksavefile()
 
349
        if filename:
 
350
            if self.writefile(filename):
 
351
                self.set_filename(filename)
 
352
                self.set_saved(1)
 
353
                try:
 
354
                    self.editwin.store_file_breaks()
 
355
                except AttributeError:
 
356
                    pass
 
357
        self.text.focus_set()
 
358
        self.updaterecentfileslist(filename)
 
359
        return "break"
 
360
 
 
361
    def save_a_copy(self, event):
 
362
        filename = self.asksavefile()
 
363
        if filename:
 
364
            self.writefile(filename)
 
365
        self.text.focus_set()
 
366
        self.updaterecentfileslist(filename)
 
367
        return "break"
 
368
 
 
369
    def writefile(self, filename):
 
370
        self.fixlastline()
 
371
        chars = self.encode(self.text.get("1.0", "end-1c"))
 
372
        if self.eol_convention != "\n":
 
373
            chars = chars.replace("\n", self.eol_convention)
 
374
        try:
 
375
            f = open(filename, "wb")
 
376
            f.write(chars)
 
377
            f.close()
 
378
            return True
 
379
        except IOError, msg:
 
380
            tkMessageBox.showerror("I/O Error", str(msg),
 
381
                                   master=self.text)
 
382
            return False
 
383
 
 
384
    def encode(self, chars):
 
385
        if isinstance(chars, types.StringType):
 
386
            # This is either plain ASCII, or Tk was returning mixed-encoding
 
387
            # text to us. Don't try to guess further.
 
388
            return chars
 
389
        # See whether there is anything non-ASCII in it.
 
390
        # If not, no need to figure out the encoding.
 
391
        try:
 
392
            return chars.encode('ascii')
 
393
        except UnicodeError:
 
394
            pass
 
395
        # If there is an encoding declared, try this first.
 
396
        try:
 
397
            enc = coding_spec(chars)
 
398
            failed = None
 
399
        except LookupError, msg:
 
400
            failed = msg
 
401
            enc = None
 
402
        if enc:
 
403
            try:
 
404
                return chars.encode(enc)
 
405
            except UnicodeError:
 
406
                failed = "Invalid encoding '%s'" % enc
 
407
        if failed:
 
408
            tkMessageBox.showerror(
 
409
                "I/O Error",
 
410
                "%s. Saving as UTF-8" % failed,
 
411
                master = self.text)
 
412
        # If there was a UTF-8 signature, use that. This should not fail
 
413
        if self.fileencoding == BOM_UTF8 or failed:
 
414
            return BOM_UTF8 + chars.encode("utf-8")
 
415
        # Try the original file encoding next, if any
 
416
        if self.fileencoding:
 
417
            try:
 
418
                return chars.encode(self.fileencoding)
 
419
            except UnicodeError:
 
420
                tkMessageBox.showerror(
 
421
                    "I/O Error",
 
422
                    "Cannot save this as '%s' anymore. Saving as UTF-8" \
 
423
                    % self.fileencoding,
 
424
                    master = self.text)
 
425
                return BOM_UTF8 + chars.encode("utf-8")
 
426
        # Nothing was declared, and we had not determined an encoding
 
427
        # on loading. Recommend an encoding line.
 
428
        config_encoding = idleConf.GetOption("main","EditorWindow",
 
429
                                             "encoding")
 
430
        if config_encoding == 'utf-8':
 
431
            # User has requested that we save files as UTF-8
 
432
            return BOM_UTF8 + chars.encode("utf-8")
 
433
        ask_user = True
 
434
        try:
 
435
            chars = chars.encode(encoding)
 
436
            enc = encoding
 
437
            if config_encoding == 'locale':
 
438
                ask_user = False
 
439
        except UnicodeError:
 
440
            chars = BOM_UTF8 + chars.encode("utf-8")
 
441
            enc = "utf-8"
 
442
        if not ask_user:
 
443
            return chars
 
444
        dialog = EncodingMessage(self.editwin.top, enc)
 
445
        dialog.go()
 
446
        if dialog.num == 1:
 
447
            # User asked us to edit the file
 
448
            encline = "# -*- coding: %s -*-\n" % enc
 
449
            firstline = self.text.get("1.0", "2.0")
 
450
            if firstline.startswith("#!"):
 
451
                # Insert encoding after #! line
 
452
                self.text.insert("2.0", encline)
 
453
            else:
 
454
                self.text.insert("1.0", encline)
 
455
            return self.encode(self.text.get("1.0", "end-1c"))
 
456
        return chars
 
457
 
 
458
    def fixlastline(self):
 
459
        c = self.text.get("end-2c")
 
460
        if c != '\n':
 
461
            self.text.insert("end-1c", "\n")
 
462
 
 
463
    def print_window(self, event):
 
464
        tempfilename = None
 
465
        saved = self.get_saved()
 
466
        if saved:
 
467
            filename = self.filename
 
468
        # shell undo is reset after every prompt, looks saved, probably isn't
 
469
        if not saved or filename is None:
 
470
            # XXX KBK 08Jun03 Wouldn't it be better to ask the user to save?
 
471
            (tfd, tempfilename) = tempfile.mkstemp(prefix='IDLE_tmp_')
 
472
            filename = tempfilename
 
473
            os.close(tfd)
 
474
            if not self.writefile(tempfilename):
 
475
                os.unlink(tempfilename)
 
476
                return "break"
 
477
        platform=os.name
 
478
        printPlatform=1
 
479
        if platform == 'posix': #posix platform
 
480
            command = idleConf.GetOption('main','General',
 
481
                                         'print-command-posix')
 
482
            command = command + " 2>&1"
 
483
        elif platform == 'nt': #win32 platform
 
484
            command = idleConf.GetOption('main','General','print-command-win')
 
485
        else: #no printing for this platform
 
486
            printPlatform=0
 
487
        if printPlatform:  #we can try to print for this platform
 
488
            command = command % filename
 
489
            pipe = os.popen(command, "r")
 
490
            # things can get ugly on NT if there is no printer available.
 
491
            output = pipe.read().strip()
 
492
            status = pipe.close()
 
493
            if status:
 
494
                output = "Printing failed (exit status 0x%x)\n" % \
 
495
                         status + output
 
496
            if output:
 
497
                output = "Printing command: %s\n" % repr(command) + output
 
498
                tkMessageBox.showerror("Print status", output, master=self.text)
 
499
        else:  #no printing for this platform
 
500
            message="Printing is not enabled for this platform: %s" % platform
 
501
            tkMessageBox.showinfo("Print status", message, master=self.text)
 
502
        if tempfilename:
 
503
            os.unlink(tempfilename)
 
504
        return "break"
 
505
 
 
506
    opendialog = None
 
507
    savedialog = None
 
508
 
 
509
    filetypes = [
 
510
        ("Python and text files", "*.py *.pyw *.txt", "TEXT"),
 
511
        ("All text files", "*", "TEXT"),
 
512
        ("All files", "*"),
 
513
        ]
 
514
 
 
515
    def askopenfile(self):
 
516
        dir, base = self.defaultfilename("open")
 
517
        if not self.opendialog:
 
518
            self.opendialog = tkFileDialog.Open(master=self.text,
 
519
                                                filetypes=self.filetypes)
 
520
        return self.opendialog.show(initialdir=dir, initialfile=base)
 
521
 
 
522
    def defaultfilename(self, mode="open"):
 
523
        if self.filename:
 
524
            return os.path.split(self.filename)
 
525
        elif self.dirname:
 
526
            return self.dirname, ""
 
527
        else:
 
528
            try:
 
529
                pwd = os.getcwd()
 
530
            except os.error:
 
531
                pwd = ""
 
532
            return pwd, ""
 
533
 
 
534
    def asksavefile(self):
 
535
        dir, base = self.defaultfilename("save")
 
536
        if not self.savedialog:
 
537
            self.savedialog = tkFileDialog.SaveAs(master=self.text,
 
538
                                                  filetypes=self.filetypes)
 
539
        return self.savedialog.show(initialdir=dir, initialfile=base)
 
540
 
 
541
    def updaterecentfileslist(self,filename):
 
542
        "Update recent file list on all editor windows"
 
543
        self.editwin.update_recent_files_list(filename)
 
544
 
 
545
def test():
 
546
    root = Tk()
 
547
    class MyEditWin:
 
548
        def __init__(self, text):
 
549
            self.text = text
 
550
            self.flist = None
 
551
            self.text.bind("<Control-o>", self.open)
 
552
            self.text.bind("<Control-s>", self.save)
 
553
            self.text.bind("<Alt-s>", self.save_as)
 
554
            self.text.bind("<Alt-z>", self.save_a_copy)
 
555
        def get_saved(self): return 0
 
556
        def set_saved(self, flag): pass
 
557
        def reset_undo(self): pass
 
558
        def open(self, event):
 
559
            self.text.event_generate("<<open-window-from-file>>")
 
560
        def save(self, event):
 
561
            self.text.event_generate("<<save-window>>")
 
562
        def save_as(self, event):
 
563
            self.text.event_generate("<<save-window-as-file>>")
 
564
        def save_a_copy(self, event):
 
565
            self.text.event_generate("<<save-copy-of-window-as-file>>")
 
566
    text = Text(root)
 
567
    text.pack()
 
568
    text.focus_set()
 
569
    editwin = MyEditWin(text)
 
570
    io = IOBinding(editwin)
 
571
    root.mainloop()
 
572
 
 
573
if __name__ == "__main__":
 
574
    test()