EditorWindow.py 23.1 KB
Newer Older
1 2 3
import sys
import os
import string
4
import re
5
import imp
6
from Tkinter import *
7
import tkSimpleDialog
8
import tkMessageBox
9 10

import webbrowser
Guido van Rossum's avatar
Guido van Rossum committed
11
import idlever
12
import WindowList
13
from IdleConf import idleconf
14

15 16 17
# The default tab setting for a Text widget, in average-width characters.
TK_TABWIDTH_DEFAULT = 8

Guido van Rossum's avatar
Guido van Rossum committed
18 19 20 21 22 23 24 25 26 27
# File menu

#$ event <<open-module>>
#$ win <Alt-m>
#$ unix <Control-x><Control-m>

#$ event <<open-class-browser>>
#$ win <Alt-c>
#$ unix <Control-x><Control-b>

28 29
#$ event <<open-path-browser>>

Guido van Rossum's avatar
Guido van Rossum committed
30
#$ event <<close-window>>
31

Guido van Rossum's avatar
Guido van Rossum committed
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
#$ unix <Control-x><Control-0>
#$ unix <Control-x><Key-0>
#$ win <Alt-F4>

# Edit menu

#$ event <<Copy>>
#$ win <Control-c>
#$ unix <Alt-w>

#$ event <<Cut>>
#$ win <Control-x>
#$ unix <Control-w>

#$ event <<Paste>>
#$ win <Control-v>
#$ unix <Control-y>

#$ event <<select-all>>
#$ win <Alt-a>
#$ unix <Alt-a>

# Help menu

#$ event <<help>>
#$ win <F1>
#$ unix <F1>

#$ event <<about-idle>>

# Events without menu entries

#$ event <<remove-selection>>
#$ win <Escape>

#$ event <<center-insert>>
#$ win <Control-l>
#$ unix <Control-l>

#$ event <<do-nothing>>
#$ unix <Control-x>

74

75 76
about_title = "About IDLE"
about_text = """\
Guido van Rossum's avatar
Guido van Rossum committed
77
IDLE %s
78

Guido van Rossum's avatar
Guido van Rossum committed
79
An Integrated DeveLopment Environment for Python
80 81

by Guido van Rossum
Guido van Rossum's avatar
Guido van Rossum committed
82
""" % idlever.IDLE_VERSION
83 84 85 86 87 88 89 90

class EditorWindow:

    from Percolator import Percolator
    from ColorDelegator import ColorDelegator
    from UndoDelegator import UndoDelegator
    from IOBinding import IOBinding
    import Bindings
Guido van Rossum's avatar
Guido van Rossum committed
91
    from Tkinter import Toplevel
92
    from MultiStatusBar import MultiStatusBar
Guido van Rossum's avatar
Guido van Rossum committed
93

94
    about_title = about_title
Guido van Rossum's avatar
Guido van Rossum committed
95
    about_text = about_text
96

97 98
    vars = {}

Guido van Rossum's avatar
Guido van Rossum committed
99
    def __init__(self, flist=None, filename=None, key=None, root=None):
100 101
        edconf = idleconf.getsection('EditorWindow')
        coconf = idleconf.getsection('Colors')
Guido van Rossum's avatar
Guido van Rossum committed
102 103
        self.flist = flist
        root = root or flist.root
104
        self.root = root
105 106
        if flist:
            self.vars = flist.vars
107
        self.menubar = Menu(root)
Guido van Rossum's avatar
Guido van Rossum committed
108
        self.top = top = self.Toplevel(root, menu=self.menubar)
109
        self.vbar = vbar = Scrollbar(top, name='vbar')
110 111
        self.text_frame = text_frame = Frame(top)
        self.text = text = Text(text_frame, name='text', padx=5,
112 113 114 115 116 117 118 119
                      foreground=coconf.getdef('normal-foreground'),
                      background=coconf.getdef('normal-background'),
                      highlightcolor=coconf.getdef('hilite-foreground'),
                      highlightbackground=coconf.getdef('hilite-background'),
                      insertbackground=coconf.getdef('cursor-background'),
                      width=edconf.getint('width'),
                      height=edconf.getint('height'),
                      wrap="none")
120

121
        self.createmenubar()
122
        self.apply_bindings()
123 124 125

        self.top.protocol("WM_DELETE_WINDOW", self.close)
        self.top.bind("<<close-window>>", self.close_event)
Guido van Rossum's avatar
Guido van Rossum committed
126 127
        text.bind("<<center-insert>>", self.center_insert_event)
        text.bind("<<help>>", self.help_dialog)
