EditorWindow.py revision 260cb9034c861fa159f26fba8679ac265af47109
1import sys
2import os
3import re
4import imp
5from Tkinter import *
6import tkSimpleDialog
7import tkMessageBox
8
9import webbrowser
10import idlever
11import WindowList
12import SearchDialog
13import GrepDialog
14import ReplaceDialog
15import PyParse
16from configHandler import idleConf
17import aboutDialog, textView, configDialog
18
19# The default tab setting for a Text widget, in average-width characters.
20TK_TABWIDTH_DEFAULT = 8
21
22def _find_module(fullname, path=None):
23    """Version of imp.find_module() that handles hierarchical module names"""
24
25    file = None
26    for tgt in fullname.split('.'):
27        if file is not None:
28            file.close()            # close intermediate files
29        (file, filename, descr) = imp.find_module(tgt, path)
30        if descr[2] == imp.PY_SOURCE:
31            break                   # find but not load the source file
32        module = imp.load_module(tgt, file, filename, descr)
33        try:
34            path = module.__path__
35        except AttributeError:
36            raise ImportError, 'No source for module ' + module.__name__
37    return file, filename, descr
38
39class EditorWindow:
40    from Percolator import Percolator
41    from ColorDelegator import ColorDelegator
42    from UndoDelegator import UndoDelegator
43    from IOBinding import IOBinding
44    import Bindings
45    from Tkinter import Toplevel
46    from MultiStatusBar import MultiStatusBar
47
48    vars = {}
49    help_url = None
50
51    def __init__(self, flist=None, filename=None, key=None, root=None):
52        if EditorWindow.help_url is None:
53            if sys.platform.count('linux'):
54                # look for html docs in a couple of standard places
55                pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
56                if os.path.isdir('/var/www/html/python/'):  # "python2" rpm
57                    dochome = '/var/www/html/python/index.html'
58                else:
59                    basepath = '/usr/share/doc/'  # standard location
60                    dochome = os.path.join(basepath, pyver,
61                                           'Doc', 'index.html')
62            else:
63                dochome =  os.path.join(sys.prefix, 'Doc', 'index.html')
64            dochome = os.path.normpath(dochome)
65            if os.path.isfile(dochome):
66                EditorWindow.help_url = dochome
67            else:
68                EditorWindow.help_url = "http://www.python.org/doc/current"
69        currentTheme=idleConf.CurrentTheme()
70        self.flist = flist
71        root = root or flist.root
72        self.root = root
73        self.menubar = Menu(root)
74        self.top = top = self.Toplevel(root, menu=self.menubar)
75        if flist:
76            self.vars = flist.vars
77            #self.top.instanceDict makes flist.inversedict avalable to
78            #configDialog.py so it can access all EditorWindow instaces
79            self.top.instanceDict=flist.inversedict
80        self.recentFilesPath=os.path.join(idleConf.GetUserCfgDir(),
81                'recent-files.lst')
82        self.vbar = vbar = Scrollbar(top, name='vbar')
83        self.text_frame = text_frame = Frame(top)
84        self.width = idleConf.GetOption('main','EditorWindow','width')
85        self.text = text = Text(text_frame, name='text', padx=5, wrap='none',
86                foreground=idleConf.GetHighlight(currentTheme,
87                        'normal',fgBg='fg'),
88                background=idleConf.GetHighlight(currentTheme,
89                        'normal',fgBg='bg'),
90                highlightcolor=idleConf.GetHighlight(currentTheme,
91                        'hilite',fgBg='fg'),
92                highlightbackground=idleConf.GetHighlight(currentTheme,
93                        'hilite',fgBg='bg'),
94                insertbackground=idleConf.GetHighlight(currentTheme,
95                        'cursor',fgBg='fg'),
96                width=self.width,
97                height=idleConf.GetOption('main','EditorWindow','height') )
98
99        self.createmenubar()
100        self.apply_bindings()
101
102        self.top.protocol("WM_DELETE_WINDOW", self.close)
103        self.top.bind("<<close-window>>", self.close_event)
104        text.bind("<<cut>>", self.cut)
105        text.bind("<<copy>>", self.copy)
106        text.bind("<<paste>>", self.paste)
107        text.bind("<<center-insert>>", self.center_insert_event)
108        text.bind("<<help>>", self.help_dialog)
109        text.bind("<<view-readme>>", self.view_readme)
110        text.bind("<<python-docs>>", self.python_docs)
111        text.bind("<<about-idle>>", self.about_dialog)
112        text.bind("<<open-config-dialog>>", self.config_dialog)
113        text.bind("<<open-module>>", self.open_module)
114        text.bind("<<do-nothing>>", lambda event: "break")
115        text.bind("<<select-all>>", self.select_all)
116        text.bind("<<remove-selection>>", self.remove_selection)
117        text.bind("<<find>>", self.find_event)
118        text.bind("<<find-again>>", self.find_again_event)
119        text.bind("<<find-in-files>>", self.find_in_files_event)
120        text.bind("<<find-selection>>", self.find_selection_event)
121        text.bind("<<replace>>", self.replace_event)
122        text.bind("<<goto-line>>", self.goto_line_event)
123        text.bind("<3>", self.right_menu_event)
124        text.bind("<<smart-backspace>>",self.smart_backspace_event)
125        text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
126        text.bind("<<smart-indent>>",self.smart_indent_event)
127        text.bind("<<indent-region>>",self.indent_region_event)
128        text.bind("<<dedent-region>>",self.dedent_region_event)
129        text.bind("<<comment-region>>",self.comment_region_event)
130        text.bind("<<uncomment-region>>",self.uncomment_region_event)
131        text.bind("<<tabify-region>>",self.tabify_region_event)
132        text.bind("<<untabify-region>>",self.untabify_region_event)
133        text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
134        text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
135        text.bind("<Left>", self.move_at_edge_if_selection(0))
136        text.bind("<Right>", self.move_at_edge_if_selection(1))
137
138        if flist:
139            flist.inversedict[self] = key
140            if key:
141                flist.dict[key] = self
142            text.bind("<<open-new-window>>", self.new_callback)
143            text.bind("<<close-all-windows>>", self.flist.close_all_callback)
144            text.bind("<<open-class-browser>>", self.open_class_browser)
145            text.bind("<<open-path-browser>>", self.open_path_browser)
146
147        self.set_status_bar()
148        vbar['command'] = text.yview
149        vbar.pack(side=RIGHT, fill=Y)
150        text['yscrollcommand'] = vbar.set
151        fontWeight='normal'
152        if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
153            fontWeight='bold'
154        text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
155                idleConf.GetOption('main','EditorWindow','font-size'),
156                fontWeight))
157        text_frame.pack(side=LEFT, fill=BOTH, expand=1)
158        text.pack(side=TOP, fill=BOTH, expand=1)
159        text.focus_set()
160
161        self.per = per = self.Percolator(text)
162        if self.ispythonsource(filename):
163            self.color = color = self.ColorDelegator()
164            per.insertfilter(color)
165        else:
166            self.color = None
167
168        self.undo = undo = self.UndoDelegator()
169        per.insertfilter(undo)
170        text.undo_block_start = undo.undo_block_start
171        text.undo_block_stop = undo.undo_block_stop
172        undo.set_saved_change_hook(self.saved_change_hook)
173
174        # IOBinding implements file I/O and printing functionality
175        self.io = io = self.IOBinding(self)
176        io.set_filename_change_hook(self.filename_change_hook)
177
178        #create the Recent Files submenu
179        self.menuRecentFiles=Menu(self.menubar)
180        self.menudict['file'].insert_cascade(3,label='Recent Files',
181                underline=0,menu=self.menuRecentFiles)
182        self.UpdateRecentFilesList()
183
184        if filename:
185            if os.path.exists(filename) and not os.path.isdir(filename):
186                io.loadfile(filename)
187            else:
188                io.set_filename(filename)
189        self.saved_change_hook()
190
191        self.load_extensions()
192
193        menu = self.menudict.get('windows')
194        if menu:
195            end = menu.index("end")
196            if end is None:
197                end = -1
198            if end >= 0:
199                menu.add_separator()
200                end = end + 1
201            self.wmenu_end = end
202            WindowList.register_callback(self.postwindowsmenu)
203
204        # Some abstractions so IDLE extensions are cross-IDE
205        self.askyesno = tkMessageBox.askyesno
206        self.askinteger = tkSimpleDialog.askinteger
207        self.showerror = tkMessageBox.showerror
208
209        if self.extensions.has_key('AutoIndent'):
210            self.extensions['AutoIndent'].set_indentation_params(
211                self.ispythonsource(filename))
212
213    def new_callback(self, event):
214        dirname, basename = self.io.defaultfilename()
215        self.flist.new(dirname)
216        return "break"
217
218    def set_status_bar(self):
219        self.status_bar = self.MultiStatusBar(self.top)
220        self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
221        self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
222        self.status_bar.pack(side=BOTTOM, fill=X)
223        self.text.bind('<KeyRelease>', self.set_line_and_column)
224        self.text.bind('<ButtonRelease>', self.set_line_and_column)
225        self.text.after_idle(self.set_line_and_column)
226
227    def set_line_and_column(self, event=None):
228        line, column = self.text.index(INSERT).split('.')
229        self.status_bar.set_label('column', 'Col: %s' % column)
230        self.status_bar.set_label('line', 'Ln: %s' % line)
231
232    def wakeup(self):
233        if self.top.wm_state() == "iconic":
234            self.top.wm_deiconify()
235        else:
236            self.top.tkraise()
237        self.text.focus_set()
238
239    menu_specs = [
240        ("file", "_File"),
241        ("edit", "_Edit"),
242        ("format", "F_ormat"),
243        ("run", "_Run"),
244        ("options", "_Options"),
245        ("windows", "_Windows"),
246        ("help", "_Help"),
247    ]
248
249    def createmenubar(self):
250        mbar = self.menubar
251        self.menudict = menudict = {}
252        for name, label in self.menu_specs:
253            underline, label = prepstr(label)
254            menudict[name] = menu = Menu(mbar, name=name)
255            mbar.add_cascade(label=label, menu=menu, underline=underline)
256        self.fill_menus()
257        self.base_helpmenu_length = self.menudict['help'].index(END)
258        self.reset_help_menu_entries()
259
260    def postwindowsmenu(self):
261        # Only called when Windows menu exists
262        # XXX Actually, this Just-In-Time updating interferes badly
263        # XXX with the tear-off feature.  It would be better to update
264        # XXX all Windows menus whenever the list of windows changes.
265        menu = self.menudict['windows']
266        end = menu.index("end")
267        if end is None:
268            end = -1
269        if end > self.wmenu_end:
270            menu.delete(self.wmenu_end+1, end)
271        WindowList.add_windows_to_menu(menu)
272
273    rmenu = None
274
275    def right_menu_event(self, event):
276        self.text.tag_remove("sel", "1.0", "end")
277        self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
278        if not self.rmenu:
279            self.make_rmenu()
280        rmenu = self.rmenu
281        self.event = event
282        iswin = sys.platform[:3] == 'win'
283        if iswin:
284            self.text.config(cursor="arrow")
285        rmenu.tk_popup(event.x_root, event.y_root)
286        if iswin:
287            self.text.config(cursor="ibeam")
288
289    rmenu_specs = [
290        # ("Label", "<<virtual-event>>"), ...
291        ("Close", "<<close-window>>"), # Example
292    ]
293
294    def make_rmenu(self):
295        rmenu = Menu(self.text, tearoff=0)
296        for label, eventname in self.rmenu_specs:
297            def command(text=self.text, eventname=eventname):
298                text.event_generate(eventname)
299            rmenu.add_command(label=label, command=command)
300        self.rmenu = rmenu
301
302    def about_dialog(self, event=None):
303        aboutDialog.AboutDialog(self.top,'About IDLEfork')
304
305    def config_dialog(self, event=None):
306        configDialog.ConfigDialog(self.top,'Settings')
307
308    def view_readme(self, event=None):
309        fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
310        textView.TextViewer(self.top,'IDLEfork - README',fn)
311
312    def help_dialog(self, event=None):
313        fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
314        textView.TextViewer(self.top,'Help',fn)
315
316    def python_docs(self, event=None):
317        if sys.platform.count('win') or sys.platform.count('nt'):
318            os.startfile(self.help_url)
319            return "break"
320        else:
321            webbrowser.open(self.help_url)
322            return "break"
323
324    def display_docs(self, url):
325        if not (url.startswith('www') or url.startswith('http')):
326            url = os.path.normpath(url)
327        if sys.platform.count('win') or sys.platform.count('nt'):
328            os.startfile(url)
329        else:
330            webbrowser.open(url)
331
332    def cut(self,event):
333        self.text.event_generate("<<Cut>>")
334        return "break"
335
336    def copy(self,event):
337        self.text.event_generate("<<Copy>>")
338        return "break"
339
340    def paste(self,event):
341        self.text.event_generate("<<Paste>>")
342        return "break"
343
344    def select_all(self, event=None):
345        self.text.tag_add("sel", "1.0", "end-1c")
346        self.text.mark_set("insert", "1.0")
347        self.text.see("insert")
348        return "break"
349
350    def remove_selection(self, event=None):
351        self.text.tag_remove("sel", "1.0", "end")
352        self.text.see("insert")
353
354    def move_at_edge_if_selection(self, edge_index):
355        """Cursor move begins at start or end of selection
356
357        When a left/right cursor key is pressed create and return to Tkinter a
358        function which causes a cursor move from the associated edge of the
359        selection.
360
361        """
362        self_text_index = self.text.index
363        self_text_mark_set = self.text.mark_set
364        edges_table = ("sel.first+1c", "sel.last-1c")
365        def move_at_edge(event):
366            if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
367                try:
368                    self_text_index("sel.first")
369                    self_text_mark_set("insert", edges_table[edge_index])
370                except TclError:
371                    pass
372        return move_at_edge
373
374    def find_event(self, event):
375        SearchDialog.find(self.text)
376        return "break"
377
378    def find_again_event(self, event):
379        SearchDialog.find_again(self.text)
380        return "break"
381
382    def find_selection_event(self, event):
383        SearchDialog.find_selection(self.text)
384        return "break"
385
386    def find_in_files_event(self, event):
387        GrepDialog.grep(self.text, self.io, self.flist)
388        return "break"
389
390    def replace_event(self, event):
391        ReplaceDialog.replace(self.text)
392        return "break"
393
394    def goto_line_event(self, event):
395        text = self.text
396        lineno = tkSimpleDialog.askinteger("Goto",
397                "Go to line number:",parent=text)
398        if lineno is None:
399            return "break"
400        if lineno <= 0:
401            text.bell()
402            return "break"
403        text.mark_set("insert", "%d.0" % lineno)
404        text.see("insert")
405
406    def open_module(self, event=None):
407        # XXX Shouldn't this be in IOBinding or in FileList?
408        try:
409            name = self.text.get("sel.first", "sel.last")
410        except TclError:
411            name = ""
412        else:
413            name = name.strip()
414        name = tkSimpleDialog.askstring("Module",
415                 "Enter the name of a Python module\n"
416                 "to search on sys.path and open:",
417                 parent=self.text, initialvalue=name)
418        if name:
419            name = name.strip()
420        if not name:
421            return
422        # XXX Ought to insert current file's directory in front of path
423        try:
424            (f, file, (suffix, mode, type)) = _find_module(name)
425        except (NameError, ImportError), msg:
426            tkMessageBox.showerror("Import error", str(msg), parent=self.text)
427            return
428        if type != imp.PY_SOURCE:
429            tkMessageBox.showerror("Unsupported type",
430                "%s is not a source module" % name, parent=self.text)
431            return
432        if f:
433            f.close()
434        if self.flist:
435            self.flist.open(file)
436        else:
437            self.io.loadfile(file)
438
439    def open_class_browser(self, event=None):
440        filename = self.io.filename
441        if not filename:
442            tkMessageBox.showerror(
443                "No filename",
444                "This buffer has no associated filename",
445                master=self.text)
446            self.text.focus_set()
447            return None
448        head, tail = os.path.split(filename)
449        base, ext = os.path.splitext(tail)
450        import ClassBrowser
451        ClassBrowser.ClassBrowser(self.flist, base, [head])
452
453    def open_path_browser(self, event=None):
454        import PathBrowser
455        PathBrowser.PathBrowser(self.flist)
456
457    def gotoline(self, lineno):
458        if lineno is not None and lineno > 0:
459            self.text.mark_set("insert", "%d.0" % lineno)
460            self.text.tag_remove("sel", "1.0", "end")
461            self.text.tag_add("sel", "insert", "insert +1l")
462            self.center()
463
464    def ispythonsource(self, filename):
465        if not filename:
466            return True
467        base, ext = os.path.splitext(os.path.basename(filename))
468        if os.path.normcase(ext) in (".py", ".pyw"):
469            return True
470        try:
471            f = open(filename)
472            line = f.readline()
473            f.close()
474        except IOError:
475            return False
476        return line.startswith('#!') and line.find('python') >= 0
477
478    def close_hook(self):
479        if self.flist:
480            self.flist.close_edit(self)
481
482    def set_close_hook(self, close_hook):
483        self.close_hook = close_hook
484
485    def filename_change_hook(self):
486        if self.flist:
487            self.flist.filename_changed_edit(self)
488        self.saved_change_hook()
489        self.top.update_windowlist_registry(self)
490        if self.ispythonsource(self.io.filename):
491            self.addcolorizer()
492        else:
493            self.rmcolorizer()
494
495    def addcolorizer(self):
496        if self.color:
497            return
498        self.per.removefilter(self.undo)
499        self.color = self.ColorDelegator()
500        self.per.insertfilter(self.color)
501        self.per.insertfilter(self.undo)
502
503    def rmcolorizer(self):
504        if not self.color:
505            return
506        self.per.removefilter(self.undo)
507        self.per.removefilter(self.color)
508        self.color = None
509        self.per.insertfilter(self.undo)
510
511    def ResetColorizer(self):
512        "Update the colour theme if it is changed"
513        # Called from configDialog.py
514        if self.color:
515            self.color = self.ColorDelegator()
516            self.per.insertfilter(self.color)
517
518    def ResetFont(self):
519        "Update the text widgets' font if it is changed"
520        # Called from configDialog.py
521        fontWeight='normal'
522        if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
523            fontWeight='bold'
524        self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
525                idleConf.GetOption('main','EditorWindow','font-size'),
526                fontWeight))
527
528    def ResetKeybindings(self):
529        "Update the keybindings if they are changed"
530        # Called from configDialog.py
531        self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
532        keydefs = self.Bindings.default_keydefs
533        for event, keylist in keydefs.items():
534            self.text.event_delete(event)
535        self.apply_bindings()
536        #update menu accelerators
537        menuEventDict={}
538        for menu in self.Bindings.menudefs:
539            menuEventDict[menu[0]]={}
540            for item in menu[1]:
541                if item:
542                    menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
543        for menubarItem in self.menudict.keys():
544            menu=self.menudict[menubarItem]
545            end=menu.index(END)+1
546            for index in range(0,end):
547                if menu.type(index)=='command':
548                    accel=menu.entrycget(index,'accelerator')
549                    if accel:
550                        itemName=menu.entrycget(index,'label')
551                        event=''
552                        if menuEventDict.has_key(menubarItem):
553                            if menuEventDict[menubarItem].has_key(itemName):
554                                event=menuEventDict[menubarItem][itemName]
555                        if event:
556                            #print 'accel was:',accel
557                            accel=get_accelerator(keydefs, event)
558                            menu.entryconfig(index,accelerator=accel)
559                            #print 'accel now:',accel,'\n'
560
561    def reset_help_menu_entries(self):
562        "Update the additional help entries on the Help menu"
563        help_list = idleConf.GetAllExtraHelpSourcesList()
564        helpmenu = self.menudict['help']
565        # first delete the extra help entries, if any
566        helpmenu_length = helpmenu.index(END)
567        if helpmenu_length > self.base_helpmenu_length:
568            helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
569        # then rebuild them
570        if help_list:
571            helpmenu.add_separator()
572            for entry in help_list:
573                cmd = self.__extra_help_callback(entry[1])
574                helpmenu.add_command(label=entry[0], command=cmd)
575        # and update the menu dictionary
576        self.menudict['help'] = helpmenu
577
578    def __extra_help_callback(self, helpfile):
579        "Create a callback with the helpfile value frozen at definition time"
580        def display_extra_help(helpfile=helpfile):
581            self.display_docs(helpfile)
582        return display_extra_help
583
584    def UpdateRecentFilesList(self,newFile=None):
585        "Load or update the recent files list, and menu if required"
586        rfList=[]
587        if os.path.exists(self.recentFilesPath):
588            RFfile=open(self.recentFilesPath,'r')
589            try:
590                rfList=RFfile.readlines()
591            finally:
592                RFfile.close()
593        if newFile:
594            newFile=os.path.abspath(newFile)+'\n'
595            if newFile in rfList:
596                rfList.remove(newFile)
597            rfList.insert(0,newFile)
598        rfList=self.__CleanRecentFiles(rfList)
599        #print self.flist.inversedict
600        #print self.top.instanceDict
601        #print self
602        ullist = "1234567890ABCDEFGHIJ"
603        if rfList:
604            for instance in self.top.instanceDict.keys():
605                menu = instance.menuRecentFiles
606                menu.delete(1,END)
607                i = 0 ; ul = 0; ullen = len(ullist)
608                for file in rfList:
609                    fileName=file[0:-1]
610                    callback = instance.__RecentFileCallback(fileName)
611                    if i > ullen: # don't underline menuitems
612                        ul=None
613                    menu.add_command(label=ullist[i] + " " + fileName,
614                                     command=callback,
615                                     underline=ul)
616                    i += 1
617
618    def __CleanRecentFiles(self,rfList):
619        origRfList=rfList[:]
620        count=0
621        nonFiles=[]
622        for path in rfList:
623            if not os.path.exists(path[0:-1]):
624                nonFiles.append(count)
625            count=count+1
626        if nonFiles:
627            nonFiles.reverse()
628            for index in nonFiles:
629                del(rfList[index])
630        if len(rfList)>19:
631            rfList=rfList[0:19]
632        #if rfList != origRfList:
633        RFfile=open(self.recentFilesPath,'w')
634        try:
635            RFfile.writelines(rfList)
636        finally:
637            RFfile.close()
638        return rfList
639
640    def __RecentFileCallback(self,fileName):
641        def OpenRecentFile(fileName=fileName):
642            self.io.open(editFile=fileName)
643        return OpenRecentFile
644
645    def saved_change_hook(self):
646        short = self.short_title()
647        long = self.long_title()
648        if short and long:
649            title = short + " - " + long
650        elif short:
651            title = short
652        elif long:
653            title = long
654        else:
655            title = "Untitled"
656        icon = short or long or title
657        if not self.get_saved():
658            title = "*%s*" % title
659            icon = "*%s" % icon
660        self.top.wm_title(title)
661        self.top.wm_iconname(icon)
662
663    def get_saved(self):
664        return self.undo.get_saved()
665
666    def set_saved(self, flag):
667        self.undo.set_saved(flag)
668
669    def reset_undo(self):
670        self.undo.reset_undo()
671
672    def short_title(self):
673        filename = self.io.filename
674        if filename:
675            filename = os.path.basename(filename)
676        return filename
677
678    def long_title(self):
679        return self.io.filename or ""
680
681    def center_insert_event(self, event):
682        self.center()
683
684    def center(self, mark="insert"):
685        text = self.text
686        top, bot = self.getwindowlines()
687        lineno = self.getlineno(mark)
688        height = bot - top
689        newtop = max(1, lineno - height//2)
690        text.yview(float(newtop))
691
692    def getwindowlines(self):
693        text = self.text
694        top = self.getlineno("@0,0")
695        bot = self.getlineno("@0,65535")
696        if top == bot and text.winfo_height() == 1:
697            # Geometry manager hasn't run yet
698            height = int(text['height'])
699            bot = top + height - 1
700        return top, bot
701
702    def getlineno(self, mark="insert"):
703        text = self.text
704        return int(float(text.index(mark)))
705
706    def get_geometry(self):
707        "Return (width, height, x, y)"
708        geom = self.top.wm_geometry()
709        m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
710        tuple = (map(int, m.groups()))
711        return tuple
712
713    def close_event(self, event):
714        self.close()
715
716    def maybesave(self):
717        if self.io:
718            if not self.get_saved():
719                if self.top.state()!='normal':
720                    self.top.deiconify()
721                self.top.lower()
722                self.top.lift()
723            return self.io.maybesave()
724
725    def close(self):
726        reply = self.maybesave()
727        if reply != "cancel":
728            self._close()
729        return reply
730
731    def _close(self):
732        #print self.io.filename
733        if self.io.filename:
734            self.UpdateRecentFilesList(newFile=self.io.filename)
735        WindowList.unregister_callback(self.postwindowsmenu)
736        if self.close_hook:
737            self.close_hook()
738        self.flist = None
739        colorizing = 0
740        self.unload_extensions()
741        self.io.close(); self.io = None
742        self.undo = None # XXX
743        if self.color:
744            colorizing = self.color.colorizing
745            doh = colorizing and self.top
746            self.color.close(doh) # Cancel colorization
747        self.text = None
748        self.vars = None
749        self.per.close(); self.per = None
750        if not colorizing:
751            self.top.destroy()
752
753    def load_extensions(self):
754        self.extensions = {}
755        self.load_standard_extensions()
756
757    def unload_extensions(self):
758        for ins in self.extensions.values():
759            if hasattr(ins, "close"):
760                ins.close()
761        self.extensions = {}
762
763    def load_standard_extensions(self):
764        for name in self.get_standard_extension_names():
765            try:
766                self.load_extension(name)
767            except:
768                print "Failed to load extension", `name`
769                import traceback
770                traceback.print_exc()
771
772    def get_standard_extension_names(self):
773        return idleConf.GetExtensions()
774
775    def load_extension(self, name):
776        mod = __import__(name, globals(), locals(), [])
777        cls = getattr(mod, name)
778        ins = cls(self)
779        self.extensions[name] = ins
780        keydefs=idleConf.GetExtensionBindings(name)
781        if keydefs:
782            self.apply_bindings(keydefs)
783            for vevent in keydefs.keys():
784                methodname = vevent.replace("-", "_")
785                while methodname[:1] == '<':
786                    methodname = methodname[1:]
787                while methodname[-1:] == '>':
788                    methodname = methodname[:-1]
789                methodname = methodname + "_event"
790                if hasattr(ins, methodname):
791                    self.text.bind(vevent, getattr(ins, methodname))
792        if hasattr(ins, "menudefs"):
793            self.fill_menus(ins.menudefs, keydefs)
794        return ins
795
796    def apply_bindings(self, keydefs=None):
797        if keydefs is None:
798            keydefs = self.Bindings.default_keydefs
799        text = self.text
800        text.keydefs = keydefs
801        for event, keylist in keydefs.items():
802            if keylist:
803                apply(text.event_add, (event,) + tuple(keylist))
804
805    def fill_menus(self, defs=None, keydefs=None):
806        """Add appropriate entries to the menus and submenus
807
808        Menus that are absent or None in self.menudict are ignored.
809        """
810        if defs is None:
811            defs = self.Bindings.menudefs
812        if keydefs is None:
813            keydefs = self.Bindings.default_keydefs
814        menudict = self.menudict
815        text = self.text
816        for mname, itemlist in defs:
817            menu = menudict.get(mname)
818            if not menu:
819                continue
820            for item in itemlist:
821                if not item:
822                    menu.add_separator()
823                else:
824                    label, event = item
825                    checkbutton = (label[:1] == '!')
826                    if checkbutton:
827                        label = label[1:]
828                    underline, label = prepstr(label)
829                    accelerator = get_accelerator(keydefs, event)
830                    def command(text=text, event=event):
831                        text.event_generate(event)
832                    if checkbutton:
833                        var = self.getrawvar(event, BooleanVar)
834                        menu.add_checkbutton(label=label, underline=underline,
835                            command=command, accelerator=accelerator,
836                            variable=var)
837                    else:
838                        menu.add_command(label=label, underline=underline,
839                                         command=command,
840                                         accelerator=accelerator)
841
842    def getvar(self, name):
843        var = self.getrawvar(name)
844        if var:
845            return var.get()
846
847    def setvar(self, name, value, vartype=None):
848        var = self.getrawvar(name, vartype)
849        if var:
850            var.set(value)
851
852    def getrawvar(self, name, vartype=None):
853        var = self.vars.get(name)
854        if not var and vartype:
855            self.vars[name] = var = vartype(self.text)
856        return var
857
858    # Tk implementations of "virtual text methods" -- each platform
859    # reusing IDLE's support code needs to define these for its GUI's
860    # flavor of widget.
861
862    # Is character at text_index in a Python string?  Return 0 for
863    # "guaranteed no", true for anything else.  This info is expensive
864    # to compute ab initio, but is probably already known by the
865    # platform's colorizer.
866
867    def is_char_in_string(self, text_index):
868        if self.color:
869            # Return true iff colorizer hasn't (re)gotten this far
870            # yet, or the character is tagged as being in a string
871            return self.text.tag_prevrange("TODO", text_index) or \
872                   "STRING" in self.text.tag_names(text_index)
873        else:
874            # The colorizer is missing: assume the worst
875            return 1
876
877    # If a selection is defined in the text widget, return (start,
878    # end) as Tkinter text indices, otherwise return (None, None)
879    def get_selection_indices(self):
880        try:
881            first = self.text.index("sel.first")
882            last = self.text.index("sel.last")
883            return first, last
884        except TclError:
885            return None, None
886
887    # Return the text widget's current view of what a tab stop means
888    # (equivalent width in spaces).
889
890    def get_tabwidth(self):
891        current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
892        return int(current)
893
894    # Set the text widget's current view of what a tab stop means.
895
896    def set_tabwidth(self, newtabwidth):
897        text = self.text
898        if self.get_tabwidth() != newtabwidth:
899            pixels = text.tk.call("font", "measure", text["font"],
900                                  "-displayof", text.master,
901                                  "n" * newtabwidth)
902            text.configure(tabs=pixels)
903
904### begin autoindent code ###
905
906    # usetabs true  -> literal tab characters are used by indent and
907    #                  dedent cmds, possibly mixed with spaces if
908    #                  indentwidth is not a multiple of tabwidth
909    #         false -> tab characters are converted to spaces by indent
910    #                  and dedent cmds, and ditto TAB keystrokes
911    # indentwidth is the number of characters per logical indent level.
912    # tabwidth is the display width of a literal tab character.
913    # CAUTION:  telling Tk to use anything other than its default
914    # tab setting causes it to use an entirely different tabbing algorithm,
915    # treating tab stops as fixed distances from the left margin.
916    # Nobody expects this, so for now tabwidth should never be changed.
917    usetabs = 0
918    indentwidth = 4
919    tabwidth = 8    # for IDLE use, must remain 8 until Tk is fixed
920
921    # If context_use_ps1 is true, parsing searches back for a ps1 line;
922    # else searches for a popular (if, def, ...) Python stmt.
923    context_use_ps1 = 0
924
925    # When searching backwards for a reliable place to begin parsing,
926    # first start num_context_lines[0] lines back, then
927    # num_context_lines[1] lines back if that didn't work, and so on.
928    # The last value should be huge (larger than the # of lines in a
929    # conceivable file).
930    # Making the initial values larger slows things down more often.
931    num_context_lines = 50, 500, 5000000
932
933    def config(self, **options):
934        for key, value in options.items():
935            if key == 'usetabs':
936                self.usetabs = value
937            elif key == 'indentwidth':
938                self.indentwidth = value
939            elif key == 'tabwidth':
940                self.tabwidth = value
941            elif key == 'context_use_ps1':
942                self.context_use_ps1 = value
943            else:
944                raise KeyError, "bad option name: %s" % `key`
945
946    # If ispythonsource and guess are true, guess a good value for
947    # indentwidth based on file content (if possible), and if
948    # indentwidth != tabwidth set usetabs false.
949    # In any case, adjust the Text widget's view of what a tab
950    # character means.
951
952    def set_indentation_params(self, ispythonsource, guess=1):
953        if guess and ispythonsource:
954            i = self.guess_indent()
955            if 2 <= i <= 8:
956                self.indentwidth = i
957            if self.indentwidth != self.tabwidth:
958                self.usetabs = 0
959
960        self.set_tabwidth(self.tabwidth)
961
962    def smart_backspace_event(self, event):
963        text = self.text
964        first, last = self.get_selection_indices()
965        if first and last:
966            text.delete(first, last)
967            text.mark_set("insert", first)
968            return "break"
969        # Delete whitespace left, until hitting a real char or closest
970        # preceding virtual tab stop.
971        chars = text.get("insert linestart", "insert")
972        if chars == '':
973            if text.compare("insert", ">", "1.0"):
974                # easy: delete preceding newline
975                text.delete("insert-1c")
976            else:
977                text.bell()     # at start of buffer
978            return "break"
979        if  chars[-1] not in " \t":
980            # easy: delete preceding real char
981            text.delete("insert-1c")
982            return "break"
983        # Ick.  It may require *inserting* spaces if we back up over a
984        # tab character!  This is written to be clear, not fast.
985        tabwidth = self.tabwidth
986        have = len(chars.expandtabs(tabwidth))
987        assert have > 0
988        want = ((have - 1) // self.indentwidth) * self.indentwidth
989        # Debug prompt is multilined....
990        last_line_of_prompt = sys.ps1.split('\n')[-1]
991        ncharsdeleted = 0
992        while 1:
993            if chars == last_line_of_prompt:
994                break
995            chars = chars[:-1]
996            ncharsdeleted = ncharsdeleted + 1
997            have = len(chars.expandtabs(tabwidth))
998            if have <= want or chars[-1] not in " \t":
999                break
1000        text.undo_block_start()
1001        text.delete("insert-%dc" % ncharsdeleted, "insert")
1002        if have < want:
1003            text.insert("insert", ' ' * (want - have))
1004        text.undo_block_stop()
1005        return "break"
1006
1007    def smart_indent_event(self, event):
1008        # if intraline selection:
1009        #     delete it
1010        # elif multiline selection:
1011        #     do indent-region & return
1012        # indent one level
1013        text = self.text
1014        first, last = self.get_selection_indices()
1015        text.undo_block_start()
1016        try:
1017            if first and last:
1018                if index2line(first) != index2line(last):
1019                    return self.indent_region_event(event)
1020                text.delete(first, last)
1021                text.mark_set("insert", first)
1022            prefix = text.get("insert linestart", "insert")
1023            raw, effective = classifyws(prefix, self.tabwidth)
1024            if raw == len(prefix):
1025                # only whitespace to the left
1026                self.reindent_to(effective + self.indentwidth)
1027            else:
1028                if self.usetabs:
1029                    pad = '\t'
1030                else:
1031                    effective = len(prefix.expandtabs(self.tabwidth))
1032                    n = self.indentwidth
1033                    pad = ' ' * (n - effective % n)
1034                text.insert("insert", pad)
1035            text.see("insert")
1036            return "break"
1037        finally:
1038            text.undo_block_stop()
1039
1040    def newline_and_indent_event(self, event):
1041        text = self.text
1042        first, last = self.get_selection_indices()
1043        text.undo_block_start()
1044        try:
1045            if first and last:
1046                text.delete(first, last)
1047                text.mark_set("insert", first)
1048            line = text.get("insert linestart", "insert")
1049            i, n = 0, len(line)
1050            while i < n and line[i] in " \t":
1051                i = i+1
1052            if i == n:
1053                # the cursor is in or at leading indentation in a continuation
1054                # line; just inject an empty line at the start
1055                text.insert("insert linestart", '\n')
1056                return "break"
1057            indent = line[:i]
1058            # strip whitespace before insert point unless it's in the prompt
1059            i = 0
1060            last_line_of_prompt = sys.ps1.split('\n')[-1]
1061            while line and line[-1] in " \t" and line != last_line_of_prompt:
1062                line = line[:-1]
1063                i = i+1
1064            if i:
1065                text.delete("insert - %d chars" % i, "insert")
1066            # strip whitespace after insert point
1067            while text.get("insert") in " \t":
1068                text.delete("insert")
1069            # start new line
1070            text.insert("insert", '\n')
1071
1072            # adjust indentation for continuations and block
1073            # open/close first need to find the last stmt
1074            lno = index2line(text.index('insert'))
1075            y = PyParse.Parser(self.indentwidth, self.tabwidth)
1076            for context in self.num_context_lines:
1077                startat = max(lno - context, 1)
1078                startatindex = `startat` + ".0"
1079                rawtext = text.get(startatindex, "insert")
1080                y.set_str(rawtext)
1081                bod = y.find_good_parse_start(
1082                          self.context_use_ps1,
1083                          self._build_char_in_string_func(startatindex))
1084                if bod is not None or startat == 1:
1085                    break
1086            y.set_lo(bod or 0)
1087            c = y.get_continuation_type()
1088            if c != PyParse.C_NONE:
1089                # The current stmt hasn't ended yet.
1090                if c == PyParse.C_STRING:
1091                    # inside a string; just mimic the current indent
1092                    text.insert("insert", indent)
1093                elif c == PyParse.C_BRACKET:
1094                    # line up with the first (if any) element of the
1095                    # last open bracket structure; else indent one
1096                    # level beyond the indent of the line with the
1097                    # last open bracket
1098                    self.reindent_to(y.compute_bracket_indent())
1099                elif c == PyParse.C_BACKSLASH:
1100                    # if more than one line in this stmt already, just
1101                    # mimic the current indent; else if initial line
1102                    # has a start on an assignment stmt, indent to
1103                    # beyond leftmost =; else to beyond first chunk of
1104                    # non-whitespace on initial line
1105                    if y.get_num_lines_in_stmt() > 1:
1106                        text.insert("insert", indent)
1107                    else:
1108                        self.reindent_to(y.compute_backslash_indent())
1109                else:
1110                    assert 0, "bogus continuation type " + `c`
1111                return "break"
1112
1113            # This line starts a brand new stmt; indent relative to
1114            # indentation of initial line of closest preceding
1115            # interesting stmt.
1116            indent = y.get_base_indent_string()
1117            text.insert("insert", indent)
1118            if y.is_block_opener():
1119                self.smart_indent_event(event)
1120            elif indent and y.is_block_closer():
1121                self.smart_backspace_event(event)
1122            return "break"
1123        finally:
1124            text.see("insert")
1125            text.undo_block_stop()
1126
1127    # Our editwin provides a is_char_in_string function that works
1128    # with a Tk text index, but PyParse only knows about offsets into
1129    # a string. This builds a function for PyParse that accepts an
1130    # offset.
1131
1132    def _build_char_in_string_func(self, startindex):
1133        def inner(offset, _startindex=startindex,
1134                  _icis=self.is_char_in_string):
1135            return _icis(_startindex + "+%dc" % offset)
1136        return inner
1137
1138    def indent_region_event(self, event):
1139        head, tail, chars, lines = self.get_region()
1140        for pos in range(len(lines)):
1141            line = lines[pos]
1142            if line:
1143                raw, effective = classifyws(line, self.tabwidth)
1144                effective = effective + self.indentwidth
1145                lines[pos] = self._make_blanks(effective) + line[raw:]
1146        self.set_region(head, tail, chars, lines)
1147        return "break"
1148
1149    def dedent_region_event(self, event):
1150        head, tail, chars, lines = self.get_region()
1151        for pos in range(len(lines)):
1152            line = lines[pos]
1153            if line:
1154                raw, effective = classifyws(line, self.tabwidth)
1155                effective = max(effective - self.indentwidth, 0)
1156                lines[pos] = self._make_blanks(effective) + line[raw:]
1157        self.set_region(head, tail, chars, lines)
1158        return "break"
1159
1160    def comment_region_event(self, event):
1161        head, tail, chars, lines = self.get_region()
1162        for pos in range(len(lines) - 1):
1163            line = lines[pos]
1164            lines[pos] = '##' + line
1165        self.set_region(head, tail, chars, lines)
1166
1167    def uncomment_region_event(self, event):
1168        head, tail, chars, lines = self.get_region()
1169        for pos in range(len(lines)):
1170            line = lines[pos]
1171            if not line:
1172                continue
1173            if line[:2] == '##':
1174                line = line[2:]
1175            elif line[:1] == '#':
1176                line = line[1:]
1177            lines[pos] = line
1178        self.set_region(head, tail, chars, lines)
1179
1180    def tabify_region_event(self, event):
1181        head, tail, chars, lines = self.get_region()
1182        tabwidth = self._asktabwidth()
1183        for pos in range(len(lines)):
1184            line = lines[pos]
1185            if line:
1186                raw, effective = classifyws(line, tabwidth)
1187                ntabs, nspaces = divmod(effective, tabwidth)
1188                lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1189        self.set_region(head, tail, chars, lines)
1190
1191    def untabify_region_event(self, event):
1192        head, tail, chars, lines = self.get_region()
1193        tabwidth = self._asktabwidth()
1194        for pos in range(len(lines)):
1195            lines[pos] = lines[pos].expandtabs(tabwidth)
1196        self.set_region(head, tail, chars, lines)
1197
1198    def toggle_tabs_event(self, event):
1199        if self.askyesno(
1200              "Toggle tabs",
1201              "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1202              parent=self.text):
1203            self.usetabs = not self.usetabs
1204        return "break"
1205
1206    # XXX this isn't bound to anything -- see class tabwidth comments
1207    def change_tabwidth_event(self, event):
1208        new = self._asktabwidth()
1209        if new != self.tabwidth:
1210            self.tabwidth = new
1211            self.set_indentation_params(0, guess=0)
1212        return "break"
1213
1214    def change_indentwidth_event(self, event):
1215        new = self.askinteger(
1216                  "Indent width",
1217                  "New indent width (2-16)",
1218                  parent=self.text,
1219                  initialvalue=self.indentwidth,
1220                  minvalue=2,
1221                  maxvalue=16)
1222        if new and new != self.indentwidth:
1223            self.indentwidth = new
1224        return "break"
1225
1226    def get_region(self):
1227        text = self.text
1228        first, last = self.get_selection_indices()
1229        if first and last:
1230            head = text.index(first + " linestart")
1231            tail = text.index(last + "-1c lineend +1c")
1232        else:
1233            head = text.index("insert linestart")
1234            tail = text.index("insert lineend +1c")
1235        chars = text.get(head, tail)
1236        lines = chars.split("\n")
1237        return head, tail, chars, lines
1238
1239    def set_region(self, head, tail, chars, lines):
1240        text = self.text
1241        newchars = "\n".join(lines)
1242        if newchars == chars:
1243            text.bell()
1244            return
1245        text.tag_remove("sel", "1.0", "end")
1246        text.mark_set("insert", head)
1247        text.undo_block_start()
1248        text.delete(head, tail)
1249        text.insert(head, newchars)
1250        text.undo_block_stop()
1251        text.tag_add("sel", head, "insert")
1252
1253    # Make string that displays as n leading blanks.
1254
1255    def _make_blanks(self, n):
1256        if self.usetabs:
1257            ntabs, nspaces = divmod(n, self.tabwidth)
1258            return '\t' * ntabs + ' ' * nspaces
1259        else:
1260            return ' ' * n
1261
1262    # Delete from beginning of line to insert point, then reinsert
1263    # column logical (meaning use tabs if appropriate) spaces.
1264
1265    def reindent_to(self, column):
1266        text = self.text
1267        text.undo_block_start()
1268        if text.compare("insert linestart", "!=", "insert"):
1269            text.delete("insert linestart", "insert")
1270        if column:
1271            text.insert("insert", self._make_blanks(column))
1272        text.undo_block_stop()
1273
1274    def _asktabwidth(self):
1275        return self.askinteger(
1276            "Tab width",
1277            "Spaces per tab? (2-16)",
1278            parent=self.text,
1279            initialvalue=self.indentwidth,
1280            minvalue=2,
1281            maxvalue=16) or self.tabwidth
1282
1283    # Guess indentwidth from text content.
1284    # Return guessed indentwidth.  This should not be believed unless
1285    # it's in a reasonable range (e.g., it will be 0 if no indented
1286    # blocks are found).
1287
1288    def guess_indent(self):
1289        opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1290        if opener and indented:
1291            raw, indentsmall = classifyws(opener, self.tabwidth)
1292            raw, indentlarge = classifyws(indented, self.tabwidth)
1293        else:
1294            indentsmall = indentlarge = 0
1295        return indentlarge - indentsmall
1296
1297# "line.col" -> line, as an int
1298def index2line(index):
1299    return int(float(index))
1300
1301# Look at the leading whitespace in s.
1302# Return pair (# of leading ws characters,
1303#              effective # of leading blanks after expanding
1304#              tabs to width tabwidth)
1305
1306def classifyws(s, tabwidth):
1307    raw = effective = 0
1308    for ch in s:
1309        if ch == ' ':
1310            raw = raw + 1
1311            effective = effective + 1
1312        elif ch == '\t':
1313            raw = raw + 1
1314            effective = (effective // tabwidth + 1) * tabwidth
1315        else:
1316            break
1317    return raw, effective
1318
1319import tokenize
1320_tokenize = tokenize
1321del tokenize
1322
1323class IndentSearcher:
1324
1325    # .run() chews over the Text widget, looking for a block opener
1326    # and the stmt following it.  Returns a pair,
1327    #     (line containing block opener, line containing stmt)
1328    # Either or both may be None.
1329
1330    def __init__(self, text, tabwidth):
1331        self.text = text
1332        self.tabwidth = tabwidth
1333        self.i = self.finished = 0
1334        self.blkopenline = self.indentedline = None
1335
1336    def readline(self):
1337        if self.finished:
1338            return ""
1339        i = self.i = self.i + 1
1340        mark = `i` + ".0"
1341        if self.text.compare(mark, ">=", "end"):
1342            return ""
1343        return self.text.get(mark, mark + " lineend+1c")
1344
1345    def tokeneater(self, type, token, start, end, line,
1346                   INDENT=_tokenize.INDENT,
1347                   NAME=_tokenize.NAME,
1348                   OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1349        if self.finished:
1350            pass
1351        elif type == NAME and token in OPENERS:
1352            self.blkopenline = line
1353        elif type == INDENT and self.blkopenline:
1354            self.indentedline = line
1355            self.finished = 1
1356
1357    def run(self):
1358        save_tabsize = _tokenize.tabsize
1359        _tokenize.tabsize = self.tabwidth
1360        try:
1361            try:
1362                _tokenize.tokenize(self.readline, self.tokeneater)
1363            except _tokenize.TokenError:
1364                # since we cut off the tokenizer early, we can trigger
1365                # spurious errors
1366                pass
1367        finally:
1368            _tokenize.tabsize = save_tabsize
1369        return self.blkopenline, self.indentedline
1370
1371### end autoindent code ###
1372
1373def prepstr(s):
1374    # Helper to extract the underscore from a string, e.g.
1375    # prepstr("Co_py") returns (2, "Copy").
1376    i = s.find('_')
1377    if i >= 0:
1378        s = s[:i] + s[i+1:]
1379    return i, s
1380
1381
1382keynames = {
1383 'bracketleft': '[',
1384 'bracketright': ']',
1385 'slash': '/',
1386}
1387
1388def get_accelerator(keydefs, event):
1389    keylist = keydefs.get(event)
1390    if not keylist:
1391        return ""
1392    s = keylist[0]
1393    s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
1394    s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1395    s = re.sub("Key-", "", s)
1396    s = re.sub("Cancel","Ctrl-Break",s)   # dscherer@cmu.edu
1397    s = re.sub("Control-", "Ctrl-", s)
1398    s = re.sub("-", "+", s)
1399    s = re.sub("><", " ", s)
1400    s = re.sub("<", "", s)
1401    s = re.sub(">", "", s)
1402    return s
1403
1404
1405def fixwordbreaks(root):
1406    # Make sure that Tk's double-click and next/previous word
1407    # operations use our definition of a word (i.e. an identifier)
1408    tk = root.tk
1409    tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1410    tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1411    tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1412
1413
1414def test():
1415    root = Tk()
1416    fixwordbreaks(root)
1417    root.withdraw()
1418    if sys.argv[1:]:
1419        filename = sys.argv[1]
1420    else:
1421        filename = None
1422    edit = EditorWindow(root=root, filename=filename)
1423    edit.set_close_hook(root.quit)
1424    root.mainloop()
1425    root.destroy()
1426
1427if __name__ == '__main__':
1428    test()
1429