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