128
        text.bind("<<python-docs>>", self.python_docs)
Guido van Rossum's avatar
Guido van Rossum committed
129 130 131 132 133 134 135 136 137 138 139 140 141
        text.bind("<<about-idle>>", self.about_dialog)
        text.bind("<<open-module>>", self.open_module)
        text.bind("<<do-nothing>>", lambda event: "break")
        text.bind("<<select-all>>", self.select_all)
        text.bind("<<remove-selection>>", self.remove_selection)
        text.bind("<3>", self.right_menu_event)
        if flist:
            flist.inversedict[self] = key
            if key:
                flist.dict[key] = self
            text.bind("<<open-new-window>>", self.flist.new_callback)
            text.bind("<<close-all-windows>>", self.flist.close_all_callback)
            text.bind("<<open-class-browser>>", self.open_class_browser)
142
            text.bind("<<open-path-browser>>", self.open_path_browser)
143 144 145 146 147

        vbar['command'] = text.yview
        vbar.pack(side=RIGHT, fill=Y)

        text['yscrollcommand'] = vbar.set
148
        text['font'] = edconf.get('font-name'), edconf.get('font-size')
149 150
        text_frame.pack(side=LEFT, fill=BOTH, expand=1)
        text.pack(side=TOP, fill=BOTH, expand=1)
151 152 153 154 155 156 157 158 159 160
        text.focus_set()

        self.per = per = self.Percolator(text)
        if self.ispythonsource(filename):
            self.color = color = self.ColorDelegator(); per.insertfilter(color)
            ##print "Initial colorizer"
        else:
            ##print "No initial colorizer"
            self.color = None
        self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
Guido van Rossum's avatar
Guido van Rossum committed
161
        self.io = io = self.IOBinding(self)
162

Guido van Rossum's avatar
Guido van Rossum committed
163 164
        text.undo_block_start = undo.undo_block_start
        text.undo_block_stop = undo.undo_block_stop
165 166 167 168 169 170 171 172 173 174 175
        undo.set_saved_change_hook(self.saved_change_hook)
        io.set_filename_change_hook(self.filename_change_hook)

        if filename:
            if os.path.exists(filename):
                io.loadfile(filename)
            else:
                io.set_filename(filename)

        self.saved_change_hook()

Guido van Rossum's avatar
Guido van Rossum committed
176 177 178 179 180 181 182 183 184 185 186
        self.load_extensions()

        menu = self.menudict.get('windows')
        if menu:
            end = menu.index("end")
            if end is None:
                end = -1
            if end >= 0:
                menu.add_separator()
                end = end + 1
            self.wmenu_end = end
187
            WindowList.register_callback(self.postwindowsmenu)
Guido van Rossum's avatar
Guido van Rossum committed
188

189 190 191 192 193
        # Some abstractions so IDLE extensions are cross-IDE
        self.askyesno = tkMessageBox.askyesno
        self.askinteger = tkSimpleDialog.askinteger
        self.showerror = tkMessageBox.showerror

194 195 196
        if self.extensions.has_key('AutoIndent'):
            self.extensions['AutoIndent'].set_indentation_params(
                self.ispythonsource(filename))
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
        self.set_status_bar()

    def set_status_bar(self):
        self.status_bar = self.MultiStatusBar(self.text_frame)
        self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
        self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
        self.status_bar.pack(side=BOTTOM, fill=X)
        self.text.bind('<KeyRelease>', self.set_line_and_column)
        self.text.bind('<ButtonRelease>', self.set_line_and_column)
        self.text.after_idle(self.set_line_and_column)

    def set_line_and_column(self, event=None):
        line, column = string.split(self.text.index(INSERT), '.')
        self.status_bar.set_label('column', 'Col: %s' % column)
        self.status_bar.set_label('line', 'Ln: %s' % line)
212

Guido van Rossum's avatar
Guido van Rossum committed
213
    def wakeup(self):
214 215 216 217
        if self.top.wm_state() == "iconic":
            self.top.wm_deiconify()
        else:
            self.top.tkraise()
Guido van Rossum's avatar
Guido van Rossum committed
218 219
        self.text.focus_set()

220
    menu_specs = [
221 222
        ("file", "_File"),
        ("edit", "_Edit"),
Guido van Rossum's avatar
Guido van Rossum committed
223
        ("windows", "_Windows"),
224
        ("help", "_Help"),
225 226
    ]

227 228
    def createmenubar(self):
        mbar = self.menubar
