17db96d56Sopenharmony_ciimport importlib.abc
27db96d56Sopenharmony_ciimport importlib.util
37db96d56Sopenharmony_ciimport os
47db96d56Sopenharmony_ciimport platform
57db96d56Sopenharmony_ciimport re
67db96d56Sopenharmony_ciimport string
77db96d56Sopenharmony_ciimport sys
87db96d56Sopenharmony_ciimport tokenize
97db96d56Sopenharmony_ciimport traceback
107db96d56Sopenharmony_ciimport webbrowser
117db96d56Sopenharmony_ci
127db96d56Sopenharmony_cifrom tkinter import *
137db96d56Sopenharmony_cifrom tkinter.font import Font
147db96d56Sopenharmony_cifrom tkinter.ttk import Scrollbar
157db96d56Sopenharmony_cifrom tkinter import simpledialog
167db96d56Sopenharmony_cifrom tkinter import messagebox
177db96d56Sopenharmony_ci
187db96d56Sopenharmony_cifrom idlelib.config import idleConf
197db96d56Sopenharmony_cifrom idlelib import configdialog
207db96d56Sopenharmony_cifrom idlelib import grep
217db96d56Sopenharmony_cifrom idlelib import help
227db96d56Sopenharmony_cifrom idlelib import help_about
237db96d56Sopenharmony_cifrom idlelib import macosx
247db96d56Sopenharmony_cifrom idlelib.multicall import MultiCallCreator
257db96d56Sopenharmony_cifrom idlelib import pyparse
267db96d56Sopenharmony_cifrom idlelib import query
277db96d56Sopenharmony_cifrom idlelib import replace
287db96d56Sopenharmony_cifrom idlelib import search
297db96d56Sopenharmony_cifrom idlelib.tree import wheel_event
307db96d56Sopenharmony_cifrom idlelib.util import py_extensions
317db96d56Sopenharmony_cifrom idlelib import window
327db96d56Sopenharmony_ci
337db96d56Sopenharmony_ci# The default tab setting for a Text widget, in average-width characters.
347db96d56Sopenharmony_ciTK_TABWIDTH_DEFAULT = 8
357db96d56Sopenharmony_ci_py_version = ' (%s)' % platform.python_version()
367db96d56Sopenharmony_cidarwin = sys.platform == 'darwin'
377db96d56Sopenharmony_ci
387db96d56Sopenharmony_cidef _sphinx_version():
397db96d56Sopenharmony_ci    "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
407db96d56Sopenharmony_ci    major, minor, micro, level, serial = sys.version_info
417db96d56Sopenharmony_ci    # TODO remove unneeded function since .chm no longer installed
427db96d56Sopenharmony_ci    release = f'{major}{minor}'
437db96d56Sopenharmony_ci    release += f'{micro}'
447db96d56Sopenharmony_ci    if level == 'candidate':
457db96d56Sopenharmony_ci        release += f'rc{serial}'
467db96d56Sopenharmony_ci    elif level != 'final':
477db96d56Sopenharmony_ci        release += f'{level[0]}{serial}'
487db96d56Sopenharmony_ci    return release
497db96d56Sopenharmony_ci
507db96d56Sopenharmony_ci
517db96d56Sopenharmony_ciclass EditorWindow:
527db96d56Sopenharmony_ci    from idlelib.percolator import Percolator
537db96d56Sopenharmony_ci    from idlelib.colorizer import ColorDelegator, color_config
547db96d56Sopenharmony_ci    from idlelib.undo import UndoDelegator
557db96d56Sopenharmony_ci    from idlelib.iomenu import IOBinding, encoding
567db96d56Sopenharmony_ci    from idlelib import mainmenu
577db96d56Sopenharmony_ci    from idlelib.statusbar import MultiStatusBar
587db96d56Sopenharmony_ci    from idlelib.autocomplete import AutoComplete
597db96d56Sopenharmony_ci    from idlelib.autoexpand import AutoExpand
607db96d56Sopenharmony_ci    from idlelib.calltip import Calltip
617db96d56Sopenharmony_ci    from idlelib.codecontext import CodeContext
627db96d56Sopenharmony_ci    from idlelib.sidebar import LineNumbers
637db96d56Sopenharmony_ci    from idlelib.format import FormatParagraph, FormatRegion, Indents, Rstrip
647db96d56Sopenharmony_ci    from idlelib.parenmatch import ParenMatch
657db96d56Sopenharmony_ci    from idlelib.zoomheight import ZoomHeight
667db96d56Sopenharmony_ci
677db96d56Sopenharmony_ci    filesystemencoding = sys.getfilesystemencoding()  # for file names
687db96d56Sopenharmony_ci    help_url = None
697db96d56Sopenharmony_ci
707db96d56Sopenharmony_ci    allow_code_context = True
717db96d56Sopenharmony_ci    allow_line_numbers = True
727db96d56Sopenharmony_ci    user_input_insert_tags = None
737db96d56Sopenharmony_ci
747db96d56Sopenharmony_ci    def __init__(self, flist=None, filename=None, key=None, root=None):
757db96d56Sopenharmony_ci        # Delay import: runscript imports pyshell imports EditorWindow.
767db96d56Sopenharmony_ci        from idlelib.runscript import ScriptBinding
777db96d56Sopenharmony_ci
787db96d56Sopenharmony_ci        if EditorWindow.help_url is None:
797db96d56Sopenharmony_ci            dochome =  os.path.join(sys.base_prefix, 'Doc', 'index.html')
807db96d56Sopenharmony_ci            if sys.platform.count('linux'):
817db96d56Sopenharmony_ci                # look for html docs in a couple of standard places
827db96d56Sopenharmony_ci                pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
837db96d56Sopenharmony_ci                if os.path.isdir('/var/www/html/python/'):  # "python2" rpm
847db96d56Sopenharmony_ci                    dochome = '/var/www/html/python/index.html'
857db96d56Sopenharmony_ci                else:
867db96d56Sopenharmony_ci                    basepath = '/usr/share/doc/'  # standard location
877db96d56Sopenharmony_ci                    dochome = os.path.join(basepath, pyver,
887db96d56Sopenharmony_ci                                           'Doc', 'index.html')
897db96d56Sopenharmony_ci            elif sys.platform[:3] == 'win':
907db96d56Sopenharmony_ci                import winreg  # Windows only, block only executed once.
917db96d56Sopenharmony_ci                docfile = ''
927db96d56Sopenharmony_ci                KEY = (rf"Software\Python\PythonCore\{sys.winver}"
937db96d56Sopenharmony_ci                        r"\Help\Main Python Documentation")
947db96d56Sopenharmony_ci                try:
957db96d56Sopenharmony_ci                    docfile = winreg.QueryValue(winreg.HKEY_CURRENT_USER, KEY)
967db96d56Sopenharmony_ci                except FileNotFoundError:
977db96d56Sopenharmony_ci                    try:
987db96d56Sopenharmony_ci                        docfile = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE,
997db96d56Sopenharmony_ci                                                    KEY)
1007db96d56Sopenharmony_ci                    except FileNotFoundError:
1017db96d56Sopenharmony_ci                        pass
1027db96d56Sopenharmony_ci                if os.path.isfile(docfile):
1037db96d56Sopenharmony_ci                    dochome = docfile
1047db96d56Sopenharmony_ci            elif sys.platform == 'darwin':
1057db96d56Sopenharmony_ci                # documentation may be stored inside a python framework
1067db96d56Sopenharmony_ci                dochome = os.path.join(sys.base_prefix,
1077db96d56Sopenharmony_ci                        'Resources/English.lproj/Documentation/index.html')
1087db96d56Sopenharmony_ci            dochome = os.path.normpath(dochome)
1097db96d56Sopenharmony_ci            if os.path.isfile(dochome):
1107db96d56Sopenharmony_ci                EditorWindow.help_url = dochome
1117db96d56Sopenharmony_ci                if sys.platform == 'darwin':
1127db96d56Sopenharmony_ci                    # Safari requires real file:-URLs
1137db96d56Sopenharmony_ci                    EditorWindow.help_url = 'file://' + EditorWindow.help_url
1147db96d56Sopenharmony_ci            else:
1157db96d56Sopenharmony_ci                EditorWindow.help_url = ("https://docs.python.org/%d.%d/"
1167db96d56Sopenharmony_ci                                         % sys.version_info[:2])
1177db96d56Sopenharmony_ci        self.flist = flist
1187db96d56Sopenharmony_ci        root = root or flist.root
1197db96d56Sopenharmony_ci        self.root = root
1207db96d56Sopenharmony_ci        self.menubar = Menu(root)
1217db96d56Sopenharmony_ci        self.top = top = window.ListedToplevel(root, menu=self.menubar)
1227db96d56Sopenharmony_ci        if flist:
1237db96d56Sopenharmony_ci            self.tkinter_vars = flist.vars
1247db96d56Sopenharmony_ci            #self.top.instance_dict makes flist.inversedict available to
1257db96d56Sopenharmony_ci            #configdialog.py so it can access all EditorWindow instances
1267db96d56Sopenharmony_ci            self.top.instance_dict = flist.inversedict
1277db96d56Sopenharmony_ci        else:
1287db96d56Sopenharmony_ci            self.tkinter_vars = {}  # keys: Tkinter event names
1297db96d56Sopenharmony_ci                                    # values: Tkinter variable instances
1307db96d56Sopenharmony_ci            self.top.instance_dict = {}
1317db96d56Sopenharmony_ci        self.recent_files_path = idleConf.userdir and os.path.join(
1327db96d56Sopenharmony_ci                idleConf.userdir, 'recent-files.lst')
1337db96d56Sopenharmony_ci
1347db96d56Sopenharmony_ci        self.prompt_last_line = ''  # Override in PyShell
1357db96d56Sopenharmony_ci        self.text_frame = text_frame = Frame(top)
1367db96d56Sopenharmony_ci        self.vbar = vbar = Scrollbar(text_frame, name='vbar')
1377db96d56Sopenharmony_ci        width = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
1387db96d56Sopenharmony_ci        text_options = {
1397db96d56Sopenharmony_ci                'name': 'text',
1407db96d56Sopenharmony_ci                'padx': 5,
1417db96d56Sopenharmony_ci                'wrap': 'none',
1427db96d56Sopenharmony_ci                'highlightthickness': 0,
1437db96d56Sopenharmony_ci                'width': width,
1447db96d56Sopenharmony_ci                'tabstyle': 'wordprocessor',  # new in 8.5
1457db96d56Sopenharmony_ci                'height': idleConf.GetOption(
1467db96d56Sopenharmony_ci                        'main', 'EditorWindow', 'height', type='int'),
1477db96d56Sopenharmony_ci                }
1487db96d56Sopenharmony_ci        self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
1497db96d56Sopenharmony_ci        self.top.focused_widget = self.text
1507db96d56Sopenharmony_ci
1517db96d56Sopenharmony_ci        self.createmenubar()
1527db96d56Sopenharmony_ci        self.apply_bindings()
1537db96d56Sopenharmony_ci
1547db96d56Sopenharmony_ci        self.top.protocol("WM_DELETE_WINDOW", self.close)
1557db96d56Sopenharmony_ci        self.top.bind("<<close-window>>", self.close_event)
1567db96d56Sopenharmony_ci        if macosx.isAquaTk():
1577db96d56Sopenharmony_ci            # Command-W on editor windows doesn't work without this.
1587db96d56Sopenharmony_ci            text.bind('<<close-window>>', self.close_event)
1597db96d56Sopenharmony_ci            # Some OS X systems have only one mouse button, so use
1607db96d56Sopenharmony_ci            # control-click for popup context menus there. For two
1617db96d56Sopenharmony_ci            # buttons, AquaTk defines <2> as the right button, not <3>.
1627db96d56Sopenharmony_ci            text.bind("<Control-Button-1>",self.right_menu_event)
1637db96d56Sopenharmony_ci            text.bind("<2>", self.right_menu_event)
1647db96d56Sopenharmony_ci        else:
1657db96d56Sopenharmony_ci            # Elsewhere, use right-click for popup menus.
1667db96d56Sopenharmony_ci            text.bind("<3>",self.right_menu_event)
1677db96d56Sopenharmony_ci
1687db96d56Sopenharmony_ci        text.bind('<MouseWheel>', wheel_event)
1697db96d56Sopenharmony_ci        text.bind('<Button-4>', wheel_event)
1707db96d56Sopenharmony_ci        text.bind('<Button-5>', wheel_event)
1717db96d56Sopenharmony_ci        text.bind('<Configure>', self.handle_winconfig)
1727db96d56Sopenharmony_ci        text.bind("<<cut>>", self.cut)
1737db96d56Sopenharmony_ci        text.bind("<<copy>>", self.copy)
1747db96d56Sopenharmony_ci        text.bind("<<paste>>", self.paste)
1757db96d56Sopenharmony_ci        text.bind("<<center-insert>>", self.center_insert_event)
1767db96d56Sopenharmony_ci        text.bind("<<help>>", self.help_dialog)
1777db96d56Sopenharmony_ci        text.bind("<<python-docs>>", self.python_docs)
1787db96d56Sopenharmony_ci        text.bind("<<about-idle>>", self.about_dialog)
1797db96d56Sopenharmony_ci        text.bind("<<open-config-dialog>>", self.config_dialog)
1807db96d56Sopenharmony_ci        text.bind("<<open-module>>", self.open_module_event)
1817db96d56Sopenharmony_ci        text.bind("<<do-nothing>>", lambda event: "break")
1827db96d56Sopenharmony_ci        text.bind("<<select-all>>", self.select_all)
1837db96d56Sopenharmony_ci        text.bind("<<remove-selection>>", self.remove_selection)
1847db96d56Sopenharmony_ci        text.bind("<<find>>", self.find_event)
1857db96d56Sopenharmony_ci        text.bind("<<find-again>>", self.find_again_event)
1867db96d56Sopenharmony_ci        text.bind("<<find-in-files>>", self.find_in_files_event)
1877db96d56Sopenharmony_ci        text.bind("<<find-selection>>", self.find_selection_event)
1887db96d56Sopenharmony_ci        text.bind("<<replace>>", self.replace_event)
1897db96d56Sopenharmony_ci        text.bind("<<goto-line>>", self.goto_line_event)
1907db96d56Sopenharmony_ci        text.bind("<<smart-backspace>>",self.smart_backspace_event)
1917db96d56Sopenharmony_ci        text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
1927db96d56Sopenharmony_ci        text.bind("<<smart-indent>>",self.smart_indent_event)
1937db96d56Sopenharmony_ci        self.fregion = fregion = self.FormatRegion(self)
1947db96d56Sopenharmony_ci        # self.fregion used in smart_indent_event to access indent_region.
1957db96d56Sopenharmony_ci        text.bind("<<indent-region>>", fregion.indent_region_event)
1967db96d56Sopenharmony_ci        text.bind("<<dedent-region>>", fregion.dedent_region_event)
1977db96d56Sopenharmony_ci        text.bind("<<comment-region>>", fregion.comment_region_event)
1987db96d56Sopenharmony_ci        text.bind("<<uncomment-region>>", fregion.uncomment_region_event)
1997db96d56Sopenharmony_ci        text.bind("<<tabify-region>>", fregion.tabify_region_event)
2007db96d56Sopenharmony_ci        text.bind("<<untabify-region>>", fregion.untabify_region_event)
2017db96d56Sopenharmony_ci        indents = self.Indents(self)
2027db96d56Sopenharmony_ci        text.bind("<<toggle-tabs>>", indents.toggle_tabs_event)
2037db96d56Sopenharmony_ci        text.bind("<<change-indentwidth>>", indents.change_indentwidth_event)
2047db96d56Sopenharmony_ci        text.bind("<Left>", self.move_at_edge_if_selection(0))
2057db96d56Sopenharmony_ci        text.bind("<Right>", self.move_at_edge_if_selection(1))
2067db96d56Sopenharmony_ci        text.bind("<<del-word-left>>", self.del_word_left)
2077db96d56Sopenharmony_ci        text.bind("<<del-word-right>>", self.del_word_right)
2087db96d56Sopenharmony_ci        text.bind("<<beginning-of-line>>", self.home_callback)
2097db96d56Sopenharmony_ci
2107db96d56Sopenharmony_ci        if flist:
2117db96d56Sopenharmony_ci            flist.inversedict[self] = key
2127db96d56Sopenharmony_ci            if key:
2137db96d56Sopenharmony_ci                flist.dict[key] = self
2147db96d56Sopenharmony_ci            text.bind("<<open-new-window>>", self.new_callback)
2157db96d56Sopenharmony_ci            text.bind("<<close-all-windows>>", self.flist.close_all_callback)
2167db96d56Sopenharmony_ci            text.bind("<<open-class-browser>>", self.open_module_browser)
2177db96d56Sopenharmony_ci            text.bind("<<open-path-browser>>", self.open_path_browser)
2187db96d56Sopenharmony_ci            text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
2197db96d56Sopenharmony_ci
2207db96d56Sopenharmony_ci        self.set_status_bar()
2217db96d56Sopenharmony_ci        text_frame.pack(side=LEFT, fill=BOTH, expand=1)
2227db96d56Sopenharmony_ci        text_frame.rowconfigure(1, weight=1)
2237db96d56Sopenharmony_ci        text_frame.columnconfigure(1, weight=1)
2247db96d56Sopenharmony_ci        vbar['command'] = self.handle_yview
2257db96d56Sopenharmony_ci        vbar.grid(row=1, column=2, sticky=NSEW)
2267db96d56Sopenharmony_ci        text['yscrollcommand'] = vbar.set
2277db96d56Sopenharmony_ci        text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
2287db96d56Sopenharmony_ci        text.grid(row=1, column=1, sticky=NSEW)
2297db96d56Sopenharmony_ci        text.focus_set()
2307db96d56Sopenharmony_ci        self.set_width()
2317db96d56Sopenharmony_ci
2327db96d56Sopenharmony_ci        # usetabs true  -> literal tab characters are used by indent and
2337db96d56Sopenharmony_ci        #                  dedent cmds, possibly mixed with spaces if
2347db96d56Sopenharmony_ci        #                  indentwidth is not a multiple of tabwidth,
2357db96d56Sopenharmony_ci        #                  which will cause Tabnanny to nag!
2367db96d56Sopenharmony_ci        #         false -> tab characters are converted to spaces by indent
2377db96d56Sopenharmony_ci        #                  and dedent cmds, and ditto TAB keystrokes
2387db96d56Sopenharmony_ci        # Although use-spaces=0 can be configured manually in config-main.def,
2397db96d56Sopenharmony_ci        # configuration of tabs v. spaces is not supported in the configuration
2407db96d56Sopenharmony_ci        # dialog.  IDLE promotes the preferred Python indentation: use spaces!
2417db96d56Sopenharmony_ci        usespaces = idleConf.GetOption('main', 'Indent',
2427db96d56Sopenharmony_ci                                       'use-spaces', type='bool')
2437db96d56Sopenharmony_ci        self.usetabs = not usespaces
2447db96d56Sopenharmony_ci
2457db96d56Sopenharmony_ci        # tabwidth is the display width of a literal tab character.
2467db96d56Sopenharmony_ci        # CAUTION:  telling Tk to use anything other than its default
2477db96d56Sopenharmony_ci        # tab setting causes it to use an entirely different tabbing algorithm,
2487db96d56Sopenharmony_ci        # treating tab stops as fixed distances from the left margin.
2497db96d56Sopenharmony_ci        # Nobody expects this, so for now tabwidth should never be changed.
2507db96d56Sopenharmony_ci        self.tabwidth = 8    # must remain 8 until Tk is fixed.
2517db96d56Sopenharmony_ci
2527db96d56Sopenharmony_ci        # indentwidth is the number of screen characters per indent level.
2537db96d56Sopenharmony_ci        # The recommended Python indentation is four spaces.
2547db96d56Sopenharmony_ci        self.indentwidth = self.tabwidth
2557db96d56Sopenharmony_ci        self.set_notabs_indentwidth()
2567db96d56Sopenharmony_ci
2577db96d56Sopenharmony_ci        # Store the current value of the insertofftime now so we can restore
2587db96d56Sopenharmony_ci        # it if needed.
2597db96d56Sopenharmony_ci        if not hasattr(idleConf, 'blink_off_time'):
2607db96d56Sopenharmony_ci            idleConf.blink_off_time = self.text['insertofftime']
2617db96d56Sopenharmony_ci        self.update_cursor_blink()
2627db96d56Sopenharmony_ci
2637db96d56Sopenharmony_ci        # When searching backwards for a reliable place to begin parsing,
2647db96d56Sopenharmony_ci        # first start num_context_lines[0] lines back, then
2657db96d56Sopenharmony_ci        # num_context_lines[1] lines back if that didn't work, and so on.
2667db96d56Sopenharmony_ci        # The last value should be huge (larger than the # of lines in a
2677db96d56Sopenharmony_ci        # conceivable file).
2687db96d56Sopenharmony_ci        # Making the initial values larger slows things down more often.
2697db96d56Sopenharmony_ci        self.num_context_lines = 50, 500, 5000000
2707db96d56Sopenharmony_ci        self.per = per = self.Percolator(text)
2717db96d56Sopenharmony_ci        self.undo = undo = self.UndoDelegator()
2727db96d56Sopenharmony_ci        per.insertfilter(undo)
2737db96d56Sopenharmony_ci        text.undo_block_start = undo.undo_block_start
2747db96d56Sopenharmony_ci        text.undo_block_stop = undo.undo_block_stop
2757db96d56Sopenharmony_ci        undo.set_saved_change_hook(self.saved_change_hook)
2767db96d56Sopenharmony_ci        # IOBinding implements file I/O and printing functionality
2777db96d56Sopenharmony_ci        self.io = io = self.IOBinding(self)
2787db96d56Sopenharmony_ci        io.set_filename_change_hook(self.filename_change_hook)
2797db96d56Sopenharmony_ci        self.good_load = False
2807db96d56Sopenharmony_ci        self.set_indentation_params(False)
2817db96d56Sopenharmony_ci        self.color = None # initialized below in self.ResetColorizer
2827db96d56Sopenharmony_ci        self.code_context = None # optionally initialized later below
2837db96d56Sopenharmony_ci        self.line_numbers = None # optionally initialized later below
2847db96d56Sopenharmony_ci        if filename:
2857db96d56Sopenharmony_ci            if os.path.exists(filename) and not os.path.isdir(filename):
2867db96d56Sopenharmony_ci                if io.loadfile(filename):
2877db96d56Sopenharmony_ci                    self.good_load = True
2887db96d56Sopenharmony_ci                    is_py_src = self.ispythonsource(filename)
2897db96d56Sopenharmony_ci                    self.set_indentation_params(is_py_src)
2907db96d56Sopenharmony_ci            else:
2917db96d56Sopenharmony_ci                io.set_filename(filename)
2927db96d56Sopenharmony_ci                self.good_load = True
2937db96d56Sopenharmony_ci
2947db96d56Sopenharmony_ci        self.ResetColorizer()
2957db96d56Sopenharmony_ci        self.saved_change_hook()
2967db96d56Sopenharmony_ci        self.update_recent_files_list()
2977db96d56Sopenharmony_ci        self.load_extensions()
2987db96d56Sopenharmony_ci        menu = self.menudict.get('window')
2997db96d56Sopenharmony_ci        if menu:
3007db96d56Sopenharmony_ci            end = menu.index("end")
3017db96d56Sopenharmony_ci            if end is None:
3027db96d56Sopenharmony_ci                end = -1
3037db96d56Sopenharmony_ci            if end >= 0:
3047db96d56Sopenharmony_ci                menu.add_separator()
3057db96d56Sopenharmony_ci                end = end + 1
3067db96d56Sopenharmony_ci            self.wmenu_end = end
3077db96d56Sopenharmony_ci            window.register_callback(self.postwindowsmenu)
3087db96d56Sopenharmony_ci
3097db96d56Sopenharmony_ci        # Some abstractions so IDLE extensions are cross-IDE
3107db96d56Sopenharmony_ci        self.askinteger = simpledialog.askinteger
3117db96d56Sopenharmony_ci        self.askyesno = messagebox.askyesno
3127db96d56Sopenharmony_ci        self.showerror = messagebox.showerror
3137db96d56Sopenharmony_ci
3147db96d56Sopenharmony_ci        # Add pseudoevents for former extension fixed keys.
3157db96d56Sopenharmony_ci        # (This probably needs to be done once in the process.)
3167db96d56Sopenharmony_ci        text.event_add('<<autocomplete>>', '<Key-Tab>')
3177db96d56Sopenharmony_ci        text.event_add('<<try-open-completions>>', '<KeyRelease-period>',
3187db96d56Sopenharmony_ci                       '<KeyRelease-slash>', '<KeyRelease-backslash>')
3197db96d56Sopenharmony_ci        text.event_add('<<try-open-calltip>>', '<KeyRelease-parenleft>')
3207db96d56Sopenharmony_ci        text.event_add('<<refresh-calltip>>', '<KeyRelease-parenright>')
3217db96d56Sopenharmony_ci        text.event_add('<<paren-closed>>', '<KeyRelease-parenright>',
3227db96d56Sopenharmony_ci                       '<KeyRelease-bracketright>', '<KeyRelease-braceright>')
3237db96d56Sopenharmony_ci
3247db96d56Sopenharmony_ci        # Former extension bindings depends on frame.text being packed
3257db96d56Sopenharmony_ci        # (called from self.ResetColorizer()).
3267db96d56Sopenharmony_ci        autocomplete = self.AutoComplete(self, self.user_input_insert_tags)
3277db96d56Sopenharmony_ci        text.bind("<<autocomplete>>", autocomplete.autocomplete_event)
3287db96d56Sopenharmony_ci        text.bind("<<try-open-completions>>",
3297db96d56Sopenharmony_ci                  autocomplete.try_open_completions_event)
3307db96d56Sopenharmony_ci        text.bind("<<force-open-completions>>",
3317db96d56Sopenharmony_ci                  autocomplete.force_open_completions_event)
3327db96d56Sopenharmony_ci        text.bind("<<expand-word>>", self.AutoExpand(self).expand_word_event)
3337db96d56Sopenharmony_ci        text.bind("<<format-paragraph>>",
3347db96d56Sopenharmony_ci                  self.FormatParagraph(self).format_paragraph_event)
3357db96d56Sopenharmony_ci        parenmatch = self.ParenMatch(self)
3367db96d56Sopenharmony_ci        text.bind("<<flash-paren>>", parenmatch.flash_paren_event)
3377db96d56Sopenharmony_ci        text.bind("<<paren-closed>>", parenmatch.paren_closed_event)
3387db96d56Sopenharmony_ci        scriptbinding = ScriptBinding(self)
3397db96d56Sopenharmony_ci        text.bind("<<check-module>>", scriptbinding.check_module_event)
3407db96d56Sopenharmony_ci        text.bind("<<run-module>>", scriptbinding.run_module_event)
3417db96d56Sopenharmony_ci        text.bind("<<run-custom>>", scriptbinding.run_custom_event)
3427db96d56Sopenharmony_ci        text.bind("<<do-rstrip>>", self.Rstrip(self).do_rstrip)
3437db96d56Sopenharmony_ci        self.ctip = ctip = self.Calltip(self)
3447db96d56Sopenharmony_ci        text.bind("<<try-open-calltip>>", ctip.try_open_calltip_event)
3457db96d56Sopenharmony_ci        #refresh-calltip must come after paren-closed to work right
3467db96d56Sopenharmony_ci        text.bind("<<refresh-calltip>>", ctip.refresh_calltip_event)
3477db96d56Sopenharmony_ci        text.bind("<<force-open-calltip>>", ctip.force_open_calltip_event)
3487db96d56Sopenharmony_ci        text.bind("<<zoom-height>>", self.ZoomHeight(self).zoom_height_event)
3497db96d56Sopenharmony_ci        if self.allow_code_context:
3507db96d56Sopenharmony_ci            self.code_context = self.CodeContext(self)
3517db96d56Sopenharmony_ci            text.bind("<<toggle-code-context>>",
3527db96d56Sopenharmony_ci                      self.code_context.toggle_code_context_event)
3537db96d56Sopenharmony_ci        else:
3547db96d56Sopenharmony_ci            self.update_menu_state('options', '*ode*ontext', 'disabled')
3557db96d56Sopenharmony_ci        if self.allow_line_numbers:
3567db96d56Sopenharmony_ci            self.line_numbers = self.LineNumbers(self)
3577db96d56Sopenharmony_ci            if idleConf.GetOption('main', 'EditorWindow',
3587db96d56Sopenharmony_ci                                  'line-numbers-default', type='bool'):
3597db96d56Sopenharmony_ci                self.toggle_line_numbers_event()
3607db96d56Sopenharmony_ci            text.bind("<<toggle-line-numbers>>", self.toggle_line_numbers_event)
3617db96d56Sopenharmony_ci        else:
3627db96d56Sopenharmony_ci            self.update_menu_state('options', '*ine*umbers', 'disabled')
3637db96d56Sopenharmony_ci
3647db96d56Sopenharmony_ci    def handle_winconfig(self, event=None):
3657db96d56Sopenharmony_ci        self.set_width()
3667db96d56Sopenharmony_ci
3677db96d56Sopenharmony_ci    def set_width(self):
3687db96d56Sopenharmony_ci        text = self.text
3697db96d56Sopenharmony_ci        inner_padding = sum(map(text.tk.getint, [text.cget('border'),
3707db96d56Sopenharmony_ci                                                 text.cget('padx')]))
3717db96d56Sopenharmony_ci        pixel_width = text.winfo_width() - 2 * inner_padding
3727db96d56Sopenharmony_ci
3737db96d56Sopenharmony_ci        # Divide the width of the Text widget by the font width,
3747db96d56Sopenharmony_ci        # which is taken to be the width of '0' (zero).
3757db96d56Sopenharmony_ci        # http://www.tcl.tk/man/tcl8.6/TkCmd/text.htm#M21
3767db96d56Sopenharmony_ci        zero_char_width = \
3777db96d56Sopenharmony_ci            Font(text, font=text.cget('font')).measure('0')
3787db96d56Sopenharmony_ci        self.width = pixel_width // zero_char_width
3797db96d56Sopenharmony_ci
3807db96d56Sopenharmony_ci    def new_callback(self, event):
3817db96d56Sopenharmony_ci        dirname, basename = self.io.defaultfilename()
3827db96d56Sopenharmony_ci        self.flist.new(dirname)
3837db96d56Sopenharmony_ci        return "break"
3847db96d56Sopenharmony_ci
3857db96d56Sopenharmony_ci    def home_callback(self, event):
3867db96d56Sopenharmony_ci        if (event.state & 4) != 0 and event.keysym == "Home":
3877db96d56Sopenharmony_ci            # state&4==Control. If <Control-Home>, use the Tk binding.
3887db96d56Sopenharmony_ci            return None
3897db96d56Sopenharmony_ci        if self.text.index("iomark") and \
3907db96d56Sopenharmony_ci           self.text.compare("iomark", "<=", "insert lineend") and \
3917db96d56Sopenharmony_ci           self.text.compare("insert linestart", "<=", "iomark"):
3927db96d56Sopenharmony_ci            # In Shell on input line, go to just after prompt
3937db96d56Sopenharmony_ci            insertpt = int(self.text.index("iomark").split(".")[1])
3947db96d56Sopenharmony_ci        else:
3957db96d56Sopenharmony_ci            line = self.text.get("insert linestart", "insert lineend")
3967db96d56Sopenharmony_ci            for insertpt in range(len(line)):
3977db96d56Sopenharmony_ci                if line[insertpt] not in (' ','\t'):
3987db96d56Sopenharmony_ci                    break
3997db96d56Sopenharmony_ci            else:
4007db96d56Sopenharmony_ci                insertpt=len(line)
4017db96d56Sopenharmony_ci        lineat = int(self.text.index("insert").split('.')[1])
4027db96d56Sopenharmony_ci        if insertpt == lineat:
4037db96d56Sopenharmony_ci            insertpt = 0
4047db96d56Sopenharmony_ci        dest = "insert linestart+"+str(insertpt)+"c"
4057db96d56Sopenharmony_ci        if (event.state&1) == 0:
4067db96d56Sopenharmony_ci            # shift was not pressed
4077db96d56Sopenharmony_ci            self.text.tag_remove("sel", "1.0", "end")
4087db96d56Sopenharmony_ci        else:
4097db96d56Sopenharmony_ci            if not self.text.index("sel.first"):
4107db96d56Sopenharmony_ci                # there was no previous selection
4117db96d56Sopenharmony_ci                self.text.mark_set("my_anchor", "insert")
4127db96d56Sopenharmony_ci            else:
4137db96d56Sopenharmony_ci                if self.text.compare(self.text.index("sel.first"), "<",
4147db96d56Sopenharmony_ci                                     self.text.index("insert")):
4157db96d56Sopenharmony_ci                    self.text.mark_set("my_anchor", "sel.first") # extend back
4167db96d56Sopenharmony_ci                else:
4177db96d56Sopenharmony_ci                    self.text.mark_set("my_anchor", "sel.last") # extend forward
4187db96d56Sopenharmony_ci            first = self.text.index(dest)
4197db96d56Sopenharmony_ci            last = self.text.index("my_anchor")
4207db96d56Sopenharmony_ci            if self.text.compare(first,">",last):
4217db96d56Sopenharmony_ci                first,last = last,first
4227db96d56Sopenharmony_ci            self.text.tag_remove("sel", "1.0", "end")
4237db96d56Sopenharmony_ci            self.text.tag_add("sel", first, last)
4247db96d56Sopenharmony_ci        self.text.mark_set("insert", dest)
4257db96d56Sopenharmony_ci        self.text.see("insert")
4267db96d56Sopenharmony_ci        return "break"
4277db96d56Sopenharmony_ci
4287db96d56Sopenharmony_ci    def set_status_bar(self):
4297db96d56Sopenharmony_ci        self.status_bar = self.MultiStatusBar(self.top)
4307db96d56Sopenharmony_ci        sep = Frame(self.top, height=1, borderwidth=1, background='grey75')
4317db96d56Sopenharmony_ci        if sys.platform == "darwin":
4327db96d56Sopenharmony_ci            # Insert some padding to avoid obscuring some of the statusbar
4337db96d56Sopenharmony_ci            # by the resize widget.
4347db96d56Sopenharmony_ci            self.status_bar.set_label('_padding1', '    ', side=RIGHT)
4357db96d56Sopenharmony_ci        self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
4367db96d56Sopenharmony_ci        self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
4377db96d56Sopenharmony_ci        self.status_bar.pack(side=BOTTOM, fill=X)
4387db96d56Sopenharmony_ci        sep.pack(side=BOTTOM, fill=X)
4397db96d56Sopenharmony_ci        self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
4407db96d56Sopenharmony_ci        self.text.event_add("<<set-line-and-column>>",
4417db96d56Sopenharmony_ci                            "<KeyRelease>", "<ButtonRelease>")
4427db96d56Sopenharmony_ci        self.text.after_idle(self.set_line_and_column)
4437db96d56Sopenharmony_ci
4447db96d56Sopenharmony_ci    def set_line_and_column(self, event=None):
4457db96d56Sopenharmony_ci        line, column = self.text.index(INSERT).split('.')
4467db96d56Sopenharmony_ci        self.status_bar.set_label('column', 'Col: %s' % column)
4477db96d56Sopenharmony_ci        self.status_bar.set_label('line', 'Ln: %s' % line)
4487db96d56Sopenharmony_ci
4497db96d56Sopenharmony_ci
4507db96d56Sopenharmony_ci    """ Menu definitions and functions.
4517db96d56Sopenharmony_ci    * self.menubar - the always visible horizontal menu bar.
4527db96d56Sopenharmony_ci    * mainmenu.menudefs - a list of tuples, one for each menubar item.
4537db96d56Sopenharmony_ci      Each tuple pairs a lower-case name and list of dropdown items.
4547db96d56Sopenharmony_ci      Each item is a name, virtual event pair or None for separator.
4557db96d56Sopenharmony_ci    * mainmenu.default_keydefs - maps events to keys.
4567db96d56Sopenharmony_ci    * text.keydefs - same.
4577db96d56Sopenharmony_ci    * cls.menu_specs - menubar name, titlecase display form pairs
4587db96d56Sopenharmony_ci      with Alt-hotkey indicator.  A subset of menudefs items.
4597db96d56Sopenharmony_ci    * self.menudict - map menu name to dropdown menu.
4607db96d56Sopenharmony_ci    * self.recent_files_menu - 2nd level cascade in the file cascade.
4617db96d56Sopenharmony_ci    * self.wmenu_end - set in __init__ (purpose unclear).
4627db96d56Sopenharmony_ci
4637db96d56Sopenharmony_ci    createmenubar, postwindowsmenu, update_menu_label, update_menu_state,
4647db96d56Sopenharmony_ci    ApplyKeybings (2nd part), reset_help_menu_entries,
4657db96d56Sopenharmony_ci    _extra_help_callback, update_recent_files_list,
4667db96d56Sopenharmony_ci    apply_bindings, fill_menus, (other functions?)
4677db96d56Sopenharmony_ci    """
4687db96d56Sopenharmony_ci
4697db96d56Sopenharmony_ci    menu_specs = [
4707db96d56Sopenharmony_ci        ("file", "_File"),
4717db96d56Sopenharmony_ci        ("edit", "_Edit"),
4727db96d56Sopenharmony_ci        ("format", "F_ormat"),
4737db96d56Sopenharmony_ci        ("run", "_Run"),
4747db96d56Sopenharmony_ci        ("options", "_Options"),
4757db96d56Sopenharmony_ci        ("window", "_Window"),
4767db96d56Sopenharmony_ci        ("help", "_Help"),
4777db96d56Sopenharmony_ci    ]
4787db96d56Sopenharmony_ci
4797db96d56Sopenharmony_ci    def createmenubar(self):
4807db96d56Sopenharmony_ci        """Populate the menu bar widget for the editor window.
4817db96d56Sopenharmony_ci
4827db96d56Sopenharmony_ci        Each option on the menubar is itself a cascade-type Menu widget
4837db96d56Sopenharmony_ci        with the menubar as the parent.  The names, labels, and menu
4847db96d56Sopenharmony_ci        shortcuts for the menubar items are stored in menu_specs.  Each
4857db96d56Sopenharmony_ci        submenu is subsequently populated in fill_menus(), except for
4867db96d56Sopenharmony_ci        'Recent Files' which is added to the File menu here.
4877db96d56Sopenharmony_ci
4887db96d56Sopenharmony_ci        Instance variables:
4897db96d56Sopenharmony_ci        menubar: Menu widget containing first level menu items.
4907db96d56Sopenharmony_ci        menudict: Dictionary of {menuname: Menu instance} items.  The keys
4917db96d56Sopenharmony_ci            represent the valid menu items for this window and may be a
4927db96d56Sopenharmony_ci            subset of all the menudefs available.
4937db96d56Sopenharmony_ci        recent_files_menu: Menu widget contained within the 'file' menudict.
4947db96d56Sopenharmony_ci        """
4957db96d56Sopenharmony_ci        mbar = self.menubar
4967db96d56Sopenharmony_ci        self.menudict = menudict = {}
4977db96d56Sopenharmony_ci        for name, label in self.menu_specs:
4987db96d56Sopenharmony_ci            underline, label = prepstr(label)
4997db96d56Sopenharmony_ci            postcommand = getattr(self, f'{name}_menu_postcommand', None)
5007db96d56Sopenharmony_ci            menudict[name] = menu = Menu(mbar, name=name, tearoff=0,
5017db96d56Sopenharmony_ci                                         postcommand=postcommand)
5027db96d56Sopenharmony_ci            mbar.add_cascade(label=label, menu=menu, underline=underline)
5037db96d56Sopenharmony_ci        if macosx.isCarbonTk():
5047db96d56Sopenharmony_ci            # Insert the application menu
5057db96d56Sopenharmony_ci            menudict['application'] = menu = Menu(mbar, name='apple',
5067db96d56Sopenharmony_ci                                                  tearoff=0)
5077db96d56Sopenharmony_ci            mbar.add_cascade(label='IDLE', menu=menu)
5087db96d56Sopenharmony_ci        self.fill_menus()
5097db96d56Sopenharmony_ci        self.recent_files_menu = Menu(self.menubar, tearoff=0)
5107db96d56Sopenharmony_ci        self.menudict['file'].insert_cascade(3, label='Recent Files',
5117db96d56Sopenharmony_ci                                             underline=0,
5127db96d56Sopenharmony_ci                                             menu=self.recent_files_menu)
5137db96d56Sopenharmony_ci        self.base_helpmenu_length = self.menudict['help'].index(END)
5147db96d56Sopenharmony_ci        self.reset_help_menu_entries()
5157db96d56Sopenharmony_ci
5167db96d56Sopenharmony_ci    def postwindowsmenu(self):
5177db96d56Sopenharmony_ci        """Callback to register window.
5187db96d56Sopenharmony_ci
5197db96d56Sopenharmony_ci        Only called when Window menu exists.
5207db96d56Sopenharmony_ci        """
5217db96d56Sopenharmony_ci        menu = self.menudict['window']
5227db96d56Sopenharmony_ci        end = menu.index("end")
5237db96d56Sopenharmony_ci        if end is None:
5247db96d56Sopenharmony_ci            end = -1
5257db96d56Sopenharmony_ci        if end > self.wmenu_end:
5267db96d56Sopenharmony_ci            menu.delete(self.wmenu_end+1, end)
5277db96d56Sopenharmony_ci        window.add_windows_to_menu(menu)
5287db96d56Sopenharmony_ci
5297db96d56Sopenharmony_ci    def update_menu_label(self, menu, index, label):
5307db96d56Sopenharmony_ci        "Update label for menu item at index."
5317db96d56Sopenharmony_ci        menuitem = self.menudict[menu]
5327db96d56Sopenharmony_ci        menuitem.entryconfig(index, label=label)
5337db96d56Sopenharmony_ci
5347db96d56Sopenharmony_ci    def update_menu_state(self, menu, index, state):
5357db96d56Sopenharmony_ci        "Update state for menu item at index."
5367db96d56Sopenharmony_ci        menuitem = self.menudict[menu]
5377db96d56Sopenharmony_ci        menuitem.entryconfig(index, state=state)
5387db96d56Sopenharmony_ci
5397db96d56Sopenharmony_ci    def handle_yview(self, event, *args):
5407db96d56Sopenharmony_ci        "Handle scrollbar."
5417db96d56Sopenharmony_ci        if event == 'moveto':
5427db96d56Sopenharmony_ci            fraction = float(args[0])
5437db96d56Sopenharmony_ci            lines = (round(self.getlineno('end') * fraction) -
5447db96d56Sopenharmony_ci                     self.getlineno('@0,0'))
5457db96d56Sopenharmony_ci            event = 'scroll'
5467db96d56Sopenharmony_ci            args = (lines, 'units')
5477db96d56Sopenharmony_ci        self.text.yview(event, *args)
5487db96d56Sopenharmony_ci        return 'break'
5497db96d56Sopenharmony_ci
5507db96d56Sopenharmony_ci    rmenu = None
5517db96d56Sopenharmony_ci
5527db96d56Sopenharmony_ci    def right_menu_event(self, event):
5537db96d56Sopenharmony_ci        text = self.text
5547db96d56Sopenharmony_ci        newdex = text.index(f'@{event.x},{event.y}')
5557db96d56Sopenharmony_ci        try:
5567db96d56Sopenharmony_ci            in_selection = (text.compare('sel.first', '<=', newdex) and
5577db96d56Sopenharmony_ci                           text.compare(newdex, '<=',  'sel.last'))
5587db96d56Sopenharmony_ci        except TclError:
5597db96d56Sopenharmony_ci            in_selection = False
5607db96d56Sopenharmony_ci        if not in_selection:
5617db96d56Sopenharmony_ci            text.tag_remove("sel", "1.0", "end")
5627db96d56Sopenharmony_ci            text.mark_set("insert", newdex)
5637db96d56Sopenharmony_ci        if not self.rmenu:
5647db96d56Sopenharmony_ci            self.make_rmenu()
5657db96d56Sopenharmony_ci        rmenu = self.rmenu
5667db96d56Sopenharmony_ci        self.event = event
5677db96d56Sopenharmony_ci        iswin = sys.platform[:3] == 'win'
5687db96d56Sopenharmony_ci        if iswin:
5697db96d56Sopenharmony_ci            text.config(cursor="arrow")
5707db96d56Sopenharmony_ci
5717db96d56Sopenharmony_ci        for item in self.rmenu_specs:
5727db96d56Sopenharmony_ci            try:
5737db96d56Sopenharmony_ci                label, eventname, verify_state = item
5747db96d56Sopenharmony_ci            except ValueError: # see issue1207589
5757db96d56Sopenharmony_ci                continue
5767db96d56Sopenharmony_ci
5777db96d56Sopenharmony_ci            if verify_state is None:
5787db96d56Sopenharmony_ci                continue
5797db96d56Sopenharmony_ci            state = getattr(self, verify_state)()
5807db96d56Sopenharmony_ci            rmenu.entryconfigure(label, state=state)
5817db96d56Sopenharmony_ci
5827db96d56Sopenharmony_ci        rmenu.tk_popup(event.x_root, event.y_root)
5837db96d56Sopenharmony_ci        if iswin:
5847db96d56Sopenharmony_ci            self.text.config(cursor="ibeam")
5857db96d56Sopenharmony_ci        return "break"
5867db96d56Sopenharmony_ci
5877db96d56Sopenharmony_ci    rmenu_specs = [
5887db96d56Sopenharmony_ci        # ("Label", "<<virtual-event>>", "statefuncname"), ...
5897db96d56Sopenharmony_ci        ("Close", "<<close-window>>", None), # Example
5907db96d56Sopenharmony_ci    ]
5917db96d56Sopenharmony_ci
5927db96d56Sopenharmony_ci    def make_rmenu(self):
5937db96d56Sopenharmony_ci        rmenu = Menu(self.text, tearoff=0)
5947db96d56Sopenharmony_ci        for item in self.rmenu_specs:
5957db96d56Sopenharmony_ci            label, eventname = item[0], item[1]
5967db96d56Sopenharmony_ci            if label is not None:
5977db96d56Sopenharmony_ci                def command(text=self.text, eventname=eventname):
5987db96d56Sopenharmony_ci                    text.event_generate(eventname)
5997db96d56Sopenharmony_ci                rmenu.add_command(label=label, command=command)
6007db96d56Sopenharmony_ci            else:
6017db96d56Sopenharmony_ci                rmenu.add_separator()
6027db96d56Sopenharmony_ci        self.rmenu = rmenu
6037db96d56Sopenharmony_ci
6047db96d56Sopenharmony_ci    def rmenu_check_cut(self):
6057db96d56Sopenharmony_ci        return self.rmenu_check_copy()
6067db96d56Sopenharmony_ci
6077db96d56Sopenharmony_ci    def rmenu_check_copy(self):
6087db96d56Sopenharmony_ci        try:
6097db96d56Sopenharmony_ci            indx = self.text.index('sel.first')
6107db96d56Sopenharmony_ci        except TclError:
6117db96d56Sopenharmony_ci            return 'disabled'
6127db96d56Sopenharmony_ci        else:
6137db96d56Sopenharmony_ci            return 'normal' if indx else 'disabled'
6147db96d56Sopenharmony_ci
6157db96d56Sopenharmony_ci    def rmenu_check_paste(self):
6167db96d56Sopenharmony_ci        try:
6177db96d56Sopenharmony_ci            self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
6187db96d56Sopenharmony_ci        except TclError:
6197db96d56Sopenharmony_ci            return 'disabled'
6207db96d56Sopenharmony_ci        else:
6217db96d56Sopenharmony_ci            return 'normal'
6227db96d56Sopenharmony_ci
6237db96d56Sopenharmony_ci    def about_dialog(self, event=None):
6247db96d56Sopenharmony_ci        "Handle Help 'About IDLE' event."
6257db96d56Sopenharmony_ci        # Synchronize with macosx.overrideRootMenu.about_dialog.
6267db96d56Sopenharmony_ci        help_about.AboutDialog(self.top)
6277db96d56Sopenharmony_ci        return "break"
6287db96d56Sopenharmony_ci
6297db96d56Sopenharmony_ci    def config_dialog(self, event=None):
6307db96d56Sopenharmony_ci        "Handle Options 'Configure IDLE' event."
6317db96d56Sopenharmony_ci        # Synchronize with macosx.overrideRootMenu.config_dialog.
6327db96d56Sopenharmony_ci        configdialog.ConfigDialog(self.top,'Settings')
6337db96d56Sopenharmony_ci        return "break"
6347db96d56Sopenharmony_ci
6357db96d56Sopenharmony_ci    def help_dialog(self, event=None):
6367db96d56Sopenharmony_ci        "Handle Help 'IDLE Help' event."
6377db96d56Sopenharmony_ci        # Synchronize with macosx.overrideRootMenu.help_dialog.
6387db96d56Sopenharmony_ci        if self.root:
6397db96d56Sopenharmony_ci            parent = self.root
6407db96d56Sopenharmony_ci        else:
6417db96d56Sopenharmony_ci            parent = self.top
6427db96d56Sopenharmony_ci        help.show_idlehelp(parent)
6437db96d56Sopenharmony_ci        return "break"
6447db96d56Sopenharmony_ci
6457db96d56Sopenharmony_ci    def python_docs(self, event=None):
6467db96d56Sopenharmony_ci        if sys.platform[:3] == 'win':
6477db96d56Sopenharmony_ci            try:
6487db96d56Sopenharmony_ci                os.startfile(self.help_url)
6497db96d56Sopenharmony_ci            except OSError as why:
6507db96d56Sopenharmony_ci                messagebox.showerror(title='Document Start Failure',
6517db96d56Sopenharmony_ci                    message=str(why), parent=self.text)
6527db96d56Sopenharmony_ci        else:
6537db96d56Sopenharmony_ci            webbrowser.open(self.help_url)
6547db96d56Sopenharmony_ci        return "break"
6557db96d56Sopenharmony_ci
6567db96d56Sopenharmony_ci    def cut(self,event):
6577db96d56Sopenharmony_ci        self.text.event_generate("<<Cut>>")
6587db96d56Sopenharmony_ci        return "break"
6597db96d56Sopenharmony_ci
6607db96d56Sopenharmony_ci    def copy(self,event):
6617db96d56Sopenharmony_ci        if not self.text.tag_ranges("sel"):
6627db96d56Sopenharmony_ci            # There is no selection, so do nothing and maybe interrupt.
6637db96d56Sopenharmony_ci            return None
6647db96d56Sopenharmony_ci        self.text.event_generate("<<Copy>>")
6657db96d56Sopenharmony_ci        return "break"
6667db96d56Sopenharmony_ci
6677db96d56Sopenharmony_ci    def paste(self,event):
6687db96d56Sopenharmony_ci        self.text.event_generate("<<Paste>>")
6697db96d56Sopenharmony_ci        self.text.see("insert")
6707db96d56Sopenharmony_ci        return "break"
6717db96d56Sopenharmony_ci
6727db96d56Sopenharmony_ci    def select_all(self, event=None):
6737db96d56Sopenharmony_ci        self.text.tag_add("sel", "1.0", "end-1c")
6747db96d56Sopenharmony_ci        self.text.mark_set("insert", "1.0")
6757db96d56Sopenharmony_ci        self.text.see("insert")
6767db96d56Sopenharmony_ci        return "break"
6777db96d56Sopenharmony_ci
6787db96d56Sopenharmony_ci    def remove_selection(self, event=None):
6797db96d56Sopenharmony_ci        self.text.tag_remove("sel", "1.0", "end")
6807db96d56Sopenharmony_ci        self.text.see("insert")
6817db96d56Sopenharmony_ci        return "break"
6827db96d56Sopenharmony_ci
6837db96d56Sopenharmony_ci    def move_at_edge_if_selection(self, edge_index):
6847db96d56Sopenharmony_ci        """Cursor move begins at start or end of selection
6857db96d56Sopenharmony_ci
6867db96d56Sopenharmony_ci        When a left/right cursor key is pressed create and return to Tkinter a
6877db96d56Sopenharmony_ci        function which causes a cursor move from the associated edge of the
6887db96d56Sopenharmony_ci        selection.
6897db96d56Sopenharmony_ci
6907db96d56Sopenharmony_ci        """
6917db96d56Sopenharmony_ci        self_text_index = self.text.index
6927db96d56Sopenharmony_ci        self_text_mark_set = self.text.mark_set
6937db96d56Sopenharmony_ci        edges_table = ("sel.first+1c", "sel.last-1c")
6947db96d56Sopenharmony_ci        def move_at_edge(event):
6957db96d56Sopenharmony_ci            if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
6967db96d56Sopenharmony_ci                try:
6977db96d56Sopenharmony_ci                    self_text_index("sel.first")
6987db96d56Sopenharmony_ci                    self_text_mark_set("insert", edges_table[edge_index])
6997db96d56Sopenharmony_ci                except TclError:
7007db96d56Sopenharmony_ci                    pass
7017db96d56Sopenharmony_ci        return move_at_edge
7027db96d56Sopenharmony_ci
7037db96d56Sopenharmony_ci    def del_word_left(self, event):
7047db96d56Sopenharmony_ci        self.text.event_generate('<Meta-Delete>')
7057db96d56Sopenharmony_ci        return "break"
7067db96d56Sopenharmony_ci
7077db96d56Sopenharmony_ci    def del_word_right(self, event):
7087db96d56Sopenharmony_ci        self.text.event_generate('<Meta-d>')
7097db96d56Sopenharmony_ci        return "break"
7107db96d56Sopenharmony_ci
7117db96d56Sopenharmony_ci    def find_event(self, event):
7127db96d56Sopenharmony_ci        search.find(self.text)
7137db96d56Sopenharmony_ci        return "break"
7147db96d56Sopenharmony_ci
7157db96d56Sopenharmony_ci    def find_again_event(self, event):
7167db96d56Sopenharmony_ci        search.find_again(self.text)
7177db96d56Sopenharmony_ci        return "break"
7187db96d56Sopenharmony_ci
7197db96d56Sopenharmony_ci    def find_selection_event(self, event):
7207db96d56Sopenharmony_ci        search.find_selection(self.text)
7217db96d56Sopenharmony_ci        return "break"
7227db96d56Sopenharmony_ci
7237db96d56Sopenharmony_ci    def find_in_files_event(self, event):
7247db96d56Sopenharmony_ci        grep.grep(self.text, self.io, self.flist)
7257db96d56Sopenharmony_ci        return "break"
7267db96d56Sopenharmony_ci
7277db96d56Sopenharmony_ci    def replace_event(self, event):
7287db96d56Sopenharmony_ci        replace.replace(self.text)
7297db96d56Sopenharmony_ci        return "break"
7307db96d56Sopenharmony_ci
7317db96d56Sopenharmony_ci    def goto_line_event(self, event):
7327db96d56Sopenharmony_ci        text = self.text
7337db96d56Sopenharmony_ci        lineno = query.Goto(
7347db96d56Sopenharmony_ci                text, "Go To Line",
7357db96d56Sopenharmony_ci                "Enter a positive integer\n"
7367db96d56Sopenharmony_ci                "('big' = end of file):"
7377db96d56Sopenharmony_ci                ).result
7387db96d56Sopenharmony_ci        if lineno is not None:
7397db96d56Sopenharmony_ci            text.tag_remove("sel", "1.0", "end")
7407db96d56Sopenharmony_ci            text.mark_set("insert", f'{lineno}.0')
7417db96d56Sopenharmony_ci            text.see("insert")
7427db96d56Sopenharmony_ci            self.set_line_and_column()
7437db96d56Sopenharmony_ci        return "break"
7447db96d56Sopenharmony_ci
7457db96d56Sopenharmony_ci    def open_module(self):
7467db96d56Sopenharmony_ci        """Get module name from user and open it.
7477db96d56Sopenharmony_ci
7487db96d56Sopenharmony_ci        Return module path or None for calls by open_module_browser
7497db96d56Sopenharmony_ci        when latter is not invoked in named editor window.
7507db96d56Sopenharmony_ci        """
7517db96d56Sopenharmony_ci        # XXX This, open_module_browser, and open_path_browser
7527db96d56Sopenharmony_ci        # would fit better in iomenu.IOBinding.
7537db96d56Sopenharmony_ci        try:
7547db96d56Sopenharmony_ci            name = self.text.get("sel.first", "sel.last").strip()
7557db96d56Sopenharmony_ci        except TclError:
7567db96d56Sopenharmony_ci            name = ''
7577db96d56Sopenharmony_ci        file_path = query.ModuleName(
7587db96d56Sopenharmony_ci                self.text, "Open Module",
7597db96d56Sopenharmony_ci                "Enter the name of a Python module\n"
7607db96d56Sopenharmony_ci                "to search on sys.path and open:",
7617db96d56Sopenharmony_ci                name).result
7627db96d56Sopenharmony_ci        if file_path is not None:
7637db96d56Sopenharmony_ci            if self.flist:
7647db96d56Sopenharmony_ci                self.flist.open(file_path)
7657db96d56Sopenharmony_ci            else:
7667db96d56Sopenharmony_ci                self.io.loadfile(file_path)
7677db96d56Sopenharmony_ci        return file_path
7687db96d56Sopenharmony_ci
7697db96d56Sopenharmony_ci    def open_module_event(self, event):
7707db96d56Sopenharmony_ci        self.open_module()
7717db96d56Sopenharmony_ci        return "break"
7727db96d56Sopenharmony_ci
7737db96d56Sopenharmony_ci    def open_module_browser(self, event=None):
7747db96d56Sopenharmony_ci        filename = self.io.filename
7757db96d56Sopenharmony_ci        if not (self.__class__.__name__ == 'PyShellEditorWindow'
7767db96d56Sopenharmony_ci                and filename):
7777db96d56Sopenharmony_ci            filename = self.open_module()
7787db96d56Sopenharmony_ci            if filename is None:
7797db96d56Sopenharmony_ci                return "break"
7807db96d56Sopenharmony_ci        from idlelib import browser
7817db96d56Sopenharmony_ci        browser.ModuleBrowser(self.root, filename)
7827db96d56Sopenharmony_ci        return "break"
7837db96d56Sopenharmony_ci
7847db96d56Sopenharmony_ci    def open_path_browser(self, event=None):
7857db96d56Sopenharmony_ci        from idlelib import pathbrowser
7867db96d56Sopenharmony_ci        pathbrowser.PathBrowser(self.root)
7877db96d56Sopenharmony_ci        return "break"
7887db96d56Sopenharmony_ci
7897db96d56Sopenharmony_ci    def open_turtle_demo(self, event = None):
7907db96d56Sopenharmony_ci        import subprocess
7917db96d56Sopenharmony_ci
7927db96d56Sopenharmony_ci        cmd = [sys.executable,
7937db96d56Sopenharmony_ci               '-c',
7947db96d56Sopenharmony_ci               'from turtledemo.__main__ import main; main()']
7957db96d56Sopenharmony_ci        subprocess.Popen(cmd, shell=False)
7967db96d56Sopenharmony_ci        return "break"
7977db96d56Sopenharmony_ci
7987db96d56Sopenharmony_ci    def gotoline(self, lineno):
7997db96d56Sopenharmony_ci        if lineno is not None and lineno > 0:
8007db96d56Sopenharmony_ci            self.text.mark_set("insert", "%d.0" % lineno)
8017db96d56Sopenharmony_ci            self.text.tag_remove("sel", "1.0", "end")
8027db96d56Sopenharmony_ci            self.text.tag_add("sel", "insert", "insert +1l")
8037db96d56Sopenharmony_ci            self.center()
8047db96d56Sopenharmony_ci
8057db96d56Sopenharmony_ci    def ispythonsource(self, filename):
8067db96d56Sopenharmony_ci        if not filename or os.path.isdir(filename):
8077db96d56Sopenharmony_ci            return True
8087db96d56Sopenharmony_ci        base, ext = os.path.splitext(os.path.basename(filename))
8097db96d56Sopenharmony_ci        if os.path.normcase(ext) in py_extensions:
8107db96d56Sopenharmony_ci            return True
8117db96d56Sopenharmony_ci        line = self.text.get('1.0', '1.0 lineend')
8127db96d56Sopenharmony_ci        return line.startswith('#!') and 'python' in line
8137db96d56Sopenharmony_ci
8147db96d56Sopenharmony_ci    def close_hook(self):
8157db96d56Sopenharmony_ci        if self.flist:
8167db96d56Sopenharmony_ci            self.flist.unregister_maybe_terminate(self)
8177db96d56Sopenharmony_ci            self.flist = None
8187db96d56Sopenharmony_ci
8197db96d56Sopenharmony_ci    def set_close_hook(self, close_hook):
8207db96d56Sopenharmony_ci        self.close_hook = close_hook
8217db96d56Sopenharmony_ci
8227db96d56Sopenharmony_ci    def filename_change_hook(self):
8237db96d56Sopenharmony_ci        if self.flist:
8247db96d56Sopenharmony_ci            self.flist.filename_changed_edit(self)
8257db96d56Sopenharmony_ci        self.saved_change_hook()
8267db96d56Sopenharmony_ci        self.top.update_windowlist_registry(self)
8277db96d56Sopenharmony_ci        self.ResetColorizer()
8287db96d56Sopenharmony_ci
8297db96d56Sopenharmony_ci    def _addcolorizer(self):
8307db96d56Sopenharmony_ci        if self.color:
8317db96d56Sopenharmony_ci            return
8327db96d56Sopenharmony_ci        if self.ispythonsource(self.io.filename):
8337db96d56Sopenharmony_ci            self.color = self.ColorDelegator()
8347db96d56Sopenharmony_ci        # can add more colorizers here...
8357db96d56Sopenharmony_ci        if self.color:
8367db96d56Sopenharmony_ci            self.per.insertfilterafter(filter=self.color, after=self.undo)
8377db96d56Sopenharmony_ci
8387db96d56Sopenharmony_ci    def _rmcolorizer(self):
8397db96d56Sopenharmony_ci        if not self.color:
8407db96d56Sopenharmony_ci            return
8417db96d56Sopenharmony_ci        self.color.removecolors()
8427db96d56Sopenharmony_ci        self.per.removefilter(self.color)
8437db96d56Sopenharmony_ci        self.color = None
8447db96d56Sopenharmony_ci
8457db96d56Sopenharmony_ci    def ResetColorizer(self):
8467db96d56Sopenharmony_ci        "Update the color theme"
8477db96d56Sopenharmony_ci        # Called from self.filename_change_hook and from configdialog.py
8487db96d56Sopenharmony_ci        self._rmcolorizer()
8497db96d56Sopenharmony_ci        self._addcolorizer()
8507db96d56Sopenharmony_ci        EditorWindow.color_config(self.text)
8517db96d56Sopenharmony_ci
8527db96d56Sopenharmony_ci        if self.code_context is not None:
8537db96d56Sopenharmony_ci            self.code_context.update_highlight_colors()
8547db96d56Sopenharmony_ci
8557db96d56Sopenharmony_ci        if self.line_numbers is not None:
8567db96d56Sopenharmony_ci            self.line_numbers.update_colors()
8577db96d56Sopenharmony_ci
8587db96d56Sopenharmony_ci    IDENTCHARS = string.ascii_letters + string.digits + "_"
8597db96d56Sopenharmony_ci
8607db96d56Sopenharmony_ci    def colorize_syntax_error(self, text, pos):
8617db96d56Sopenharmony_ci        text.tag_add("ERROR", pos)
8627db96d56Sopenharmony_ci        char = text.get(pos)
8637db96d56Sopenharmony_ci        if char and char in self.IDENTCHARS:
8647db96d56Sopenharmony_ci            text.tag_add("ERROR", pos + " wordstart", pos)
8657db96d56Sopenharmony_ci        if '\n' == text.get(pos):   # error at line end
8667db96d56Sopenharmony_ci            text.mark_set("insert", pos)
8677db96d56Sopenharmony_ci        else:
8687db96d56Sopenharmony_ci            text.mark_set("insert", pos + "+1c")
8697db96d56Sopenharmony_ci        text.see(pos)
8707db96d56Sopenharmony_ci
8717db96d56Sopenharmony_ci    def update_cursor_blink(self):
8727db96d56Sopenharmony_ci        "Update the cursor blink configuration."
8737db96d56Sopenharmony_ci        cursorblink = idleConf.GetOption(
8747db96d56Sopenharmony_ci                'main', 'EditorWindow', 'cursor-blink', type='bool')
8757db96d56Sopenharmony_ci        if not cursorblink:
8767db96d56Sopenharmony_ci            self.text['insertofftime'] = 0
8777db96d56Sopenharmony_ci        else:
8787db96d56Sopenharmony_ci            # Restore the original value
8797db96d56Sopenharmony_ci            self.text['insertofftime'] = idleConf.blink_off_time
8807db96d56Sopenharmony_ci
8817db96d56Sopenharmony_ci    def ResetFont(self):
8827db96d56Sopenharmony_ci        "Update the text widgets' font if it is changed"
8837db96d56Sopenharmony_ci        # Called from configdialog.py
8847db96d56Sopenharmony_ci
8857db96d56Sopenharmony_ci        # Update the code context widget first, since its height affects
8867db96d56Sopenharmony_ci        # the height of the text widget.  This avoids double re-rendering.
8877db96d56Sopenharmony_ci        if self.code_context is not None:
8887db96d56Sopenharmony_ci            self.code_context.update_font()
8897db96d56Sopenharmony_ci        # Next, update the line numbers widget, since its width affects
8907db96d56Sopenharmony_ci        # the width of the text widget.
8917db96d56Sopenharmony_ci        if self.line_numbers is not None:
8927db96d56Sopenharmony_ci            self.line_numbers.update_font()
8937db96d56Sopenharmony_ci        # Finally, update the main text widget.
8947db96d56Sopenharmony_ci        new_font = idleConf.GetFont(self.root, 'main', 'EditorWindow')
8957db96d56Sopenharmony_ci        self.text['font'] = new_font
8967db96d56Sopenharmony_ci        self.set_width()
8977db96d56Sopenharmony_ci
8987db96d56Sopenharmony_ci    def RemoveKeybindings(self):
8997db96d56Sopenharmony_ci        """Remove the virtual, configurable keybindings.
9007db96d56Sopenharmony_ci
9017db96d56Sopenharmony_ci        Leaves the default Tk Text keybindings.
9027db96d56Sopenharmony_ci        """
9037db96d56Sopenharmony_ci        # Called from configdialog.deactivate_current_config.
9047db96d56Sopenharmony_ci        self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
9057db96d56Sopenharmony_ci        for event, keylist in keydefs.items():
9067db96d56Sopenharmony_ci            self.text.event_delete(event, *keylist)
9077db96d56Sopenharmony_ci        for extensionName in self.get_standard_extension_names():
9087db96d56Sopenharmony_ci            xkeydefs = idleConf.GetExtensionBindings(extensionName)
9097db96d56Sopenharmony_ci            if xkeydefs:
9107db96d56Sopenharmony_ci                for event, keylist in xkeydefs.items():
9117db96d56Sopenharmony_ci                    self.text.event_delete(event, *keylist)
9127db96d56Sopenharmony_ci
9137db96d56Sopenharmony_ci    def ApplyKeybindings(self):
9147db96d56Sopenharmony_ci        """Apply the virtual, configurable keybindings.
9157db96d56Sopenharmony_ci
9167db96d56Sopenharmony_ci        Alse update hotkeys to current keyset.
9177db96d56Sopenharmony_ci        """
9187db96d56Sopenharmony_ci        # Called from configdialog.activate_config_changes.
9197db96d56Sopenharmony_ci        self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
9207db96d56Sopenharmony_ci        self.apply_bindings()
9217db96d56Sopenharmony_ci        for extensionName in self.get_standard_extension_names():
9227db96d56Sopenharmony_ci            xkeydefs = idleConf.GetExtensionBindings(extensionName)
9237db96d56Sopenharmony_ci            if xkeydefs:
9247db96d56Sopenharmony_ci                self.apply_bindings(xkeydefs)
9257db96d56Sopenharmony_ci
9267db96d56Sopenharmony_ci        # Update menu accelerators.
9277db96d56Sopenharmony_ci        menuEventDict = {}
9287db96d56Sopenharmony_ci        for menu in self.mainmenu.menudefs:
9297db96d56Sopenharmony_ci            menuEventDict[menu[0]] = {}
9307db96d56Sopenharmony_ci            for item in menu[1]:
9317db96d56Sopenharmony_ci                if item:
9327db96d56Sopenharmony_ci                    menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
9337db96d56Sopenharmony_ci        for menubarItem in self.menudict:
9347db96d56Sopenharmony_ci            menu = self.menudict[menubarItem]
9357db96d56Sopenharmony_ci            end = menu.index(END)
9367db96d56Sopenharmony_ci            if end is None:
9377db96d56Sopenharmony_ci                # Skip empty menus
9387db96d56Sopenharmony_ci                continue
9397db96d56Sopenharmony_ci            end += 1
9407db96d56Sopenharmony_ci            for index in range(0, end):
9417db96d56Sopenharmony_ci                if menu.type(index) == 'command':
9427db96d56Sopenharmony_ci                    accel = menu.entrycget(index, 'accelerator')
9437db96d56Sopenharmony_ci                    if accel:
9447db96d56Sopenharmony_ci                        itemName = menu.entrycget(index, 'label')
9457db96d56Sopenharmony_ci                        event = ''
9467db96d56Sopenharmony_ci                        if menubarItem in menuEventDict:
9477db96d56Sopenharmony_ci                            if itemName in menuEventDict[menubarItem]:
9487db96d56Sopenharmony_ci                                event = menuEventDict[menubarItem][itemName]
9497db96d56Sopenharmony_ci                        if event:
9507db96d56Sopenharmony_ci                            accel = get_accelerator(keydefs, event)
9517db96d56Sopenharmony_ci                            menu.entryconfig(index, accelerator=accel)
9527db96d56Sopenharmony_ci
9537db96d56Sopenharmony_ci    def set_notabs_indentwidth(self):
9547db96d56Sopenharmony_ci        "Update the indentwidth if changed and not using tabs in this window"
9557db96d56Sopenharmony_ci        # Called from configdialog.py
9567db96d56Sopenharmony_ci        if not self.usetabs:
9577db96d56Sopenharmony_ci            self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
9587db96d56Sopenharmony_ci                                                  type='int')
9597db96d56Sopenharmony_ci
9607db96d56Sopenharmony_ci    def reset_help_menu_entries(self):
9617db96d56Sopenharmony_ci        """Update the additional help entries on the Help menu."""
9627db96d56Sopenharmony_ci        help_list = idleConf.GetAllExtraHelpSourcesList()
9637db96d56Sopenharmony_ci        helpmenu = self.menudict['help']
9647db96d56Sopenharmony_ci        # First delete the extra help entries, if any.
9657db96d56Sopenharmony_ci        helpmenu_length = helpmenu.index(END)
9667db96d56Sopenharmony_ci        if helpmenu_length > self.base_helpmenu_length:
9677db96d56Sopenharmony_ci            helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
9687db96d56Sopenharmony_ci        # Then rebuild them.
9697db96d56Sopenharmony_ci        if help_list:
9707db96d56Sopenharmony_ci            helpmenu.add_separator()
9717db96d56Sopenharmony_ci            for entry in help_list:
9727db96d56Sopenharmony_ci                cmd = self._extra_help_callback(entry[1])
9737db96d56Sopenharmony_ci                helpmenu.add_command(label=entry[0], command=cmd)
9747db96d56Sopenharmony_ci        # And update the menu dictionary.
9757db96d56Sopenharmony_ci        self.menudict['help'] = helpmenu
9767db96d56Sopenharmony_ci
9777db96d56Sopenharmony_ci    def _extra_help_callback(self, resource):
9787db96d56Sopenharmony_ci        """Return a callback that loads resource (file or web page)."""
9797db96d56Sopenharmony_ci        def display_extra_help(helpfile=resource):
9807db96d56Sopenharmony_ci            if not helpfile.startswith(('www', 'http')):
9817db96d56Sopenharmony_ci                helpfile = os.path.normpath(helpfile)
9827db96d56Sopenharmony_ci            if sys.platform[:3] == 'win':
9837db96d56Sopenharmony_ci                try:
9847db96d56Sopenharmony_ci                    os.startfile(helpfile)
9857db96d56Sopenharmony_ci                except OSError as why:
9867db96d56Sopenharmony_ci                    messagebox.showerror(title='Document Start Failure',
9877db96d56Sopenharmony_ci                        message=str(why), parent=self.text)
9887db96d56Sopenharmony_ci            else:
9897db96d56Sopenharmony_ci                webbrowser.open(helpfile)
9907db96d56Sopenharmony_ci        return display_extra_help
9917db96d56Sopenharmony_ci
9927db96d56Sopenharmony_ci    def update_recent_files_list(self, new_file=None):
9937db96d56Sopenharmony_ci        "Load and update the recent files list and menus"
9947db96d56Sopenharmony_ci        # TODO: move to iomenu.
9957db96d56Sopenharmony_ci        rf_list = []
9967db96d56Sopenharmony_ci        file_path = self.recent_files_path
9977db96d56Sopenharmony_ci        if file_path and os.path.exists(file_path):
9987db96d56Sopenharmony_ci            with open(file_path,
9997db96d56Sopenharmony_ci                      encoding='utf_8', errors='replace') as rf_list_file:
10007db96d56Sopenharmony_ci                rf_list = rf_list_file.readlines()
10017db96d56Sopenharmony_ci        if new_file:
10027db96d56Sopenharmony_ci            new_file = os.path.abspath(new_file) + '\n'
10037db96d56Sopenharmony_ci            if new_file in rf_list:
10047db96d56Sopenharmony_ci                rf_list.remove(new_file)  # move to top
10057db96d56Sopenharmony_ci            rf_list.insert(0, new_file)
10067db96d56Sopenharmony_ci        # clean and save the recent files list
10077db96d56Sopenharmony_ci        bad_paths = []
10087db96d56Sopenharmony_ci        for path in rf_list:
10097db96d56Sopenharmony_ci            if '\0' in path or not os.path.exists(path[0:-1]):
10107db96d56Sopenharmony_ci                bad_paths.append(path)
10117db96d56Sopenharmony_ci        rf_list = [path for path in rf_list if path not in bad_paths]
10127db96d56Sopenharmony_ci        ulchars = "1234567890ABCDEFGHIJK"
10137db96d56Sopenharmony_ci        rf_list = rf_list[0:len(ulchars)]
10147db96d56Sopenharmony_ci        if file_path:
10157db96d56Sopenharmony_ci            try:
10167db96d56Sopenharmony_ci                with open(file_path, 'w',
10177db96d56Sopenharmony_ci                          encoding='utf_8', errors='replace') as rf_file:
10187db96d56Sopenharmony_ci                    rf_file.writelines(rf_list)
10197db96d56Sopenharmony_ci            except OSError as err:
10207db96d56Sopenharmony_ci                if not getattr(self.root, "recentfiles_message", False):
10217db96d56Sopenharmony_ci                    self.root.recentfiles_message = True
10227db96d56Sopenharmony_ci                    messagebox.showwarning(title='IDLE Warning',
10237db96d56Sopenharmony_ci                        message="Cannot save Recent Files list to disk.\n"
10247db96d56Sopenharmony_ci                                f"  {err}\n"
10257db96d56Sopenharmony_ci                                "Select OK to continue.",
10267db96d56Sopenharmony_ci                        parent=self.text)
10277db96d56Sopenharmony_ci        # for each edit window instance, construct the recent files menu
10287db96d56Sopenharmony_ci        for instance in self.top.instance_dict:
10297db96d56Sopenharmony_ci            menu = instance.recent_files_menu
10307db96d56Sopenharmony_ci            menu.delete(0, END)  # clear, and rebuild:
10317db96d56Sopenharmony_ci            for i, file_name in enumerate(rf_list):
10327db96d56Sopenharmony_ci                file_name = file_name.rstrip()  # zap \n
10337db96d56Sopenharmony_ci                callback = instance.__recent_file_callback(file_name)
10347db96d56Sopenharmony_ci                menu.add_command(label=ulchars[i] + " " + file_name,
10357db96d56Sopenharmony_ci                                 command=callback,
10367db96d56Sopenharmony_ci                                 underline=0)
10377db96d56Sopenharmony_ci
10387db96d56Sopenharmony_ci    def __recent_file_callback(self, file_name):
10397db96d56Sopenharmony_ci        def open_recent_file(fn_closure=file_name):
10407db96d56Sopenharmony_ci            self.io.open(editFile=fn_closure)
10417db96d56Sopenharmony_ci        return open_recent_file
10427db96d56Sopenharmony_ci
10437db96d56Sopenharmony_ci    def saved_change_hook(self):
10447db96d56Sopenharmony_ci        short = self.short_title()
10457db96d56Sopenharmony_ci        long = self.long_title()
10467db96d56Sopenharmony_ci        if short and long:
10477db96d56Sopenharmony_ci            title = short + " - " + long + _py_version
10487db96d56Sopenharmony_ci        elif short:
10497db96d56Sopenharmony_ci            title = short
10507db96d56Sopenharmony_ci        elif long:
10517db96d56Sopenharmony_ci            title = long
10527db96d56Sopenharmony_ci        else:
10537db96d56Sopenharmony_ci            title = "untitled"
10547db96d56Sopenharmony_ci        icon = short or long or title
10557db96d56Sopenharmony_ci        if not self.get_saved():
10567db96d56Sopenharmony_ci            title = "*%s*" % title
10577db96d56Sopenharmony_ci            icon = "*%s" % icon
10587db96d56Sopenharmony_ci        self.top.wm_title(title)
10597db96d56Sopenharmony_ci        self.top.wm_iconname(icon)
10607db96d56Sopenharmony_ci
10617db96d56Sopenharmony_ci    def get_saved(self):
10627db96d56Sopenharmony_ci        return self.undo.get_saved()
10637db96d56Sopenharmony_ci
10647db96d56Sopenharmony_ci    def set_saved(self, flag):
10657db96d56Sopenharmony_ci        self.undo.set_saved(flag)
10667db96d56Sopenharmony_ci
10677db96d56Sopenharmony_ci    def reset_undo(self):
10687db96d56Sopenharmony_ci        self.undo.reset_undo()
10697db96d56Sopenharmony_ci
10707db96d56Sopenharmony_ci    def short_title(self):
10717db96d56Sopenharmony_ci        filename = self.io.filename
10727db96d56Sopenharmony_ci        return os.path.basename(filename) if filename else "untitled"
10737db96d56Sopenharmony_ci
10747db96d56Sopenharmony_ci    def long_title(self):
10757db96d56Sopenharmony_ci        return self.io.filename or ""
10767db96d56Sopenharmony_ci
10777db96d56Sopenharmony_ci    def center_insert_event(self, event):
10787db96d56Sopenharmony_ci        self.center()
10797db96d56Sopenharmony_ci        return "break"
10807db96d56Sopenharmony_ci
10817db96d56Sopenharmony_ci    def center(self, mark="insert"):
10827db96d56Sopenharmony_ci        text = self.text
10837db96d56Sopenharmony_ci        top, bot = self.getwindowlines()
10847db96d56Sopenharmony_ci        lineno = self.getlineno(mark)
10857db96d56Sopenharmony_ci        height = bot - top
10867db96d56Sopenharmony_ci        newtop = max(1, lineno - height//2)
10877db96d56Sopenharmony_ci        text.yview(float(newtop))
10887db96d56Sopenharmony_ci
10897db96d56Sopenharmony_ci    def getwindowlines(self):
10907db96d56Sopenharmony_ci        text = self.text
10917db96d56Sopenharmony_ci        top = self.getlineno("@0,0")
10927db96d56Sopenharmony_ci        bot = self.getlineno("@0,65535")
10937db96d56Sopenharmony_ci        if top == bot and text.winfo_height() == 1:
10947db96d56Sopenharmony_ci            # Geometry manager hasn't run yet
10957db96d56Sopenharmony_ci            height = int(text['height'])
10967db96d56Sopenharmony_ci            bot = top + height - 1
10977db96d56Sopenharmony_ci        return top, bot
10987db96d56Sopenharmony_ci
10997db96d56Sopenharmony_ci    def getlineno(self, mark="insert"):
11007db96d56Sopenharmony_ci        text = self.text
11017db96d56Sopenharmony_ci        return int(float(text.index(mark)))
11027db96d56Sopenharmony_ci
11037db96d56Sopenharmony_ci    def get_geometry(self):
11047db96d56Sopenharmony_ci        "Return (width, height, x, y)"
11057db96d56Sopenharmony_ci        geom = self.top.wm_geometry()
11067db96d56Sopenharmony_ci        m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
11077db96d56Sopenharmony_ci        return list(map(int, m.groups()))
11087db96d56Sopenharmony_ci
11097db96d56Sopenharmony_ci    def close_event(self, event):
11107db96d56Sopenharmony_ci        self.close()
11117db96d56Sopenharmony_ci        return "break"
11127db96d56Sopenharmony_ci
11137db96d56Sopenharmony_ci    def maybesave(self):
11147db96d56Sopenharmony_ci        if self.io:
11157db96d56Sopenharmony_ci            if not self.get_saved():
11167db96d56Sopenharmony_ci                if self.top.state()!='normal':
11177db96d56Sopenharmony_ci                    self.top.deiconify()
11187db96d56Sopenharmony_ci                self.top.lower()
11197db96d56Sopenharmony_ci                self.top.lift()
11207db96d56Sopenharmony_ci            return self.io.maybesave()
11217db96d56Sopenharmony_ci
11227db96d56Sopenharmony_ci    def close(self):
11237db96d56Sopenharmony_ci        try:
11247db96d56Sopenharmony_ci            reply = self.maybesave()
11257db96d56Sopenharmony_ci            if str(reply) != "cancel":
11267db96d56Sopenharmony_ci                self._close()
11277db96d56Sopenharmony_ci            return reply
11287db96d56Sopenharmony_ci        except AttributeError:  # bpo-35379: close called twice
11297db96d56Sopenharmony_ci            pass
11307db96d56Sopenharmony_ci
11317db96d56Sopenharmony_ci    def _close(self):
11327db96d56Sopenharmony_ci        if self.io.filename:
11337db96d56Sopenharmony_ci            self.update_recent_files_list(new_file=self.io.filename)
11347db96d56Sopenharmony_ci        window.unregister_callback(self.postwindowsmenu)
11357db96d56Sopenharmony_ci        self.unload_extensions()
11367db96d56Sopenharmony_ci        self.io.close()
11377db96d56Sopenharmony_ci        self.io = None
11387db96d56Sopenharmony_ci        self.undo = None
11397db96d56Sopenharmony_ci        if self.color:
11407db96d56Sopenharmony_ci            self.color.close()
11417db96d56Sopenharmony_ci            self.color = None
11427db96d56Sopenharmony_ci        self.text = None
11437db96d56Sopenharmony_ci        self.tkinter_vars = None
11447db96d56Sopenharmony_ci        self.per.close()
11457db96d56Sopenharmony_ci        self.per = None
11467db96d56Sopenharmony_ci        self.top.destroy()
11477db96d56Sopenharmony_ci        if self.close_hook:
11487db96d56Sopenharmony_ci            # unless override: unregister from flist, terminate if last window
11497db96d56Sopenharmony_ci            self.close_hook()
11507db96d56Sopenharmony_ci
11517db96d56Sopenharmony_ci    def load_extensions(self):
11527db96d56Sopenharmony_ci        self.extensions = {}
11537db96d56Sopenharmony_ci        self.load_standard_extensions()
11547db96d56Sopenharmony_ci
11557db96d56Sopenharmony_ci    def unload_extensions(self):
11567db96d56Sopenharmony_ci        for ins in list(self.extensions.values()):
11577db96d56Sopenharmony_ci            if hasattr(ins, "close"):
11587db96d56Sopenharmony_ci                ins.close()
11597db96d56Sopenharmony_ci        self.extensions = {}
11607db96d56Sopenharmony_ci
11617db96d56Sopenharmony_ci    def load_standard_extensions(self):
11627db96d56Sopenharmony_ci        for name in self.get_standard_extension_names():
11637db96d56Sopenharmony_ci            try:
11647db96d56Sopenharmony_ci                self.load_extension(name)
11657db96d56Sopenharmony_ci            except:
11667db96d56Sopenharmony_ci                print("Failed to load extension", repr(name))
11677db96d56Sopenharmony_ci                traceback.print_exc()
11687db96d56Sopenharmony_ci
11697db96d56Sopenharmony_ci    def get_standard_extension_names(self):
11707db96d56Sopenharmony_ci        return idleConf.GetExtensions(editor_only=True)
11717db96d56Sopenharmony_ci
11727db96d56Sopenharmony_ci    extfiles = {  # Map built-in config-extension section names to file names.
11737db96d56Sopenharmony_ci        'ZzDummy': 'zzdummy',
11747db96d56Sopenharmony_ci        }
11757db96d56Sopenharmony_ci
11767db96d56Sopenharmony_ci    def load_extension(self, name):
11777db96d56Sopenharmony_ci        fname = self.extfiles.get(name, name)
11787db96d56Sopenharmony_ci        try:
11797db96d56Sopenharmony_ci            try:
11807db96d56Sopenharmony_ci                mod = importlib.import_module('.' + fname, package=__package__)
11817db96d56Sopenharmony_ci            except (ImportError, TypeError):
11827db96d56Sopenharmony_ci                mod = importlib.import_module(fname)
11837db96d56Sopenharmony_ci        except ImportError:
11847db96d56Sopenharmony_ci            print("\nFailed to import extension: ", name)
11857db96d56Sopenharmony_ci            raise
11867db96d56Sopenharmony_ci        cls = getattr(mod, name)
11877db96d56Sopenharmony_ci        keydefs = idleConf.GetExtensionBindings(name)
11887db96d56Sopenharmony_ci        if hasattr(cls, "menudefs"):
11897db96d56Sopenharmony_ci            self.fill_menus(cls.menudefs, keydefs)
11907db96d56Sopenharmony_ci        ins = cls(self)
11917db96d56Sopenharmony_ci        self.extensions[name] = ins
11927db96d56Sopenharmony_ci        if keydefs:
11937db96d56Sopenharmony_ci            self.apply_bindings(keydefs)
11947db96d56Sopenharmony_ci            for vevent in keydefs:
11957db96d56Sopenharmony_ci                methodname = vevent.replace("-", "_")
11967db96d56Sopenharmony_ci                while methodname[:1] == '<':
11977db96d56Sopenharmony_ci                    methodname = methodname[1:]
11987db96d56Sopenharmony_ci                while methodname[-1:] == '>':
11997db96d56Sopenharmony_ci                    methodname = methodname[:-1]
12007db96d56Sopenharmony_ci                methodname = methodname + "_event"
12017db96d56Sopenharmony_ci                if hasattr(ins, methodname):
12027db96d56Sopenharmony_ci                    self.text.bind(vevent, getattr(ins, methodname))
12037db96d56Sopenharmony_ci
12047db96d56Sopenharmony_ci    def apply_bindings(self, keydefs=None):
12057db96d56Sopenharmony_ci        """Add events with keys to self.text."""
12067db96d56Sopenharmony_ci        if keydefs is None:
12077db96d56Sopenharmony_ci            keydefs = self.mainmenu.default_keydefs
12087db96d56Sopenharmony_ci        text = self.text
12097db96d56Sopenharmony_ci        text.keydefs = keydefs
12107db96d56Sopenharmony_ci        for event, keylist in keydefs.items():
12117db96d56Sopenharmony_ci            if keylist:
12127db96d56Sopenharmony_ci                text.event_add(event, *keylist)
12137db96d56Sopenharmony_ci
12147db96d56Sopenharmony_ci    def fill_menus(self, menudefs=None, keydefs=None):
12157db96d56Sopenharmony_ci        """Fill in dropdown menus used by this window.
12167db96d56Sopenharmony_ci
12177db96d56Sopenharmony_ci        Items whose name begins with '!' become checkbuttons.
12187db96d56Sopenharmony_ci        Other names indicate commands.  None becomes a separator.
12197db96d56Sopenharmony_ci        """
12207db96d56Sopenharmony_ci        if menudefs is None:
12217db96d56Sopenharmony_ci            menudefs = self.mainmenu.menudefs
12227db96d56Sopenharmony_ci        if keydefs is None:
12237db96d56Sopenharmony_ci            keydefs = self.mainmenu.default_keydefs
12247db96d56Sopenharmony_ci        menudict = self.menudict
12257db96d56Sopenharmony_ci        text = self.text
12267db96d56Sopenharmony_ci        for mname, entrylist in menudefs:
12277db96d56Sopenharmony_ci            menu = menudict.get(mname)
12287db96d56Sopenharmony_ci            if not menu:
12297db96d56Sopenharmony_ci                continue
12307db96d56Sopenharmony_ci            for entry in entrylist:
12317db96d56Sopenharmony_ci                if entry is None:
12327db96d56Sopenharmony_ci                    menu.add_separator()
12337db96d56Sopenharmony_ci                else:
12347db96d56Sopenharmony_ci                    label, eventname = entry
12357db96d56Sopenharmony_ci                    checkbutton = (label[:1] == '!')
12367db96d56Sopenharmony_ci                    if checkbutton:
12377db96d56Sopenharmony_ci                        label = label[1:]
12387db96d56Sopenharmony_ci                    underline, label = prepstr(label)
12397db96d56Sopenharmony_ci                    accelerator = get_accelerator(keydefs, eventname)
12407db96d56Sopenharmony_ci                    def command(text=text, eventname=eventname):
12417db96d56Sopenharmony_ci                        text.event_generate(eventname)
12427db96d56Sopenharmony_ci                    if checkbutton:
12437db96d56Sopenharmony_ci                        var = self.get_var_obj(eventname, BooleanVar)
12447db96d56Sopenharmony_ci                        menu.add_checkbutton(label=label, underline=underline,
12457db96d56Sopenharmony_ci                            command=command, accelerator=accelerator,
12467db96d56Sopenharmony_ci                            variable=var)
12477db96d56Sopenharmony_ci                    else:
12487db96d56Sopenharmony_ci                        menu.add_command(label=label, underline=underline,
12497db96d56Sopenharmony_ci                                         command=command,
12507db96d56Sopenharmony_ci                                         accelerator=accelerator)
12517db96d56Sopenharmony_ci
12527db96d56Sopenharmony_ci    def getvar(self, name):
12537db96d56Sopenharmony_ci        var = self.get_var_obj(name)
12547db96d56Sopenharmony_ci        if var:
12557db96d56Sopenharmony_ci            value = var.get()
12567db96d56Sopenharmony_ci            return value
12577db96d56Sopenharmony_ci        else:
12587db96d56Sopenharmony_ci            raise NameError(name)
12597db96d56Sopenharmony_ci
12607db96d56Sopenharmony_ci    def setvar(self, name, value, vartype=None):
12617db96d56Sopenharmony_ci        var = self.get_var_obj(name, vartype)
12627db96d56Sopenharmony_ci        if var:
12637db96d56Sopenharmony_ci            var.set(value)
12647db96d56Sopenharmony_ci        else:
12657db96d56Sopenharmony_ci            raise NameError(name)
12667db96d56Sopenharmony_ci
12677db96d56Sopenharmony_ci    def get_var_obj(self, eventname, vartype=None):
12687db96d56Sopenharmony_ci        """Return a tkinter variable instance for the event.
12697db96d56Sopenharmony_ci        """
12707db96d56Sopenharmony_ci        var = self.tkinter_vars.get(eventname)
12717db96d56Sopenharmony_ci        if not var and vartype:
12727db96d56Sopenharmony_ci            # Create a Tkinter variable object.
12737db96d56Sopenharmony_ci            self.tkinter_vars[eventname] = var = vartype(self.text)
12747db96d56Sopenharmony_ci        return var
12757db96d56Sopenharmony_ci
12767db96d56Sopenharmony_ci    # Tk implementations of "virtual text methods" -- each platform
12777db96d56Sopenharmony_ci    # reusing IDLE's support code needs to define these for its GUI's
12787db96d56Sopenharmony_ci    # flavor of widget.
12797db96d56Sopenharmony_ci
12807db96d56Sopenharmony_ci    # Is character at text_index in a Python string?  Return 0 for
12817db96d56Sopenharmony_ci    # "guaranteed no", true for anything else.  This info is expensive
12827db96d56Sopenharmony_ci    # to compute ab initio, but is probably already known by the
12837db96d56Sopenharmony_ci    # platform's colorizer.
12847db96d56Sopenharmony_ci
12857db96d56Sopenharmony_ci    def is_char_in_string(self, text_index):
12867db96d56Sopenharmony_ci        if self.color:
12877db96d56Sopenharmony_ci            # Return true iff colorizer hasn't (re)gotten this far
12887db96d56Sopenharmony_ci            # yet, or the character is tagged as being in a string
12897db96d56Sopenharmony_ci            return self.text.tag_prevrange("TODO", text_index) or \
12907db96d56Sopenharmony_ci                   "STRING" in self.text.tag_names(text_index)
12917db96d56Sopenharmony_ci        else:
12927db96d56Sopenharmony_ci            # The colorizer is missing: assume the worst
12937db96d56Sopenharmony_ci            return 1
12947db96d56Sopenharmony_ci
12957db96d56Sopenharmony_ci    # If a selection is defined in the text widget, return (start,
12967db96d56Sopenharmony_ci    # end) as Tkinter text indices, otherwise return (None, None)
12977db96d56Sopenharmony_ci    def get_selection_indices(self):
12987db96d56Sopenharmony_ci        try:
12997db96d56Sopenharmony_ci            first = self.text.index("sel.first")
13007db96d56Sopenharmony_ci            last = self.text.index("sel.last")
13017db96d56Sopenharmony_ci            return first, last
13027db96d56Sopenharmony_ci        except TclError:
13037db96d56Sopenharmony_ci            return None, None
13047db96d56Sopenharmony_ci
13057db96d56Sopenharmony_ci    # Return the text widget's current view of what a tab stop means
13067db96d56Sopenharmony_ci    # (equivalent width in spaces).
13077db96d56Sopenharmony_ci
13087db96d56Sopenharmony_ci    def get_tk_tabwidth(self):
13097db96d56Sopenharmony_ci        current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
13107db96d56Sopenharmony_ci        return int(current)
13117db96d56Sopenharmony_ci
13127db96d56Sopenharmony_ci    # Set the text widget's current view of what a tab stop means.
13137db96d56Sopenharmony_ci
13147db96d56Sopenharmony_ci    def set_tk_tabwidth(self, newtabwidth):
13157db96d56Sopenharmony_ci        text = self.text
13167db96d56Sopenharmony_ci        if self.get_tk_tabwidth() != newtabwidth:
13177db96d56Sopenharmony_ci            # Set text widget tab width
13187db96d56Sopenharmony_ci            pixels = text.tk.call("font", "measure", text["font"],
13197db96d56Sopenharmony_ci                                  "-displayof", text.master,
13207db96d56Sopenharmony_ci                                  "n" * newtabwidth)
13217db96d56Sopenharmony_ci            text.configure(tabs=pixels)
13227db96d56Sopenharmony_ci
13237db96d56Sopenharmony_ci### begin autoindent code ###  (configuration was moved to beginning of class)
13247db96d56Sopenharmony_ci
13257db96d56Sopenharmony_ci    def set_indentation_params(self, is_py_src, guess=True):
13267db96d56Sopenharmony_ci        if is_py_src and guess:
13277db96d56Sopenharmony_ci            i = self.guess_indent()
13287db96d56Sopenharmony_ci            if 2 <= i <= 8:
13297db96d56Sopenharmony_ci                self.indentwidth = i
13307db96d56Sopenharmony_ci            if self.indentwidth != self.tabwidth:
13317db96d56Sopenharmony_ci                self.usetabs = False
13327db96d56Sopenharmony_ci        self.set_tk_tabwidth(self.tabwidth)
13337db96d56Sopenharmony_ci
13347db96d56Sopenharmony_ci    def smart_backspace_event(self, event):
13357db96d56Sopenharmony_ci        text = self.text
13367db96d56Sopenharmony_ci        first, last = self.get_selection_indices()
13377db96d56Sopenharmony_ci        if first and last:
13387db96d56Sopenharmony_ci            text.delete(first, last)
13397db96d56Sopenharmony_ci            text.mark_set("insert", first)
13407db96d56Sopenharmony_ci            return "break"
13417db96d56Sopenharmony_ci        # Delete whitespace left, until hitting a real char or closest
13427db96d56Sopenharmony_ci        # preceding virtual tab stop.
13437db96d56Sopenharmony_ci        chars = text.get("insert linestart", "insert")
13447db96d56Sopenharmony_ci        if chars == '':
13457db96d56Sopenharmony_ci            if text.compare("insert", ">", "1.0"):
13467db96d56Sopenharmony_ci                # easy: delete preceding newline
13477db96d56Sopenharmony_ci                text.delete("insert-1c")
13487db96d56Sopenharmony_ci            else:
13497db96d56Sopenharmony_ci                text.bell()     # at start of buffer
13507db96d56Sopenharmony_ci            return "break"
13517db96d56Sopenharmony_ci        if  chars[-1] not in " \t":
13527db96d56Sopenharmony_ci            # easy: delete preceding real char
13537db96d56Sopenharmony_ci            text.delete("insert-1c")
13547db96d56Sopenharmony_ci            return "break"
13557db96d56Sopenharmony_ci        # Ick.  It may require *inserting* spaces if we back up over a
13567db96d56Sopenharmony_ci        # tab character!  This is written to be clear, not fast.
13577db96d56Sopenharmony_ci        tabwidth = self.tabwidth
13587db96d56Sopenharmony_ci        have = len(chars.expandtabs(tabwidth))
13597db96d56Sopenharmony_ci        assert have > 0
13607db96d56Sopenharmony_ci        want = ((have - 1) // self.indentwidth) * self.indentwidth
13617db96d56Sopenharmony_ci        # Debug prompt is multilined....
13627db96d56Sopenharmony_ci        ncharsdeleted = 0
13637db96d56Sopenharmony_ci        while True:
13647db96d56Sopenharmony_ci            chars = chars[:-1]
13657db96d56Sopenharmony_ci            ncharsdeleted = ncharsdeleted + 1
13667db96d56Sopenharmony_ci            have = len(chars.expandtabs(tabwidth))
13677db96d56Sopenharmony_ci            if have <= want or chars[-1] not in " \t":
13687db96d56Sopenharmony_ci                break
13697db96d56Sopenharmony_ci        text.undo_block_start()
13707db96d56Sopenharmony_ci        text.delete("insert-%dc" % ncharsdeleted, "insert")
13717db96d56Sopenharmony_ci        if have < want:
13727db96d56Sopenharmony_ci            text.insert("insert", ' ' * (want - have),
13737db96d56Sopenharmony_ci                        self.user_input_insert_tags)
13747db96d56Sopenharmony_ci        text.undo_block_stop()
13757db96d56Sopenharmony_ci        return "break"
13767db96d56Sopenharmony_ci
13777db96d56Sopenharmony_ci    def smart_indent_event(self, event):
13787db96d56Sopenharmony_ci        # if intraline selection:
13797db96d56Sopenharmony_ci        #     delete it
13807db96d56Sopenharmony_ci        # elif multiline selection:
13817db96d56Sopenharmony_ci        #     do indent-region
13827db96d56Sopenharmony_ci        # else:
13837db96d56Sopenharmony_ci        #     indent one level
13847db96d56Sopenharmony_ci        text = self.text
13857db96d56Sopenharmony_ci        first, last = self.get_selection_indices()
13867db96d56Sopenharmony_ci        text.undo_block_start()
13877db96d56Sopenharmony_ci        try:
13887db96d56Sopenharmony_ci            if first and last:
13897db96d56Sopenharmony_ci                if index2line(first) != index2line(last):
13907db96d56Sopenharmony_ci                    return self.fregion.indent_region_event(event)
13917db96d56Sopenharmony_ci                text.delete(first, last)
13927db96d56Sopenharmony_ci                text.mark_set("insert", first)
13937db96d56Sopenharmony_ci            prefix = text.get("insert linestart", "insert")
13947db96d56Sopenharmony_ci            raw, effective = get_line_indent(prefix, self.tabwidth)
13957db96d56Sopenharmony_ci            if raw == len(prefix):
13967db96d56Sopenharmony_ci                # only whitespace to the left
13977db96d56Sopenharmony_ci                self.reindent_to(effective + self.indentwidth)
13987db96d56Sopenharmony_ci            else:
13997db96d56Sopenharmony_ci                # tab to the next 'stop' within or to right of line's text:
14007db96d56Sopenharmony_ci                if self.usetabs:
14017db96d56Sopenharmony_ci                    pad = '\t'
14027db96d56Sopenharmony_ci                else:
14037db96d56Sopenharmony_ci                    effective = len(prefix.expandtabs(self.tabwidth))
14047db96d56Sopenharmony_ci                    n = self.indentwidth
14057db96d56Sopenharmony_ci                    pad = ' ' * (n - effective % n)
14067db96d56Sopenharmony_ci                text.insert("insert", pad, self.user_input_insert_tags)
14077db96d56Sopenharmony_ci            text.see("insert")
14087db96d56Sopenharmony_ci            return "break"
14097db96d56Sopenharmony_ci        finally:
14107db96d56Sopenharmony_ci            text.undo_block_stop()
14117db96d56Sopenharmony_ci
14127db96d56Sopenharmony_ci    def newline_and_indent_event(self, event):
14137db96d56Sopenharmony_ci        """Insert a newline and indentation after Enter keypress event.
14147db96d56Sopenharmony_ci
14157db96d56Sopenharmony_ci        Properly position the cursor on the new line based on information
14167db96d56Sopenharmony_ci        from the current line.  This takes into account if the current line
14177db96d56Sopenharmony_ci        is a shell prompt, is empty, has selected text, contains a block
14187db96d56Sopenharmony_ci        opener, contains a block closer, is a continuation line, or
14197db96d56Sopenharmony_ci        is inside a string.
14207db96d56Sopenharmony_ci        """
14217db96d56Sopenharmony_ci        text = self.text
14227db96d56Sopenharmony_ci        first, last = self.get_selection_indices()
14237db96d56Sopenharmony_ci        text.undo_block_start()
14247db96d56Sopenharmony_ci        try:  # Close undo block and expose new line in finally clause.
14257db96d56Sopenharmony_ci            if first and last:
14267db96d56Sopenharmony_ci                text.delete(first, last)
14277db96d56Sopenharmony_ci                text.mark_set("insert", first)
14287db96d56Sopenharmony_ci            line = text.get("insert linestart", "insert")
14297db96d56Sopenharmony_ci
14307db96d56Sopenharmony_ci            # Count leading whitespace for indent size.
14317db96d56Sopenharmony_ci            i, n = 0, len(line)
14327db96d56Sopenharmony_ci            while i < n and line[i] in " \t":
14337db96d56Sopenharmony_ci                i += 1
14347db96d56Sopenharmony_ci            if i == n:
14357db96d56Sopenharmony_ci                # The cursor is in or at leading indentation in a continuation
14367db96d56Sopenharmony_ci                # line; just inject an empty line at the start.
14377db96d56Sopenharmony_ci                text.insert("insert linestart", '\n',
14387db96d56Sopenharmony_ci                            self.user_input_insert_tags)
14397db96d56Sopenharmony_ci                return "break"
14407db96d56Sopenharmony_ci            indent = line[:i]
14417db96d56Sopenharmony_ci
14427db96d56Sopenharmony_ci            # Strip whitespace before insert point unless it's in the prompt.
14437db96d56Sopenharmony_ci            i = 0
14447db96d56Sopenharmony_ci            while line and line[-1] in " \t":
14457db96d56Sopenharmony_ci                line = line[:-1]
14467db96d56Sopenharmony_ci                i += 1
14477db96d56Sopenharmony_ci            if i:
14487db96d56Sopenharmony_ci                text.delete("insert - %d chars" % i, "insert")
14497db96d56Sopenharmony_ci
14507db96d56Sopenharmony_ci            # Strip whitespace after insert point.
14517db96d56Sopenharmony_ci            while text.get("insert") in " \t":
14527db96d56Sopenharmony_ci                text.delete("insert")
14537db96d56Sopenharmony_ci
14547db96d56Sopenharmony_ci            # Insert new line.
14557db96d56Sopenharmony_ci            text.insert("insert", '\n', self.user_input_insert_tags)
14567db96d56Sopenharmony_ci
14577db96d56Sopenharmony_ci            # Adjust indentation for continuations and block open/close.
14587db96d56Sopenharmony_ci            # First need to find the last statement.
14597db96d56Sopenharmony_ci            lno = index2line(text.index('insert'))
14607db96d56Sopenharmony_ci            y = pyparse.Parser(self.indentwidth, self.tabwidth)
14617db96d56Sopenharmony_ci            if not self.prompt_last_line:
14627db96d56Sopenharmony_ci                for context in self.num_context_lines:
14637db96d56Sopenharmony_ci                    startat = max(lno - context, 1)
14647db96d56Sopenharmony_ci                    startatindex = repr(startat) + ".0"
14657db96d56Sopenharmony_ci                    rawtext = text.get(startatindex, "insert")
14667db96d56Sopenharmony_ci                    y.set_code(rawtext)
14677db96d56Sopenharmony_ci                    bod = y.find_good_parse_start(
14687db96d56Sopenharmony_ci                            self._build_char_in_string_func(startatindex))
14697db96d56Sopenharmony_ci                    if bod is not None or startat == 1:
14707db96d56Sopenharmony_ci                        break
14717db96d56Sopenharmony_ci                y.set_lo(bod or 0)
14727db96d56Sopenharmony_ci            else:
14737db96d56Sopenharmony_ci                r = text.tag_prevrange("console", "insert")
14747db96d56Sopenharmony_ci                if r:
14757db96d56Sopenharmony_ci                    startatindex = r[1]
14767db96d56Sopenharmony_ci                else:
14777db96d56Sopenharmony_ci                    startatindex = "1.0"
14787db96d56Sopenharmony_ci                rawtext = text.get(startatindex, "insert")
14797db96d56Sopenharmony_ci                y.set_code(rawtext)
14807db96d56Sopenharmony_ci                y.set_lo(0)
14817db96d56Sopenharmony_ci
14827db96d56Sopenharmony_ci            c = y.get_continuation_type()
14837db96d56Sopenharmony_ci            if c != pyparse.C_NONE:
14847db96d56Sopenharmony_ci                # The current statement hasn't ended yet.
14857db96d56Sopenharmony_ci                if c == pyparse.C_STRING_FIRST_LINE:
14867db96d56Sopenharmony_ci                    # After the first line of a string do not indent at all.
14877db96d56Sopenharmony_ci                    pass
14887db96d56Sopenharmony_ci                elif c == pyparse.C_STRING_NEXT_LINES:
14897db96d56Sopenharmony_ci                    # Inside a string which started before this line;
14907db96d56Sopenharmony_ci                    # just mimic the current indent.
14917db96d56Sopenharmony_ci                    text.insert("insert", indent, self.user_input_insert_tags)
14927db96d56Sopenharmony_ci                elif c == pyparse.C_BRACKET:
14937db96d56Sopenharmony_ci                    # Line up with the first (if any) element of the
14947db96d56Sopenharmony_ci                    # last open bracket structure; else indent one
14957db96d56Sopenharmony_ci                    # level beyond the indent of the line with the
14967db96d56Sopenharmony_ci                    # last open bracket.
14977db96d56Sopenharmony_ci                    self.reindent_to(y.compute_bracket_indent())
14987db96d56Sopenharmony_ci                elif c == pyparse.C_BACKSLASH:
14997db96d56Sopenharmony_ci                    # If more than one line in this statement already, just
15007db96d56Sopenharmony_ci                    # mimic the current indent; else if initial line
15017db96d56Sopenharmony_ci                    # has a start on an assignment stmt, indent to
15027db96d56Sopenharmony_ci                    # beyond leftmost =; else to beyond first chunk of
15037db96d56Sopenharmony_ci                    # non-whitespace on initial line.
15047db96d56Sopenharmony_ci                    if y.get_num_lines_in_stmt() > 1:
15057db96d56Sopenharmony_ci                        text.insert("insert", indent,
15067db96d56Sopenharmony_ci                                    self.user_input_insert_tags)
15077db96d56Sopenharmony_ci                    else:
15087db96d56Sopenharmony_ci                        self.reindent_to(y.compute_backslash_indent())
15097db96d56Sopenharmony_ci                else:
15107db96d56Sopenharmony_ci                    assert 0, f"bogus continuation type {c!r}"
15117db96d56Sopenharmony_ci                return "break"
15127db96d56Sopenharmony_ci
15137db96d56Sopenharmony_ci            # This line starts a brand new statement; indent relative to
15147db96d56Sopenharmony_ci            # indentation of initial line of closest preceding
15157db96d56Sopenharmony_ci            # interesting statement.
15167db96d56Sopenharmony_ci            indent = y.get_base_indent_string()
15177db96d56Sopenharmony_ci            text.insert("insert", indent, self.user_input_insert_tags)
15187db96d56Sopenharmony_ci            if y.is_block_opener():
15197db96d56Sopenharmony_ci                self.smart_indent_event(event)
15207db96d56Sopenharmony_ci            elif indent and y.is_block_closer():
15217db96d56Sopenharmony_ci                self.smart_backspace_event(event)
15227db96d56Sopenharmony_ci            return "break"
15237db96d56Sopenharmony_ci        finally:
15247db96d56Sopenharmony_ci            text.see("insert")
15257db96d56Sopenharmony_ci            text.undo_block_stop()
15267db96d56Sopenharmony_ci
15277db96d56Sopenharmony_ci    # Our editwin provides an is_char_in_string function that works
15287db96d56Sopenharmony_ci    # with a Tk text index, but PyParse only knows about offsets into
15297db96d56Sopenharmony_ci    # a string. This builds a function for PyParse that accepts an
15307db96d56Sopenharmony_ci    # offset.
15317db96d56Sopenharmony_ci
15327db96d56Sopenharmony_ci    def _build_char_in_string_func(self, startindex):
15337db96d56Sopenharmony_ci        def inner(offset, _startindex=startindex,
15347db96d56Sopenharmony_ci                  _icis=self.is_char_in_string):
15357db96d56Sopenharmony_ci            return _icis(_startindex + "+%dc" % offset)
15367db96d56Sopenharmony_ci        return inner
15377db96d56Sopenharmony_ci
15387db96d56Sopenharmony_ci    # XXX this isn't bound to anything -- see tabwidth comments
15397db96d56Sopenharmony_ci##     def change_tabwidth_event(self, event):
15407db96d56Sopenharmony_ci##         new = self._asktabwidth()
15417db96d56Sopenharmony_ci##         if new != self.tabwidth:
15427db96d56Sopenharmony_ci##             self.tabwidth = new
15437db96d56Sopenharmony_ci##             self.set_indentation_params(0, guess=0)
15447db96d56Sopenharmony_ci##         return "break"
15457db96d56Sopenharmony_ci
15467db96d56Sopenharmony_ci    # Make string that displays as n leading blanks.
15477db96d56Sopenharmony_ci
15487db96d56Sopenharmony_ci    def _make_blanks(self, n):
15497db96d56Sopenharmony_ci        if self.usetabs:
15507db96d56Sopenharmony_ci            ntabs, nspaces = divmod(n, self.tabwidth)
15517db96d56Sopenharmony_ci            return '\t' * ntabs + ' ' * nspaces
15527db96d56Sopenharmony_ci        else:
15537db96d56Sopenharmony_ci            return ' ' * n
15547db96d56Sopenharmony_ci
15557db96d56Sopenharmony_ci    # Delete from beginning of line to insert point, then reinsert
15567db96d56Sopenharmony_ci    # column logical (meaning use tabs if appropriate) spaces.
15577db96d56Sopenharmony_ci
15587db96d56Sopenharmony_ci    def reindent_to(self, column):
15597db96d56Sopenharmony_ci        text = self.text
15607db96d56Sopenharmony_ci        text.undo_block_start()
15617db96d56Sopenharmony_ci        if text.compare("insert linestart", "!=", "insert"):
15627db96d56Sopenharmony_ci            text.delete("insert linestart", "insert")
15637db96d56Sopenharmony_ci        if column:
15647db96d56Sopenharmony_ci            text.insert("insert", self._make_blanks(column),
15657db96d56Sopenharmony_ci                        self.user_input_insert_tags)
15667db96d56Sopenharmony_ci        text.undo_block_stop()
15677db96d56Sopenharmony_ci
15687db96d56Sopenharmony_ci    # Guess indentwidth from text content.
15697db96d56Sopenharmony_ci    # Return guessed indentwidth.  This should not be believed unless
15707db96d56Sopenharmony_ci    # it's in a reasonable range (e.g., it will be 0 if no indented
15717db96d56Sopenharmony_ci    # blocks are found).
15727db96d56Sopenharmony_ci
15737db96d56Sopenharmony_ci    def guess_indent(self):
15747db96d56Sopenharmony_ci        opener, indented = IndentSearcher(self.text).run()
15757db96d56Sopenharmony_ci        if opener and indented:
15767db96d56Sopenharmony_ci            raw, indentsmall = get_line_indent(opener, self.tabwidth)
15777db96d56Sopenharmony_ci            raw, indentlarge = get_line_indent(indented, self.tabwidth)
15787db96d56Sopenharmony_ci        else:
15797db96d56Sopenharmony_ci            indentsmall = indentlarge = 0
15807db96d56Sopenharmony_ci        return indentlarge - indentsmall
15817db96d56Sopenharmony_ci
15827db96d56Sopenharmony_ci    def toggle_line_numbers_event(self, event=None):
15837db96d56Sopenharmony_ci        if self.line_numbers is None:
15847db96d56Sopenharmony_ci            return
15857db96d56Sopenharmony_ci
15867db96d56Sopenharmony_ci        if self.line_numbers.is_shown:
15877db96d56Sopenharmony_ci            self.line_numbers.hide_sidebar()
15887db96d56Sopenharmony_ci            menu_label = "Show"
15897db96d56Sopenharmony_ci        else:
15907db96d56Sopenharmony_ci            self.line_numbers.show_sidebar()
15917db96d56Sopenharmony_ci            menu_label = "Hide"
15927db96d56Sopenharmony_ci        self.update_menu_label(menu='options', index='*ine*umbers',
15937db96d56Sopenharmony_ci                               label=f'{menu_label} Line Numbers')
15947db96d56Sopenharmony_ci
15957db96d56Sopenharmony_ci# "line.col" -> line, as an int
15967db96d56Sopenharmony_cidef index2line(index):
15977db96d56Sopenharmony_ci    return int(float(index))
15987db96d56Sopenharmony_ci
15997db96d56Sopenharmony_ci
16007db96d56Sopenharmony_ci_line_indent_re = re.compile(r'[ \t]*')
16017db96d56Sopenharmony_cidef get_line_indent(line, tabwidth):
16027db96d56Sopenharmony_ci    """Return a line's indentation as (# chars, effective # of spaces).
16037db96d56Sopenharmony_ci
16047db96d56Sopenharmony_ci    The effective # of spaces is the length after properly "expanding"
16057db96d56Sopenharmony_ci    the tabs into spaces, as done by str.expandtabs(tabwidth).
16067db96d56Sopenharmony_ci    """
16077db96d56Sopenharmony_ci    m = _line_indent_re.match(line)
16087db96d56Sopenharmony_ci    return m.end(), len(m.group().expandtabs(tabwidth))
16097db96d56Sopenharmony_ci
16107db96d56Sopenharmony_ci
16117db96d56Sopenharmony_ciclass IndentSearcher:
16127db96d56Sopenharmony_ci    "Manage initial indent guess, returned by run method."
16137db96d56Sopenharmony_ci
16147db96d56Sopenharmony_ci    def __init__(self, text):
16157db96d56Sopenharmony_ci        self.text = text
16167db96d56Sopenharmony_ci        self.i = self.finished = 0
16177db96d56Sopenharmony_ci        self.blkopenline = self.indentedline = None
16187db96d56Sopenharmony_ci
16197db96d56Sopenharmony_ci    def readline(self):
16207db96d56Sopenharmony_ci        if self.finished:
16217db96d56Sopenharmony_ci            return ""
16227db96d56Sopenharmony_ci        i = self.i = self.i + 1
16237db96d56Sopenharmony_ci        mark = repr(i) + ".0"
16247db96d56Sopenharmony_ci        if self.text.compare(mark, ">=", "end"):
16257db96d56Sopenharmony_ci            return ""
16267db96d56Sopenharmony_ci        return self.text.get(mark, mark + " lineend+1c")
16277db96d56Sopenharmony_ci
16287db96d56Sopenharmony_ci    def tokeneater(self, type, token, start, end, line,
16297db96d56Sopenharmony_ci                   INDENT=tokenize.INDENT,
16307db96d56Sopenharmony_ci                   NAME=tokenize.NAME,
16317db96d56Sopenharmony_ci                   OPENERS=('class', 'def', 'for', 'if', 'match', 'try',
16327db96d56Sopenharmony_ci                            'while', 'with')):
16337db96d56Sopenharmony_ci        if self.finished:
16347db96d56Sopenharmony_ci            pass
16357db96d56Sopenharmony_ci        elif type == NAME and token in OPENERS:
16367db96d56Sopenharmony_ci            self.blkopenline = line
16377db96d56Sopenharmony_ci        elif type == INDENT and self.blkopenline:
16387db96d56Sopenharmony_ci            self.indentedline = line
16397db96d56Sopenharmony_ci            self.finished = 1
16407db96d56Sopenharmony_ci
16417db96d56Sopenharmony_ci    def run(self):
16427db96d56Sopenharmony_ci        """Return 2 lines containing block opener and and indent.
16437db96d56Sopenharmony_ci
16447db96d56Sopenharmony_ci        Either the indent line or both may be None.
16457db96d56Sopenharmony_ci        """
16467db96d56Sopenharmony_ci        try:
16477db96d56Sopenharmony_ci            tokens = tokenize.generate_tokens(self.readline)
16487db96d56Sopenharmony_ci            for token in tokens:
16497db96d56Sopenharmony_ci                self.tokeneater(*token)
16507db96d56Sopenharmony_ci        except (tokenize.TokenError, SyntaxError):
16517db96d56Sopenharmony_ci            # Stopping the tokenizer early can trigger spurious errors.
16527db96d56Sopenharmony_ci            pass
16537db96d56Sopenharmony_ci        return self.blkopenline, self.indentedline
16547db96d56Sopenharmony_ci
16557db96d56Sopenharmony_ci### end autoindent code ###
16567db96d56Sopenharmony_ci
16577db96d56Sopenharmony_ci
16587db96d56Sopenharmony_cidef prepstr(s):
16597db96d56Sopenharmony_ci    """Extract the underscore from a string.
16607db96d56Sopenharmony_ci
16617db96d56Sopenharmony_ci    For example, prepstr("Co_py") returns (2, "Copy").
16627db96d56Sopenharmony_ci
16637db96d56Sopenharmony_ci    Args:
16647db96d56Sopenharmony_ci        s: String with underscore.
16657db96d56Sopenharmony_ci
16667db96d56Sopenharmony_ci    Returns:
16677db96d56Sopenharmony_ci        Tuple of (position of underscore, string without underscore).
16687db96d56Sopenharmony_ci    """
16697db96d56Sopenharmony_ci    i = s.find('_')
16707db96d56Sopenharmony_ci    if i >= 0:
16717db96d56Sopenharmony_ci        s = s[:i] + s[i+1:]
16727db96d56Sopenharmony_ci    return i, s
16737db96d56Sopenharmony_ci
16747db96d56Sopenharmony_ci
16757db96d56Sopenharmony_cikeynames = {
16767db96d56Sopenharmony_ci 'bracketleft': '[',
16777db96d56Sopenharmony_ci 'bracketright': ']',
16787db96d56Sopenharmony_ci 'slash': '/',
16797db96d56Sopenharmony_ci}
16807db96d56Sopenharmony_ci
16817db96d56Sopenharmony_cidef get_accelerator(keydefs, eventname):
16827db96d56Sopenharmony_ci    """Return a formatted string for the keybinding of an event.
16837db96d56Sopenharmony_ci
16847db96d56Sopenharmony_ci    Convert the first keybinding for a given event to a form that
16857db96d56Sopenharmony_ci    can be displayed as an accelerator on the menu.
16867db96d56Sopenharmony_ci
16877db96d56Sopenharmony_ci    Args:
16887db96d56Sopenharmony_ci        keydefs: Dictionary of valid events to keybindings.
16897db96d56Sopenharmony_ci        eventname: Event to retrieve keybinding for.
16907db96d56Sopenharmony_ci
16917db96d56Sopenharmony_ci    Returns:
16927db96d56Sopenharmony_ci        Formatted string of the keybinding.
16937db96d56Sopenharmony_ci    """
16947db96d56Sopenharmony_ci    keylist = keydefs.get(eventname)
16957db96d56Sopenharmony_ci    # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
16967db96d56Sopenharmony_ci    # if not keylist:
16977db96d56Sopenharmony_ci    if (not keylist) or (macosx.isCocoaTk() and eventname in {
16987db96d56Sopenharmony_ci                            "<<open-module>>",
16997db96d56Sopenharmony_ci                            "<<goto-line>>",
17007db96d56Sopenharmony_ci                            "<<change-indentwidth>>"}):
17017db96d56Sopenharmony_ci        return ""
17027db96d56Sopenharmony_ci    s = keylist[0]
17037db96d56Sopenharmony_ci    # Convert strings of the form -singlelowercase to -singleuppercase.
17047db96d56Sopenharmony_ci    s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
17057db96d56Sopenharmony_ci    # Convert certain keynames to their symbol.
17067db96d56Sopenharmony_ci    s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
17077db96d56Sopenharmony_ci    # Remove Key- from string.
17087db96d56Sopenharmony_ci    s = re.sub("Key-", "", s)
17097db96d56Sopenharmony_ci    # Convert Cancel to Ctrl-Break.
17107db96d56Sopenharmony_ci    s = re.sub("Cancel", "Ctrl-Break", s)   # dscherer@cmu.edu
17117db96d56Sopenharmony_ci    # Convert Control to Ctrl-.
17127db96d56Sopenharmony_ci    s = re.sub("Control-", "Ctrl-", s)
17137db96d56Sopenharmony_ci    # Change - to +.
17147db96d56Sopenharmony_ci    s = re.sub("-", "+", s)
17157db96d56Sopenharmony_ci    # Change >< to space.
17167db96d56Sopenharmony_ci    s = re.sub("><", " ", s)
17177db96d56Sopenharmony_ci    # Remove <.
17187db96d56Sopenharmony_ci    s = re.sub("<", "", s)
17197db96d56Sopenharmony_ci    # Remove >.
17207db96d56Sopenharmony_ci    s = re.sub(">", "", s)
17217db96d56Sopenharmony_ci    return s
17227db96d56Sopenharmony_ci
17237db96d56Sopenharmony_ci
17247db96d56Sopenharmony_cidef fixwordbreaks(root):
17257db96d56Sopenharmony_ci    # On Windows, tcl/tk breaks 'words' only on spaces, as in Command Prompt.
17267db96d56Sopenharmony_ci    # We want Motif style everywhere. See #21474, msg218992 and followup.
17277db96d56Sopenharmony_ci    tk = root.tk
17287db96d56Sopenharmony_ci    tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
17297db96d56Sopenharmony_ci    tk.call('set', 'tcl_wordchars', r'\w')
17307db96d56Sopenharmony_ci    tk.call('set', 'tcl_nonwordchars', r'\W')
17317db96d56Sopenharmony_ci
17327db96d56Sopenharmony_ci
17337db96d56Sopenharmony_cidef _editor_window(parent):  # htest #
17347db96d56Sopenharmony_ci    # error if close master window first - timer event, after script
17357db96d56Sopenharmony_ci    root = parent
17367db96d56Sopenharmony_ci    fixwordbreaks(root)
17377db96d56Sopenharmony_ci    if sys.argv[1:]:
17387db96d56Sopenharmony_ci        filename = sys.argv[1]
17397db96d56Sopenharmony_ci    else:
17407db96d56Sopenharmony_ci        filename = None
17417db96d56Sopenharmony_ci    macosx.setupApp(root, None)
17427db96d56Sopenharmony_ci    edit = EditorWindow(root=root, filename=filename)
17437db96d56Sopenharmony_ci    text = edit.text
17447db96d56Sopenharmony_ci    text['height'] = 10
17457db96d56Sopenharmony_ci    for i in range(20):
17467db96d56Sopenharmony_ci        text.insert('insert', '  '*i + str(i) + '\n')
17477db96d56Sopenharmony_ci    # text.bind("<<close-all-windows>>", edit.close_event)
17487db96d56Sopenharmony_ci    # Does not stop error, neither does following
17497db96d56Sopenharmony_ci    # edit.text.bind("<<close-window>>", edit.close_event)
17507db96d56Sopenharmony_ci
17517db96d56Sopenharmony_ciif __name__ == '__main__':
17527db96d56Sopenharmony_ci    from unittest import main
17537db96d56Sopenharmony_ci    main('idlelib.idle_test.test_editor', verbosity=2, exit=False)
17547db96d56Sopenharmony_ci
17557db96d56Sopenharmony_ci    from idlelib.idle_test.htest import run
17567db96d56Sopenharmony_ci    run(_editor_window)
1757