229
        self.menudict = menudict = {}
230
        for name, label in self.menu_specs:
231 232
            underline, label = prepstr(label)
            menudict[name] = menu = Menu(mbar, name=name)
233
            mbar.add_cascade(label=label, menu=menu, underline=underline)
234
        self.fill_menus()
235

Guido van Rossum's avatar
Guido van Rossum committed
236 237
    def postwindowsmenu(self):
        # Only called when Windows menu exists
238 239 240
        # XXX Actually, this Just-In-Time updating interferes badly
        # XXX with the tear-off feature.  It would be better to update
        # XXX all Windows menus whenever the list of windows changes.
Guido van Rossum's avatar
Guido van Rossum committed
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
        menu = self.menudict['windows']
        end = menu.index("end")
        if end is None:
            end = -1
        if end > self.wmenu_end:
            menu.delete(self.wmenu_end+1, end)
        WindowList.add_windows_to_menu(menu)

    rmenu = None

    def right_menu_event(self, event):
        self.text.tag_remove("sel", "1.0", "end")
        self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
        if not self.rmenu:
            self.make_rmenu()
        rmenu = self.rmenu
        self.event = event
        iswin = sys.platform[:3] == 'win'
        if iswin:
            self.text.config(cursor="arrow")
        rmenu.tk_popup(event.x_root, event.y_root)
        if iswin:
            self.text.config(cursor="ibeam")

    rmenu_specs = [
        # ("Label", "<<virtual-event>>"), ...
        ("Close", "<<close-window>>"), # Example
    ]

    def make_rmenu(self):
        rmenu = Menu(self.text, tearoff=0)
        for label, eventname in self.rmenu_specs:
            def command(text=self.text, eventname=eventname):
                text.event_generate(eventname)
            rmenu.add_command(label=label, command=command)
        self.rmenu = rmenu

278
    def about_dialog(self, event=None):
279 280 281
        tkMessageBox.showinfo(self.about_title, self.about_text,
                              master=self.text)

Guido van Rossum's avatar
Guido van Rossum committed
282 283
    helpfile = "help.txt"

284
    def help_dialog(self, event=None):
285 286 287 288
        try:
            helpfile = os.path.join(os.path.dirname(__file__), self.helpfile)
        except NameError:
            helpfile = self.helpfile
Guido van Rossum's avatar
Guido van Rossum committed
289 290 291 292 293
        if self.flist:
            self.flist.open(helpfile)
        else:
            self.io.loadfile(helpfile)

294
    help_url = "http://www.python.org/doc/current/"
Fred Drake's avatar
Fred Drake committed
295
    if sys.platform[:3] == "win":
296
        fn = os.path.dirname(__file__)
297
        fn = os.path.join(fn, os.pardir, os.pardir, "pythlp.chm")
298
        fn = os.path.normpath(fn)
Fred Drake's avatar
Fred Drake committed
299 300
        if os.path.isfile(fn):
            help_url = fn
301 302 303 304 305 306
        else:
            fn = os.path.dirname(__file__)
            fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
            fn = os.path.normpath(fn)
            if os.path.isfile(fn):
                help_url = fn
Fred Drake's avatar
Fred Drake committed
307
        del fn
308

309 310 311 312 313
        def python_docs(self, event=None):
            os.startfile(self.help_url)
    else:
        def python_docs(self, event=None):
            webbrowser.open(self.help_url)
314

Guido van Rossum's avatar
Guido van Rossum committed
315 316 317 318 319 320 321 322 323 324
    def select_all(self, event=None):
        self.text.tag_add("sel", "1.0", "end-1c")
        self.text.mark_set("insert", "1.0")
        self.text.see("insert")
        return "break"

    def remove_selection(self, event=None):
        self.text.tag_remove("sel", "1.0", "end")
        self.text.see("insert")

325
    def open_module(self, event=None):
Guido van Rossum's avatar
Guido van Rossum committed
326
        # XXX Shouldn't this be in IOBinding or in FileList?
327 328 329 330 331 332 333 334
        try:
            name = self.text.get("sel.first", "sel.last")
        except TclError:
            name = ""
        else:
            name = string.strip(name)
        if not name:
            name = tkSimpleDialog.askstring("Module",
335 336
                     "Enter the name of a Python module\n"
                     "to search on sys.path and open:",
337 338 339 340 341
                     parent=self.text)
            if name:
                name = string.strip(name)
            if not name:
                return
Guido van Rossum's avatar
Guido van Rossum committed
342 343
        # XXX Ought to support package syntax
        # XXX Ought to insert current file's directory in front of path
344 345
        try:
            (f, file, (suffix, mode, type)) = imp.find_module(name)
346
        except (NameError, ImportError), msg:
347 348 349 350 351 352 353 354
            tkMessageBox.showerror("Import error", str(msg), parent=self.text)
            return
        if type != imp.PY_SOURCE:
            tkMessageBox.showerror("Unsupported type",
                "%s is not a source module" % name, parent=self.text)
            return
        if f:
            f.close()
Guido van Rossum's avatar
Guido van Rossum committed
355 356 357 358 359 360 361 362 363 364 365 366
        if self.flist:
            self.flist.open(file)
        else:
            self.io.loadfile(file)

    def open_class_browser(self, event=None):
        filename = self.io.filename
        if not filename:
            tkMessageBox.showerror(
                "No filename",
                "This buffer has no associated filename",
                master=self.text)
367
            self.text.focus_set()
Guido van Rossum's avatar
Guido van Rossum committed
368 369 370 371 372
            return None
        head, tail = os.path.split(filename)
        base, ext = os.path.splitext(tail)
        import ClassBrowser
        ClassBrowser.ClassBrowser(self.flist, base, [head])
373

374 375 376
    def open_path_browser(self, event=None):
        import PathBrowser
        PathBrowser.PathBrowser(self.flist)
377

378 379 380 381 382 383 384 385 386
    def gotoline(self, lineno):
        if lineno is not None and lineno > 0:
            self.text.mark_set("insert", "%d.0" % lineno)
            self.text.tag_remove("sel", "1.0", "end")
            self.text.tag_add("sel", "insert", "insert +1l")
            self.center()

    def ispythonsource(self, filename):
        if not filename:
387
            return True
Guido van Rossum's avatar
Guido van Rossum committed
388 389
        base, ext = os.path.splitext(os.path.basename(filename))
        if os.path.normcase(ext) in (".py", ".pyw"):
390
            return True
391 392 393 394 395
        try:
            f = open(filename)
            line = f.readline()
            f.close()
        except IOError:
396
            return False
397 398
        return line[:2] == '#!' and string.find(line, 'python') >= 0

Guido van Rossum's avatar
Guido van Rossum committed
399 400 401
    def close_hook(self):
        if self.flist:
            self.flist.close_edit(self)
402 403 404 405 406

    def set_close_hook(self, close_hook):
        self.close_hook = close_hook

    def filename_change_hook(self):
Guido van Rossum's avatar
Guido van Rossum committed
407 408
        if self.flist:
            self.flist.filename_changed_edit(self)
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
        self.saved_change_hook()
        if self.ispythonsource(self.io.filename):
            self.addcolorizer()
        else:
            self.rmcolorizer()

    def addcolorizer(self):
        if self.color:
            return
        ##print "Add colorizer"
        self.per.removefilter(self.undo)
        self.color = self.ColorDelegator()
        self.per.insertfilter(self.color)
        self.per.insertfilter(self.undo)

    def rmcolorizer(self):
        if not self.color:
            return
        ##print "Remove colorizer"
        self.per.removefilter(self.undo)
        self.per.removefilter(self.color)
        self.color = None
        self.per.insertfilter(self.undo)

    def saved_change_hook(self):
Guido van Rossum's avatar
Guido van Rossum committed
434 435 436 437 438 439 440 441
        short = self.short_title()
        long = self.long_title()
        if short and long:
            title = short + " - " + long
        elif short:
            title = short
        elif long:
            title = long
442
        else:
Guido van Rossum's avatar
Guido van Rossum committed
443 444 445 446 447
            title = "Untitled"
        icon = short or long or title
        if not self.get_saved():
            title = "*%s*" % title
            icon = "*%s" % icon
448
        self.top.wm_title(title)
Guido van Rossum's avatar
Guido van Rossum committed
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
        self.top.wm_iconname(icon)

    def get_saved(self):
        return self.undo.get_saved()

    def set_saved(self, flag):
        self.undo.set_saved(flag)

    def reset_undo(self):
        self.undo.reset_undo()

    def short_title(self):
        filename = self.io.filename
        if filename:
            filename = os.path.basename(filename)
        return filename

    def long_title(self):
        return self.io.filename or ""
468 469 470 471 472

    def center_insert_event(self, event):
        self.center()

    def center(self, mark="insert"):
473 474 475 476
        text = self.text
        top, bot = self.getwindowlines()
        lineno = self.getlineno(mark)
        height = bot - top
477
        newtop = max(1, lineno - height//2)
478 479 480 481 482 483
        text.yview(float(newtop))

    def getwindowlines(self):
        text = self.text
        top = self.getlineno("@0,0")
        bot = self.getlineno("@0,65535")
484 485
        if top == bot and text.winfo_height() == 1:
            # Geometry manager hasn't run yet
486 487 488 489 490 491 492
            height = int(text['height'])
            bot = top + height - 1
        return top, bot

    def getlineno(self, mark="insert"):
        text = self.text
        return int(float(text.index(mark)))
493 494 495 496

    def close_event(self, event):
        self.close()

Guido van Rossum's avatar
Guido van Rossum committed
497 498 499 500
    def maybesave(self):
        if self.io:
            return self.io.maybesave()

501 502 503
    def close(self):
        self.top.wm_deiconify()
        self.top.tkraise()
Guido van Rossum's avatar
Guido van Rossum committed
504
        reply = self.maybesave()
505
        if reply != "cancel":
506
            self._close()
507 508
        return reply

509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
    def _close(self):
        WindowList.unregister_callback(self.postwindowsmenu)
        if self.close_hook:
            self.close_hook()
        self.flist = None
        colorizing = 0
        self.unload_extensions()
        self.io.close(); self.io = None
        self.undo = None # XXX
        if self.color:
            colorizing = self.color.colorizing
            doh = colorizing and self.top
            self.color.close(doh) # Cancel colorization
        self.text = None
        self.vars = None
        self.per.close(); self.per = None
        if not colorizing:
            self.top.destroy()

Guido van Rossum's avatar
Guido van Rossum committed
528 529 530 531
    def load_extensions(self):
        self.extensions = {}
        self.load_standard_extensions()

532 533 534 535 536 537
    def unload_extensions(self):
        for ins in self.extensions.values():
            if hasattr(ins, "close"):
                ins.close()
        self.extensions = {}

Guido van Rossum's avatar
Guido van Rossum committed
538 539 540 541 542 543 544 545 546 547
    def load_standard_extensions(self):
        for name in self.get_standard_extension_names():
            try:
                self.load_extension(name)
            except:
                print "Failed to load extension", `name`
                import traceback
                traceback.print_exc()

    def get_standard_extension_names(self):
548
        return idleconf.getextensions()
Guido van Rossum's avatar
Guido van Rossum committed
549 550

    def load_extension(self, name):
551
        mod = __import__(name, globals(), locals(), [])
Guido van Rossum's avatar
Guido van Rossum committed
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
        cls = getattr(mod, name)
        ins = cls(self)
        self.extensions[name] = ins
        kdnames = ["keydefs"]
        if sys.platform == 'win32':
            kdnames.append("windows_keydefs")
        elif sys.platform == 'mac':
            kdnames.append("mac_keydefs")
        else:
            kdnames.append("unix_keydefs")
        keydefs = {}
        for kdname in kdnames:
            if hasattr(ins, kdname):
                keydefs.update(getattr(ins, kdname))
        if keydefs:
567
            self.apply_bindings(keydefs)
Guido van Rossum's avatar
Guido van Rossum committed
568 569 570 571 572 573 574 575 576 577
            for vevent in keydefs.keys():
                methodname = string.replace(vevent, "-", "_")
                while methodname[:1] == '<':
                    methodname = methodname[1:]
                while methodname[-1:] == '>':
                    methodname = methodname[:-1]
                methodname = methodname + "_event"
                if hasattr(ins, methodname):
                    self.text.bind(vevent, getattr(ins, methodname))
        if hasattr(ins, "menudefs"):
578
            self.fill_menus(ins.menudefs, keydefs)
Guido van Rossum's avatar
Guido van Rossum committed
579 580
        return ins

581 582 583 584 585 586 587 588 589 590
    def apply_bindings(self, keydefs=None):
        if keydefs is None:
            keydefs = self.Bindings.default_keydefs
        text = self.text
        text.keydefs = keydefs
        for event, keylist in keydefs.items():
            if keylist:
                apply(text.event_add, (event,) + tuple(keylist))

    def fill_menus(self, defs=None, keydefs=None):
591 592
        # Fill the menus. Menus that are absent or None in
        # self.menudict are ignored.
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
        if defs is None:
            defs = self.Bindings.menudefs
        if keydefs is None:
            keydefs = self.Bindings.default_keydefs
        menudict = self.menudict
        text = self.text
        for mname, itemlist in defs:
            menu = menudict.get(mname)
            if not menu:
                continue
            for item in itemlist:
                if not item:
                    menu.add_separator()
                else:
                    label, event = item
                    checkbutton = (label[:1] == '!')
                    if checkbutton:
                        label = label[1:]
                    underline, label = prepstr(label)
                    accelerator = get_accelerator(keydefs, event)
                    def command(text=text, event=event):
                        text.event_generate(event)
                    if checkbutton:
                        var = self.getrawvar(event, BooleanVar)
                        menu.add_checkbutton(label=label, underline=underline,
                            command=command, accelerator=accelerator,
                            variable=var)
                    else:
                        menu.add_command(label=label, underline=underline,
                            command=command, accelerator=accelerator)
623

624 625 626 627
    def getvar(self, name):
        var = self.getrawvar(name)
        if var:
            return var.get()
628

629 630 631 632
    def setvar(self, name, value, vartype=None):
        var = self.getrawvar(name, vartype)
        if var:
            var.set(value)
633

634
    def getrawvar(self, name, vartype=None):
635 636 637 638
        var = self.vars.get(name)
        if not var and vartype:
            self.vars[name] = var = vartype(self.text)
        return var
639

640 641 642 643 644
    # Tk implementations of "virtual text methods" -- each platform
    # reusing IDLE's support code needs to define these for its GUI's
    # flavor of widget.

    # Is character at text_index in a Python string?  Return 0 for
645 646 647
    # "guaranteed no", true for anything else.  This info is expensive
    # to compute ab initio, but is probably already known by the
    # platform's colorizer.
648 649 650

    def is_char_in_string(self, text_index):
        if self.color:
651 652
            # Return true iff colorizer hasn't (re)gotten this far
            # yet, or the character is tagged as being in a string
653 654 655
            return self.text.tag_prevrange("TODO", text_index) or \
                   "STRING" in self.text.tag_names(text_index)
        else:
656
            # The colorizer is missing: assume the worst
657 658
            return 1

659 660
    # If a selection is defined in the text widget, return (start,
    # end) as Tkinter text indices, otherwise return (None, None)
661
    def get_selection_indices(self):
662 663 664 665 666 667 668
        try:
            first = self.text.index("sel.first")
            last = self.text.index("sel.last")
            return first, last
        except TclError:
            return None, None

669 670 671 672 673 674 675 676 677 678 679 680 681 682
    # Return the text widget's current view of what a tab stop means
    # (equivalent width in spaces).

    def get_tabwidth(self):
        current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
        return int(current)

    # Set the text widget's current view of what a tab stop means.

    def set_tabwidth(self, newtabwidth):
        text = self.text
        if self.get_tabwidth() != newtabwidth:
            pixels = text.tk.call("font", "measure", text["font"],
                                  "-displayof", text.master,
683
                                  "n" * newtabwidth)
684 685
            text.configure(tabs=pixels)

686
def prepstr(s):
687 688
    # Helper to extract the underscore from a string, e.g.
    # prepstr("Co_py") returns (2, "Copy").
689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
    i = string.find(s, '_')
    if i >= 0:
        s = s[:i] + s[i+1:]
    return i, s


keynames = {
 'bracketleft': '[',
 'bracketright': ']',
 'slash': '/',
}

def get_accelerator(keydefs, event):
    keylist = keydefs.get(event)
    if not keylist:
        return ""
    s = keylist[0]
    s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
    s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
    s = re.sub("Key-", "", s)
    s = re.sub("Control-", "Ctrl-", s)
    s = re.sub("-", "+", s)
    s = re.sub("><", " ", s)
    s = re.sub("<", "", s)
    s = re.sub(">", "", s)
    return s

716 717

def fixwordbreaks(root):
Guido van Rossum's avatar
Guido van Rossum committed
718 719
    # Make sure that Tk's double-click and next/previous word
    # operations use our definition of a word (i.e. an identifier)
720 721 722 723 724 725 726 727 728 729 730 731 732 733
    tk = root.tk
    tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
    tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
    tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')


def test():
    root = Tk()
    fixwordbreaks(root)
    root.withdraw()
    if sys.argv[1:]:
        filename = sys.argv[1]
    else:
        filename = None
Guido van Rossum's avatar
Guido van Rossum committed
734
    edit = EditorWindow(root=root, filename=filename)
735 736 737 738 739 740
    edit.set_close_hook(root.quit)
    root.mainloop()
    root.destroy()

if __name__ == '__main__':
    test()