ยปCore Development>Code coverage>Lib/idlelib/editor.py

Python code coverage for Lib/idlelib/editor.py

#countcontent
1n/aimport importlib
2n/aimport importlib.abc
3n/aimport importlib.util
4n/aimport os
5n/aimport platform
6n/aimport re
7n/aimport string
8n/aimport sys
9n/aimport tokenize
10n/aimport traceback
11n/aimport webbrowser
12n/a
13n/afrom tkinter import *
14n/afrom tkinter.ttk import Scrollbar
15n/aimport tkinter.simpledialog as tkSimpleDialog
16n/aimport tkinter.messagebox as tkMessageBox
17n/a
18n/afrom idlelib.config import idleConf
19n/afrom idlelib import configdialog
20n/afrom idlelib import grep
21n/afrom idlelib import help
22n/afrom idlelib import help_about
23n/afrom idlelib import macosx
24n/afrom idlelib.multicall import MultiCallCreator
25n/afrom idlelib import pyparse
26n/afrom idlelib import query
27n/afrom idlelib import replace
28n/afrom idlelib import search
29n/afrom idlelib import textview
30n/afrom idlelib import windows
31n/a
32n/a# The default tab setting for a Text widget, in average-width characters.
33n/aTK_TABWIDTH_DEFAULT = 8
34n/a_py_version = ' (%s)' % platform.python_version()
35n/a
36n/a
37n/adef _sphinx_version():
38n/a "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
39n/a major, minor, micro, level, serial = sys.version_info
40n/a release = '%s%s' % (major, minor)
41n/a release += '%s' % (micro,)
42n/a if level == 'candidate':
43n/a release += 'rc%s' % (serial,)
44n/a elif level != 'final':
45n/a release += '%s%s' % (level[0], serial)
46n/a return release
47n/a
48n/a
49n/aclass EditorWindow(object):
50n/a from idlelib.percolator import Percolator
51n/a from idlelib.colorizer import ColorDelegator, color_config
52n/a from idlelib.undo import UndoDelegator
53n/a from idlelib.iomenu import IOBinding, encoding
54n/a from idlelib import mainmenu
55n/a from tkinter import Toplevel
56n/a from idlelib.statusbar import MultiStatusBar
57n/a
58n/a filesystemencoding = sys.getfilesystemencoding() # for file names
59n/a help_url = None
60n/a
61n/a def __init__(self, flist=None, filename=None, key=None, root=None):
62n/a if EditorWindow.help_url is None:
63n/a dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
64n/a if sys.platform.count('linux'):
65n/a # look for html docs in a couple of standard places
66n/a pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
67n/a if os.path.isdir('/var/www/html/python/'): # "python2" rpm
68n/a dochome = '/var/www/html/python/index.html'
69n/a else:
70n/a basepath = '/usr/share/doc/' # standard location
71n/a dochome = os.path.join(basepath, pyver,
72n/a 'Doc', 'index.html')
73n/a elif sys.platform[:3] == 'win':
74n/a chmfile = os.path.join(sys.base_prefix, 'Doc',
75n/a 'Python%s.chm' % _sphinx_version())
76n/a if os.path.isfile(chmfile):
77n/a dochome = chmfile
78n/a elif sys.platform == 'darwin':
79n/a # documentation may be stored inside a python framework
80n/a dochome = os.path.join(sys.base_prefix,
81n/a 'Resources/English.lproj/Documentation/index.html')
82n/a dochome = os.path.normpath(dochome)
83n/a if os.path.isfile(dochome):
84n/a EditorWindow.help_url = dochome
85n/a if sys.platform == 'darwin':
86n/a # Safari requires real file:-URLs
87n/a EditorWindow.help_url = 'file://' + EditorWindow.help_url
88n/a else:
89n/a EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
90n/a self.flist = flist
91n/a root = root or flist.root
92n/a self.root = root
93n/a try:
94n/a sys.ps1
95n/a except AttributeError:
96n/a sys.ps1 = '>>> '
97n/a self.menubar = Menu(root)
98n/a self.top = top = windows.ListedToplevel(root, menu=self.menubar)
99n/a if flist:
100n/a self.tkinter_vars = flist.vars
101n/a #self.top.instance_dict makes flist.inversedict available to
102n/a #configdialog.py so it can access all EditorWindow instances
103n/a self.top.instance_dict = flist.inversedict
104n/a else:
105n/a self.tkinter_vars = {} # keys: Tkinter event names
106n/a # values: Tkinter variable instances
107n/a self.top.instance_dict = {}
108n/a self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
109n/a 'recent-files.lst')
110n/a self.text_frame = text_frame = Frame(top)
111n/a self.vbar = vbar = Scrollbar(text_frame, name='vbar')
112n/a self.width = idleConf.GetOption('main', 'EditorWindow',
113n/a 'width', type='int')
114n/a text_options = {
115n/a 'name': 'text',
116n/a 'padx': 5,
117n/a 'wrap': 'none',
118n/a 'highlightthickness': 0,
119n/a 'width': self.width,
120n/a 'tabstyle': 'wordprocessor', # new in 8.5
121n/a 'height': idleConf.GetOption(
122n/a 'main', 'EditorWindow', 'height', type='int'),
123n/a }
124n/a self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
125n/a self.top.focused_widget = self.text
126n/a
127n/a self.createmenubar()
128n/a self.apply_bindings()
129n/a
130n/a self.top.protocol("WM_DELETE_WINDOW", self.close)
131n/a self.top.bind("<<close-window>>", self.close_event)
132n/a if macosx.isAquaTk():
133n/a # Command-W on editorwindows doesn't work without this.
134n/a text.bind('<<close-window>>', self.close_event)
135n/a # Some OS X systems have only one mouse button, so use
136n/a # control-click for popup context menus there. For two
137n/a # buttons, AquaTk defines <2> as the right button, not <3>.
138n/a text.bind("<Control-Button-1>",self.right_menu_event)
139n/a text.bind("<2>", self.right_menu_event)
140n/a else:
141n/a # Elsewhere, use right-click for popup menus.
142n/a text.bind("<3>",self.right_menu_event)
143n/a text.bind("<<cut>>", self.cut)
144n/a text.bind("<<copy>>", self.copy)
145n/a text.bind("<<paste>>", self.paste)
146n/a text.bind("<<center-insert>>", self.center_insert_event)
147n/a text.bind("<<help>>", self.help_dialog)
148n/a text.bind("<<python-docs>>", self.python_docs)
149n/a text.bind("<<about-idle>>", self.about_dialog)
150n/a text.bind("<<open-config-dialog>>", self.config_dialog)
151n/a text.bind("<<open-module>>", self.open_module)
152n/a text.bind("<<do-nothing>>", lambda event: "break")
153n/a text.bind("<<select-all>>", self.select_all)
154n/a text.bind("<<remove-selection>>", self.remove_selection)
155n/a text.bind("<<find>>", self.find_event)
156n/a text.bind("<<find-again>>", self.find_again_event)
157n/a text.bind("<<find-in-files>>", self.find_in_files_event)
158n/a text.bind("<<find-selection>>", self.find_selection_event)
159n/a text.bind("<<replace>>", self.replace_event)
160n/a text.bind("<<goto-line>>", self.goto_line_event)
161n/a text.bind("<<smart-backspace>>",self.smart_backspace_event)
162n/a text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
163n/a text.bind("<<smart-indent>>",self.smart_indent_event)
164n/a text.bind("<<indent-region>>",self.indent_region_event)
165n/a text.bind("<<dedent-region>>",self.dedent_region_event)
166n/a text.bind("<<comment-region>>",self.comment_region_event)
167n/a text.bind("<<uncomment-region>>",self.uncomment_region_event)
168n/a text.bind("<<tabify-region>>",self.tabify_region_event)
169n/a text.bind("<<untabify-region>>",self.untabify_region_event)
170n/a text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
171n/a text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
172n/a text.bind("<Left>", self.move_at_edge_if_selection(0))
173n/a text.bind("<Right>", self.move_at_edge_if_selection(1))
174n/a text.bind("<<del-word-left>>", self.del_word_left)
175n/a text.bind("<<del-word-right>>", self.del_word_right)
176n/a text.bind("<<beginning-of-line>>", self.home_callback)
177n/a
178n/a if flist:
179n/a flist.inversedict[self] = key
180n/a if key:
181n/a flist.dict[key] = self
182n/a text.bind("<<open-new-window>>", self.new_callback)
183n/a text.bind("<<close-all-windows>>", self.flist.close_all_callback)
184n/a text.bind("<<open-class-browser>>", self.open_class_browser)
185n/a text.bind("<<open-path-browser>>", self.open_path_browser)
186n/a text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
187n/a
188n/a self.set_status_bar()
189n/a vbar['command'] = text.yview
190n/a vbar.pack(side=RIGHT, fill=Y)
191n/a text['yscrollcommand'] = vbar.set
192n/a text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
193n/a text_frame.pack(side=LEFT, fill=BOTH, expand=1)
194n/a text.pack(side=TOP, fill=BOTH, expand=1)
195n/a text.focus_set()
196n/a
197n/a # usetabs true -> literal tab characters are used by indent and
198n/a # dedent cmds, possibly mixed with spaces if
199n/a # indentwidth is not a multiple of tabwidth,
200n/a # which will cause Tabnanny to nag!
201n/a # false -> tab characters are converted to spaces by indent
202n/a # and dedent cmds, and ditto TAB keystrokes
203n/a # Although use-spaces=0 can be configured manually in config-main.def,
204n/a # configuration of tabs v. spaces is not supported in the configuration
205n/a # dialog. IDLE promotes the preferred Python indentation: use spaces!
206n/a usespaces = idleConf.GetOption('main', 'Indent',
207n/a 'use-spaces', type='bool')
208n/a self.usetabs = not usespaces
209n/a
210n/a # tabwidth is the display width of a literal tab character.
211n/a # CAUTION: telling Tk to use anything other than its default
212n/a # tab setting causes it to use an entirely different tabbing algorithm,
213n/a # treating tab stops as fixed distances from the left margin.
214n/a # Nobody expects this, so for now tabwidth should never be changed.
215n/a self.tabwidth = 8 # must remain 8 until Tk is fixed.
216n/a
217n/a # indentwidth is the number of screen characters per indent level.
218n/a # The recommended Python indentation is four spaces.
219n/a self.indentwidth = self.tabwidth
220n/a self.set_notabs_indentwidth()
221n/a
222n/a # If context_use_ps1 is true, parsing searches back for a ps1 line;
223n/a # else searches for a popular (if, def, ...) Python stmt.
224n/a self.context_use_ps1 = False
225n/a
226n/a # When searching backwards for a reliable place to begin parsing,
227n/a # first start num_context_lines[0] lines back, then
228n/a # num_context_lines[1] lines back if that didn't work, and so on.
229n/a # The last value should be huge (larger than the # of lines in a
230n/a # conceivable file).
231n/a # Making the initial values larger slows things down more often.
232n/a self.num_context_lines = 50, 500, 5000000
233n/a self.per = per = self.Percolator(text)
234n/a self.undo = undo = self.UndoDelegator()
235n/a per.insertfilter(undo)
236n/a text.undo_block_start = undo.undo_block_start
237n/a text.undo_block_stop = undo.undo_block_stop
238n/a undo.set_saved_change_hook(self.saved_change_hook)
239n/a # IOBinding implements file I/O and printing functionality
240n/a self.io = io = self.IOBinding(self)
241n/a io.set_filename_change_hook(self.filename_change_hook)
242n/a self.good_load = False
243n/a self.set_indentation_params(False)
244n/a self.color = None # initialized below in self.ResetColorizer
245n/a if filename:
246n/a if os.path.exists(filename) and not os.path.isdir(filename):
247n/a if io.loadfile(filename):
248n/a self.good_load = True
249n/a is_py_src = self.ispythonsource(filename)
250n/a self.set_indentation_params(is_py_src)
251n/a else:
252n/a io.set_filename(filename)
253n/a self.good_load = True
254n/a
255n/a self.ResetColorizer()
256n/a self.saved_change_hook()
257n/a self.update_recent_files_list()
258n/a self.load_extensions()
259n/a menu = self.menudict.get('windows')
260n/a if menu:
261n/a end = menu.index("end")
262n/a if end is None:
263n/a end = -1
264n/a if end >= 0:
265n/a menu.add_separator()
266n/a end = end + 1
267n/a self.wmenu_end = end
268n/a windows.register_callback(self.postwindowsmenu)
269n/a
270n/a # Some abstractions so IDLE extensions are cross-IDE
271n/a self.askyesno = tkMessageBox.askyesno
272n/a self.askinteger = tkSimpleDialog.askinteger
273n/a self.showerror = tkMessageBox.showerror
274n/a
275n/a def _filename_to_unicode(self, filename):
276n/a """Return filename as BMP unicode so diplayable in Tk."""
277n/a # Decode bytes to unicode.
278n/a if isinstance(filename, bytes):
279n/a try:
280n/a filename = filename.decode(self.filesystemencoding)
281n/a except UnicodeDecodeError:
282n/a try:
283n/a filename = filename.decode(self.encoding)
284n/a except UnicodeDecodeError:
285n/a # byte-to-byte conversion
286n/a filename = filename.decode('iso8859-1')
287n/a # Replace non-BMP char with diamond questionmark.
288n/a return re.sub('[\U00010000-\U0010FFFF]', '\ufffd', filename)
289n/a
290n/a def new_callback(self, event):
291n/a dirname, basename = self.io.defaultfilename()
292n/a self.flist.new(dirname)
293n/a return "break"
294n/a
295n/a def home_callback(self, event):
296n/a if (event.state & 4) != 0 and event.keysym == "Home":
297n/a # state&4==Control. If <Control-Home>, use the Tk binding.
298n/a return
299n/a if self.text.index("iomark") and \
300n/a self.text.compare("iomark", "<=", "insert lineend") and \
301n/a self.text.compare("insert linestart", "<=", "iomark"):
302n/a # In Shell on input line, go to just after prompt
303n/a insertpt = int(self.text.index("iomark").split(".")[1])
304n/a else:
305n/a line = self.text.get("insert linestart", "insert lineend")
306n/a for insertpt in range(len(line)):
307n/a if line[insertpt] not in (' ','\t'):
308n/a break
309n/a else:
310n/a insertpt=len(line)
311n/a lineat = int(self.text.index("insert").split('.')[1])
312n/a if insertpt == lineat:
313n/a insertpt = 0
314n/a dest = "insert linestart+"+str(insertpt)+"c"
315n/a if (event.state&1) == 0:
316n/a # shift was not pressed
317n/a self.text.tag_remove("sel", "1.0", "end")
318n/a else:
319n/a if not self.text.index("sel.first"):
320n/a # there was no previous selection
321n/a self.text.mark_set("my_anchor", "insert")
322n/a else:
323n/a if self.text.compare(self.text.index("sel.first"), "<",
324n/a self.text.index("insert")):
325n/a self.text.mark_set("my_anchor", "sel.first") # extend back
326n/a else:
327n/a self.text.mark_set("my_anchor", "sel.last") # extend forward
328n/a first = self.text.index(dest)
329n/a last = self.text.index("my_anchor")
330n/a if self.text.compare(first,">",last):
331n/a first,last = last,first
332n/a self.text.tag_remove("sel", "1.0", "end")
333n/a self.text.tag_add("sel", first, last)
334n/a self.text.mark_set("insert", dest)
335n/a self.text.see("insert")
336n/a return "break"
337n/a
338n/a def set_status_bar(self):
339n/a self.status_bar = self.MultiStatusBar(self.top)
340n/a sep = Frame(self.top, height=1, borderwidth=1, background='grey75')
341n/a if sys.platform == "darwin":
342n/a # Insert some padding to avoid obscuring some of the statusbar
343n/a # by the resize widget.
344n/a self.status_bar.set_label('_padding1', ' ', side=RIGHT)
345n/a self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
346n/a self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
347n/a self.status_bar.pack(side=BOTTOM, fill=X)
348n/a sep.pack(side=BOTTOM, fill=X)
349n/a self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
350n/a self.text.event_add("<<set-line-and-column>>",
351n/a "<KeyRelease>", "<ButtonRelease>")
352n/a self.text.after_idle(self.set_line_and_column)
353n/a
354n/a def set_line_and_column(self, event=None):
355n/a line, column = self.text.index(INSERT).split('.')
356n/a self.status_bar.set_label('column', 'Col: %s' % column)
357n/a self.status_bar.set_label('line', 'Ln: %s' % line)
358n/a
359n/a menu_specs = [
360n/a ("file", "_File"),
361n/a ("edit", "_Edit"),
362n/a ("format", "F_ormat"),
363n/a ("run", "_Run"),
364n/a ("options", "_Options"),
365n/a ("windows", "_Window"),
366n/a ("help", "_Help"),
367n/a ]
368n/a
369n/a
370n/a def createmenubar(self):
371n/a mbar = self.menubar
372n/a self.menudict = menudict = {}
373n/a for name, label in self.menu_specs:
374n/a underline, label = prepstr(label)
375n/a menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
376n/a mbar.add_cascade(label=label, menu=menu, underline=underline)
377n/a if macosx.isCarbonTk():
378n/a # Insert the application menu
379n/a menudict['application'] = menu = Menu(mbar, name='apple',
380n/a tearoff=0)
381n/a mbar.add_cascade(label='IDLE', menu=menu)
382n/a self.fill_menus()
383n/a self.recent_files_menu = Menu(self.menubar, tearoff=0)
384n/a self.menudict['file'].insert_cascade(3, label='Recent Files',
385n/a underline=0,
386n/a menu=self.recent_files_menu)
387n/a self.base_helpmenu_length = self.menudict['help'].index(END)
388n/a self.reset_help_menu_entries()
389n/a
390n/a def postwindowsmenu(self):
391n/a # Only called when Windows menu exists
392n/a menu = self.menudict['windows']
393n/a end = menu.index("end")
394n/a if end is None:
395n/a end = -1
396n/a if end > self.wmenu_end:
397n/a menu.delete(self.wmenu_end+1, end)
398n/a windows.add_windows_to_menu(menu)
399n/a
400n/a rmenu = None
401n/a
402n/a def right_menu_event(self, event):
403n/a self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
404n/a if not self.rmenu:
405n/a self.make_rmenu()
406n/a rmenu = self.rmenu
407n/a self.event = event
408n/a iswin = sys.platform[:3] == 'win'
409n/a if iswin:
410n/a self.text.config(cursor="arrow")
411n/a
412n/a for item in self.rmenu_specs:
413n/a try:
414n/a label, eventname, verify_state = item
415n/a except ValueError: # see issue1207589
416n/a continue
417n/a
418n/a if verify_state is None:
419n/a continue
420n/a state = getattr(self, verify_state)()
421n/a rmenu.entryconfigure(label, state=state)
422n/a
423n/a
424n/a rmenu.tk_popup(event.x_root, event.y_root)
425n/a if iswin:
426n/a self.text.config(cursor="ibeam")
427n/a
428n/a rmenu_specs = [
429n/a # ("Label", "<<virtual-event>>", "statefuncname"), ...
430n/a ("Close", "<<close-window>>", None), # Example
431n/a ]
432n/a
433n/a def make_rmenu(self):
434n/a rmenu = Menu(self.text, tearoff=0)
435n/a for item in self.rmenu_specs:
436n/a label, eventname = item[0], item[1]
437n/a if label is not None:
438n/a def command(text=self.text, eventname=eventname):
439n/a text.event_generate(eventname)
440n/a rmenu.add_command(label=label, command=command)
441n/a else:
442n/a rmenu.add_separator()
443n/a self.rmenu = rmenu
444n/a
445n/a def rmenu_check_cut(self):
446n/a return self.rmenu_check_copy()
447n/a
448n/a def rmenu_check_copy(self):
449n/a try:
450n/a indx = self.text.index('sel.first')
451n/a except TclError:
452n/a return 'disabled'
453n/a else:
454n/a return 'normal' if indx else 'disabled'
455n/a
456n/a def rmenu_check_paste(self):
457n/a try:
458n/a self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
459n/a except TclError:
460n/a return 'disabled'
461n/a else:
462n/a return 'normal'
463n/a
464n/a def about_dialog(self, event=None):
465n/a "Handle Help 'About IDLE' event."
466n/a # Synchronize with macosx.overrideRootMenu.about_dialog.
467n/a help_about.AboutDialog(self.top,'About IDLE')
468n/a
469n/a def config_dialog(self, event=None):
470n/a "Handle Options 'Configure IDLE' event."
471n/a # Synchronize with macosx.overrideRootMenu.config_dialog.
472n/a configdialog.ConfigDialog(self.top,'Settings')
473n/a
474n/a def help_dialog(self, event=None):
475n/a "Handle Help 'IDLE Help' event."
476n/a # Synchronize with macosx.overrideRootMenu.help_dialog.
477n/a if self.root:
478n/a parent = self.root
479n/a else:
480n/a parent = self.top
481n/a help.show_idlehelp(parent)
482n/a
483n/a def python_docs(self, event=None):
484n/a if sys.platform[:3] == 'win':
485n/a try:
486n/a os.startfile(self.help_url)
487n/a except OSError as why:
488n/a tkMessageBox.showerror(title='Document Start Failure',
489n/a message=str(why), parent=self.text)
490n/a else:
491n/a webbrowser.open(self.help_url)
492n/a return "break"
493n/a
494n/a def cut(self,event):
495n/a self.text.event_generate("<<Cut>>")
496n/a return "break"
497n/a
498n/a def copy(self,event):
499n/a if not self.text.tag_ranges("sel"):
500n/a # There is no selection, so do nothing and maybe interrupt.
501n/a return
502n/a self.text.event_generate("<<Copy>>")
503n/a return "break"
504n/a
505n/a def paste(self,event):
506n/a self.text.event_generate("<<Paste>>")
507n/a self.text.see("insert")
508n/a return "break"
509n/a
510n/a def select_all(self, event=None):
511n/a self.text.tag_add("sel", "1.0", "end-1c")
512n/a self.text.mark_set("insert", "1.0")
513n/a self.text.see("insert")
514n/a return "break"
515n/a
516n/a def remove_selection(self, event=None):
517n/a self.text.tag_remove("sel", "1.0", "end")
518n/a self.text.see("insert")
519n/a
520n/a def move_at_edge_if_selection(self, edge_index):
521n/a """Cursor move begins at start or end of selection
522n/a
523n/a When a left/right cursor key is pressed create and return to Tkinter a
524n/a function which causes a cursor move from the associated edge of the
525n/a selection.
526n/a
527n/a """
528n/a self_text_index = self.text.index
529n/a self_text_mark_set = self.text.mark_set
530n/a edges_table = ("sel.first+1c", "sel.last-1c")
531n/a def move_at_edge(event):
532n/a if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
533n/a try:
534n/a self_text_index("sel.first")
535n/a self_text_mark_set("insert", edges_table[edge_index])
536n/a except TclError:
537n/a pass
538n/a return move_at_edge
539n/a
540n/a def del_word_left(self, event):
541n/a self.text.event_generate('<Meta-Delete>')
542n/a return "break"
543n/a
544n/a def del_word_right(self, event):
545n/a self.text.event_generate('<Meta-d>')
546n/a return "break"
547n/a
548n/a def find_event(self, event):
549n/a search.find(self.text)
550n/a return "break"
551n/a
552n/a def find_again_event(self, event):
553n/a search.find_again(self.text)
554n/a return "break"
555n/a
556n/a def find_selection_event(self, event):
557n/a search.find_selection(self.text)
558n/a return "break"
559n/a
560n/a def find_in_files_event(self, event):
561n/a grep.grep(self.text, self.io, self.flist)
562n/a return "break"
563n/a
564n/a def replace_event(self, event):
565n/a replace.replace(self.text)
566n/a return "break"
567n/a
568n/a def goto_line_event(self, event):
569n/a text = self.text
570n/a lineno = tkSimpleDialog.askinteger("Goto",
571n/a "Go to line number:",parent=text)
572n/a if lineno is None:
573n/a return "break"
574n/a if lineno <= 0:
575n/a text.bell()
576n/a return "break"
577n/a text.mark_set("insert", "%d.0" % lineno)
578n/a text.see("insert")
579n/a
580n/a def open_module(self, event=None):
581n/a """Get module name from user and open it.
582n/a
583n/a Return module path or None for calls by open_class_browser
584n/a when latter is not invoked in named editor window.
585n/a """
586n/a # XXX This, open_class_browser, and open_path_browser
587n/a # would fit better in iomenu.IOBinding.
588n/a try:
589n/a name = self.text.get("sel.first", "sel.last").strip()
590n/a except TclError:
591n/a name = ''
592n/a file_path = query.ModuleName(
593n/a self.text, "Open Module",
594n/a "Enter the name of a Python module\n"
595n/a "to search on sys.path and open:",
596n/a name).result
597n/a if file_path is not None:
598n/a if self.flist:
599n/a self.flist.open(file_path)
600n/a else:
601n/a self.io.loadfile(file_path)
602n/a return file_path
603n/a
604n/a def open_class_browser(self, event=None):
605n/a filename = self.io.filename
606n/a if not (self.__class__.__name__ == 'PyShellEditorWindow'
607n/a and filename):
608n/a filename = self.open_module()
609n/a if filename is None:
610n/a return
611n/a head, tail = os.path.split(filename)
612n/a base, ext = os.path.splitext(tail)
613n/a from idlelib import browser
614n/a browser.ClassBrowser(self.flist, base, [head])
615n/a
616n/a def open_path_browser(self, event=None):
617n/a from idlelib import pathbrowser
618n/a pathbrowser.PathBrowser(self.flist)
619n/a
620n/a def open_turtle_demo(self, event = None):
621n/a import subprocess
622n/a
623n/a cmd = [sys.executable,
624n/a '-c',
625n/a 'from turtledemo.__main__ import main; main()']
626n/a subprocess.Popen(cmd, shell=False)
627n/a
628n/a def gotoline(self, lineno):
629n/a if lineno is not None and lineno > 0:
630n/a self.text.mark_set("insert", "%d.0" % lineno)
631n/a self.text.tag_remove("sel", "1.0", "end")
632n/a self.text.tag_add("sel", "insert", "insert +1l")
633n/a self.center()
634n/a
635n/a def ispythonsource(self, filename):
636n/a if not filename or os.path.isdir(filename):
637n/a return True
638n/a base, ext = os.path.splitext(os.path.basename(filename))
639n/a if os.path.normcase(ext) in (".py", ".pyw"):
640n/a return True
641n/a line = self.text.get('1.0', '1.0 lineend')
642n/a return line.startswith('#!') and 'python' in line
643n/a
644n/a def close_hook(self):
645n/a if self.flist:
646n/a self.flist.unregister_maybe_terminate(self)
647n/a self.flist = None
648n/a
649n/a def set_close_hook(self, close_hook):
650n/a self.close_hook = close_hook
651n/a
652n/a def filename_change_hook(self):
653n/a if self.flist:
654n/a self.flist.filename_changed_edit(self)
655n/a self.saved_change_hook()
656n/a self.top.update_windowlist_registry(self)
657n/a self.ResetColorizer()
658n/a
659n/a def _addcolorizer(self):
660n/a if self.color:
661n/a return
662n/a if self.ispythonsource(self.io.filename):
663n/a self.color = self.ColorDelegator()
664n/a # can add more colorizers here...
665n/a if self.color:
666n/a self.per.removefilter(self.undo)
667n/a self.per.insertfilter(self.color)
668n/a self.per.insertfilter(self.undo)
669n/a
670n/a def _rmcolorizer(self):
671n/a if not self.color:
672n/a return
673n/a self.color.removecolors()
674n/a self.per.removefilter(self.color)
675n/a self.color = None
676n/a
677n/a def ResetColorizer(self):
678n/a "Update the color theme"
679n/a # Called from self.filename_change_hook and from configdialog.py
680n/a self._rmcolorizer()
681n/a self._addcolorizer()
682n/a EditorWindow.color_config(self.text)
683n/a
684n/a IDENTCHARS = string.ascii_letters + string.digits + "_"
685n/a
686n/a def colorize_syntax_error(self, text, pos):
687n/a text.tag_add("ERROR", pos)
688n/a char = text.get(pos)
689n/a if char and char in self.IDENTCHARS:
690n/a text.tag_add("ERROR", pos + " wordstart", pos)
691n/a if '\n' == text.get(pos): # error at line end
692n/a text.mark_set("insert", pos)
693n/a else:
694n/a text.mark_set("insert", pos + "+1c")
695n/a text.see(pos)
696n/a
697n/a def ResetFont(self):
698n/a "Update the text widgets' font if it is changed"
699n/a # Called from configdialog.py
700n/a
701n/a self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
702n/a
703n/a def RemoveKeybindings(self):
704n/a "Remove the keybindings before they are changed."
705n/a # Called from configdialog.py
706n/a self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
707n/a for event, keylist in keydefs.items():
708n/a self.text.event_delete(event, *keylist)
709n/a for extensionName in self.get_standard_extension_names():
710n/a xkeydefs = idleConf.GetExtensionBindings(extensionName)
711n/a if xkeydefs:
712n/a for event, keylist in xkeydefs.items():
713n/a self.text.event_delete(event, *keylist)
714n/a
715n/a def ApplyKeybindings(self):
716n/a "Update the keybindings after they are changed"
717n/a # Called from configdialog.py
718n/a self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
719n/a self.apply_bindings()
720n/a for extensionName in self.get_standard_extension_names():
721n/a xkeydefs = idleConf.GetExtensionBindings(extensionName)
722n/a if xkeydefs:
723n/a self.apply_bindings(xkeydefs)
724n/a #update menu accelerators
725n/a menuEventDict = {}
726n/a for menu in self.mainmenu.menudefs:
727n/a menuEventDict[menu[0]] = {}
728n/a for item in menu[1]:
729n/a if item:
730n/a menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
731n/a for menubarItem in self.menudict:
732n/a menu = self.menudict[menubarItem]
733n/a end = menu.index(END)
734n/a if end is None:
735n/a # Skip empty menus
736n/a continue
737n/a end += 1
738n/a for index in range(0, end):
739n/a if menu.type(index) == 'command':
740n/a accel = menu.entrycget(index, 'accelerator')
741n/a if accel:
742n/a itemName = menu.entrycget(index, 'label')
743n/a event = ''
744n/a if menubarItem in menuEventDict:
745n/a if itemName in menuEventDict[menubarItem]:
746n/a event = menuEventDict[menubarItem][itemName]
747n/a if event:
748n/a accel = get_accelerator(keydefs, event)
749n/a menu.entryconfig(index, accelerator=accel)
750n/a
751n/a def set_notabs_indentwidth(self):
752n/a "Update the indentwidth if changed and not using tabs in this window"
753n/a # Called from configdialog.py
754n/a if not self.usetabs:
755n/a self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
756n/a type='int')
757n/a
758n/a def reset_help_menu_entries(self):
759n/a "Update the additional help entries on the Help menu"
760n/a help_list = idleConf.GetAllExtraHelpSourcesList()
761n/a helpmenu = self.menudict['help']
762n/a # first delete the extra help entries, if any
763n/a helpmenu_length = helpmenu.index(END)
764n/a if helpmenu_length > self.base_helpmenu_length:
765n/a helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
766n/a # then rebuild them
767n/a if help_list:
768n/a helpmenu.add_separator()
769n/a for entry in help_list:
770n/a cmd = self.__extra_help_callback(entry[1])
771n/a helpmenu.add_command(label=entry[0], command=cmd)
772n/a # and update the menu dictionary
773n/a self.menudict['help'] = helpmenu
774n/a
775n/a def __extra_help_callback(self, helpfile):
776n/a "Create a callback with the helpfile value frozen at definition time"
777n/a def display_extra_help(helpfile=helpfile):
778n/a if not helpfile.startswith(('www', 'http')):
779n/a helpfile = os.path.normpath(helpfile)
780n/a if sys.platform[:3] == 'win':
781n/a try:
782n/a os.startfile(helpfile)
783n/a except OSError as why:
784n/a tkMessageBox.showerror(title='Document Start Failure',
785n/a message=str(why), parent=self.text)
786n/a else:
787n/a webbrowser.open(helpfile)
788n/a return display_extra_help
789n/a
790n/a def update_recent_files_list(self, new_file=None):
791n/a "Load and update the recent files list and menus"
792n/a rf_list = []
793n/a if os.path.exists(self.recent_files_path):
794n/a with open(self.recent_files_path, 'r',
795n/a encoding='utf_8', errors='replace') as rf_list_file:
796n/a rf_list = rf_list_file.readlines()
797n/a if new_file:
798n/a new_file = os.path.abspath(new_file) + '\n'
799n/a if new_file in rf_list:
800n/a rf_list.remove(new_file) # move to top
801n/a rf_list.insert(0, new_file)
802n/a # clean and save the recent files list
803n/a bad_paths = []
804n/a for path in rf_list:
805n/a if '\0' in path or not os.path.exists(path[0:-1]):
806n/a bad_paths.append(path)
807n/a rf_list = [path for path in rf_list if path not in bad_paths]
808n/a ulchars = "1234567890ABCDEFGHIJK"
809n/a rf_list = rf_list[0:len(ulchars)]
810n/a try:
811n/a with open(self.recent_files_path, 'w',
812n/a encoding='utf_8', errors='replace') as rf_file:
813n/a rf_file.writelines(rf_list)
814n/a except OSError as err:
815n/a if not getattr(self.root, "recentfilelist_error_displayed", False):
816n/a self.root.recentfilelist_error_displayed = True
817n/a tkMessageBox.showwarning(title='IDLE Warning',
818n/a message="Cannot update File menu Recent Files list. "
819n/a "Your operating system says:\n%s\n"
820n/a "Select OK and IDLE will continue without updating."
821n/a % self._filename_to_unicode(str(err)),
822n/a parent=self.text)
823n/a # for each edit window instance, construct the recent files menu
824n/a for instance in self.top.instance_dict:
825n/a menu = instance.recent_files_menu
826n/a menu.delete(0, END) # clear, and rebuild:
827n/a for i, file_name in enumerate(rf_list):
828n/a file_name = file_name.rstrip() # zap \n
829n/a # make unicode string to display non-ASCII chars correctly
830n/a ufile_name = self._filename_to_unicode(file_name)
831n/a callback = instance.__recent_file_callback(file_name)
832n/a menu.add_command(label=ulchars[i] + " " + ufile_name,
833n/a command=callback,
834n/a underline=0)
835n/a
836n/a def __recent_file_callback(self, file_name):
837n/a def open_recent_file(fn_closure=file_name):
838n/a self.io.open(editFile=fn_closure)
839n/a return open_recent_file
840n/a
841n/a def saved_change_hook(self):
842n/a short = self.short_title()
843n/a long = self.long_title()
844n/a if short and long:
845n/a title = short + " - " + long + _py_version
846n/a elif short:
847n/a title = short
848n/a elif long:
849n/a title = long
850n/a else:
851n/a title = "Untitled"
852n/a icon = short or long or title
853n/a if not self.get_saved():
854n/a title = "*%s*" % title
855n/a icon = "*%s" % icon
856n/a self.top.wm_title(title)
857n/a self.top.wm_iconname(icon)
858n/a
859n/a def get_saved(self):
860n/a return self.undo.get_saved()
861n/a
862n/a def set_saved(self, flag):
863n/a self.undo.set_saved(flag)
864n/a
865n/a def reset_undo(self):
866n/a self.undo.reset_undo()
867n/a
868n/a def short_title(self):
869n/a filename = self.io.filename
870n/a if filename:
871n/a filename = os.path.basename(filename)
872n/a else:
873n/a filename = "Untitled"
874n/a # return unicode string to display non-ASCII chars correctly
875n/a return self._filename_to_unicode(filename)
876n/a
877n/a def long_title(self):
878n/a # return unicode string to display non-ASCII chars correctly
879n/a return self._filename_to_unicode(self.io.filename or "")
880n/a
881n/a def center_insert_event(self, event):
882n/a self.center()
883n/a
884n/a def center(self, mark="insert"):
885n/a text = self.text
886n/a top, bot = self.getwindowlines()
887n/a lineno = self.getlineno(mark)
888n/a height = bot - top
889n/a newtop = max(1, lineno - height//2)
890n/a text.yview(float(newtop))
891n/a
892n/a def getwindowlines(self):
893n/a text = self.text
894n/a top = self.getlineno("@0,0")
895n/a bot = self.getlineno("@0,65535")
896n/a if top == bot and text.winfo_height() == 1:
897n/a # Geometry manager hasn't run yet
898n/a height = int(text['height'])
899n/a bot = top + height - 1
900n/a return top, bot
901n/a
902n/a def getlineno(self, mark="insert"):
903n/a text = self.text
904n/a return int(float(text.index(mark)))
905n/a
906n/a def get_geometry(self):
907n/a "Return (width, height, x, y)"
908n/a geom = self.top.wm_geometry()
909n/a m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
910n/a return list(map(int, m.groups()))
911n/a
912n/a def close_event(self, event):
913n/a self.close()
914n/a
915n/a def maybesave(self):
916n/a if self.io:
917n/a if not self.get_saved():
918n/a if self.top.state()!='normal':
919n/a self.top.deiconify()
920n/a self.top.lower()
921n/a self.top.lift()
922n/a return self.io.maybesave()
923n/a
924n/a def close(self):
925n/a reply = self.maybesave()
926n/a if str(reply) != "cancel":
927n/a self._close()
928n/a return reply
929n/a
930n/a def _close(self):
931n/a if self.io.filename:
932n/a self.update_recent_files_list(new_file=self.io.filename)
933n/a windows.unregister_callback(self.postwindowsmenu)
934n/a self.unload_extensions()
935n/a self.io.close()
936n/a self.io = None
937n/a self.undo = None
938n/a if self.color:
939n/a self.color.close(False)
940n/a self.color = None
941n/a self.text = None
942n/a self.tkinter_vars = None
943n/a self.per.close()
944n/a self.per = None
945n/a self.top.destroy()
946n/a if self.close_hook:
947n/a # unless override: unregister from flist, terminate if last window
948n/a self.close_hook()
949n/a
950n/a def load_extensions(self):
951n/a self.extensions = {}
952n/a self.load_standard_extensions()
953n/a
954n/a def unload_extensions(self):
955n/a for ins in list(self.extensions.values()):
956n/a if hasattr(ins, "close"):
957n/a ins.close()
958n/a self.extensions = {}
959n/a
960n/a def load_standard_extensions(self):
961n/a for name in self.get_standard_extension_names():
962n/a try:
963n/a self.load_extension(name)
964n/a except:
965n/a print("Failed to load extension", repr(name))
966n/a traceback.print_exc()
967n/a
968n/a def get_standard_extension_names(self):
969n/a return idleConf.GetExtensions(editor_only=True)
970n/a
971n/a extfiles = { # map config-extension section names to new file names
972n/a 'AutoComplete': 'autocomplete',
973n/a 'AutoExpand': 'autoexpand',
974n/a 'CallTips': 'calltips',
975n/a 'CodeContext': 'codecontext',
976n/a 'FormatParagraph': 'paragraph',
977n/a 'ParenMatch': 'parenmatch',
978n/a 'RstripExtension': 'rstrip',
979n/a 'ScriptBinding': 'runscript',
980n/a 'ZoomHeight': 'zoomheight',
981n/a }
982n/a
983n/a def load_extension(self, name):
984n/a fname = self.extfiles.get(name, name)
985n/a try:
986n/a try:
987n/a mod = importlib.import_module('.' + fname, package=__package__)
988n/a except (ImportError, TypeError):
989n/a mod = importlib.import_module(fname)
990n/a except ImportError:
991n/a print("\nFailed to import extension: ", name)
992n/a raise
993n/a cls = getattr(mod, name)
994n/a keydefs = idleConf.GetExtensionBindings(name)
995n/a if hasattr(cls, "menudefs"):
996n/a self.fill_menus(cls.menudefs, keydefs)
997n/a ins = cls(self)
998n/a self.extensions[name] = ins
999n/a if keydefs:
1000n/a self.apply_bindings(keydefs)
1001n/a for vevent in keydefs:
1002n/a methodname = vevent.replace("-", "_")
1003n/a while methodname[:1] == '<':
1004n/a methodname = methodname[1:]
1005n/a while methodname[-1:] == '>':
1006n/a methodname = methodname[:-1]
1007n/a methodname = methodname + "_event"
1008n/a if hasattr(ins, methodname):
1009n/a self.text.bind(vevent, getattr(ins, methodname))
1010n/a
1011n/a def apply_bindings(self, keydefs=None):
1012n/a if keydefs is None:
1013n/a keydefs = self.mainmenu.default_keydefs
1014n/a text = self.text
1015n/a text.keydefs = keydefs
1016n/a for event, keylist in keydefs.items():
1017n/a if keylist:
1018n/a text.event_add(event, *keylist)
1019n/a
1020n/a def fill_menus(self, menudefs=None, keydefs=None):
1021n/a """Add appropriate entries to the menus and submenus
1022n/a
1023n/a Menus that are absent or None in self.menudict are ignored.
1024n/a """
1025n/a if menudefs is None:
1026n/a menudefs = self.mainmenu.menudefs
1027n/a if keydefs is None:
1028n/a keydefs = self.mainmenu.default_keydefs
1029n/a menudict = self.menudict
1030n/a text = self.text
1031n/a for mname, entrylist in menudefs:
1032n/a menu = menudict.get(mname)
1033n/a if not menu:
1034n/a continue
1035n/a for entry in entrylist:
1036n/a if not entry:
1037n/a menu.add_separator()
1038n/a else:
1039n/a label, eventname = entry
1040n/a checkbutton = (label[:1] == '!')
1041n/a if checkbutton:
1042n/a label = label[1:]
1043n/a underline, label = prepstr(label)
1044n/a accelerator = get_accelerator(keydefs, eventname)
1045n/a def command(text=text, eventname=eventname):
1046n/a text.event_generate(eventname)
1047n/a if checkbutton:
1048n/a var = self.get_var_obj(eventname, BooleanVar)
1049n/a menu.add_checkbutton(label=label, underline=underline,
1050n/a command=command, accelerator=accelerator,
1051n/a variable=var)
1052n/a else:
1053n/a menu.add_command(label=label, underline=underline,
1054n/a command=command,
1055n/a accelerator=accelerator)
1056n/a
1057n/a def getvar(self, name):
1058n/a var = self.get_var_obj(name)
1059n/a if var:
1060n/a value = var.get()
1061n/a return value
1062n/a else:
1063n/a raise NameError(name)
1064n/a
1065n/a def setvar(self, name, value, vartype=None):
1066n/a var = self.get_var_obj(name, vartype)
1067n/a if var:
1068n/a var.set(value)
1069n/a else:
1070n/a raise NameError(name)
1071n/a
1072n/a def get_var_obj(self, name, vartype=None):
1073n/a var = self.tkinter_vars.get(name)
1074n/a if not var and vartype:
1075n/a # create a Tkinter variable object with self.text as master:
1076n/a self.tkinter_vars[name] = var = vartype(self.text)
1077n/a return var
1078n/a
1079n/a # Tk implementations of "virtual text methods" -- each platform
1080n/a # reusing IDLE's support code needs to define these for its GUI's
1081n/a # flavor of widget.
1082n/a
1083n/a # Is character at text_index in a Python string? Return 0 for
1084n/a # "guaranteed no", true for anything else. This info is expensive
1085n/a # to compute ab initio, but is probably already known by the
1086n/a # platform's colorizer.
1087n/a
1088n/a def is_char_in_string(self, text_index):
1089n/a if self.color:
1090n/a # Return true iff colorizer hasn't (re)gotten this far
1091n/a # yet, or the character is tagged as being in a string
1092n/a return self.text.tag_prevrange("TODO", text_index) or \
1093n/a "STRING" in self.text.tag_names(text_index)
1094n/a else:
1095n/a # The colorizer is missing: assume the worst
1096n/a return 1
1097n/a
1098n/a # If a selection is defined in the text widget, return (start,
1099n/a # end) as Tkinter text indices, otherwise return (None, None)
1100n/a def get_selection_indices(self):
1101n/a try:
1102n/a first = self.text.index("sel.first")
1103n/a last = self.text.index("sel.last")
1104n/a return first, last
1105n/a except TclError:
1106n/a return None, None
1107n/a
1108n/a # Return the text widget's current view of what a tab stop means
1109n/a # (equivalent width in spaces).
1110n/a
1111n/a def get_tk_tabwidth(self):
1112n/a current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1113n/a return int(current)
1114n/a
1115n/a # Set the text widget's current view of what a tab stop means.
1116n/a
1117n/a def set_tk_tabwidth(self, newtabwidth):
1118n/a text = self.text
1119n/a if self.get_tk_tabwidth() != newtabwidth:
1120n/a # Set text widget tab width
1121n/a pixels = text.tk.call("font", "measure", text["font"],
1122n/a "-displayof", text.master,
1123n/a "n" * newtabwidth)
1124n/a text.configure(tabs=pixels)
1125n/a
1126n/a### begin autoindent code ### (configuration was moved to beginning of class)
1127n/a
1128n/a def set_indentation_params(self, is_py_src, guess=True):
1129n/a if is_py_src and guess:
1130n/a i = self.guess_indent()
1131n/a if 2 <= i <= 8:
1132n/a self.indentwidth = i
1133n/a if self.indentwidth != self.tabwidth:
1134n/a self.usetabs = False
1135n/a self.set_tk_tabwidth(self.tabwidth)
1136n/a
1137n/a def smart_backspace_event(self, event):
1138n/a text = self.text
1139n/a first, last = self.get_selection_indices()
1140n/a if first and last:
1141n/a text.delete(first, last)
1142n/a text.mark_set("insert", first)
1143n/a return "break"
1144n/a # Delete whitespace left, until hitting a real char or closest
1145n/a # preceding virtual tab stop.
1146n/a chars = text.get("insert linestart", "insert")
1147n/a if chars == '':
1148n/a if text.compare("insert", ">", "1.0"):
1149n/a # easy: delete preceding newline
1150n/a text.delete("insert-1c")
1151n/a else:
1152n/a text.bell() # at start of buffer
1153n/a return "break"
1154n/a if chars[-1] not in " \t":
1155n/a # easy: delete preceding real char
1156n/a text.delete("insert-1c")
1157n/a return "break"
1158n/a # Ick. It may require *inserting* spaces if we back up over a
1159n/a # tab character! This is written to be clear, not fast.
1160n/a tabwidth = self.tabwidth
1161n/a have = len(chars.expandtabs(tabwidth))
1162n/a assert have > 0
1163n/a want = ((have - 1) // self.indentwidth) * self.indentwidth
1164n/a # Debug prompt is multilined....
1165n/a if self.context_use_ps1:
1166n/a last_line_of_prompt = sys.ps1.split('\n')[-1]
1167n/a else:
1168n/a last_line_of_prompt = ''
1169n/a ncharsdeleted = 0
1170n/a while 1:
1171n/a if chars == last_line_of_prompt:
1172n/a break
1173n/a chars = chars[:-1]
1174n/a ncharsdeleted = ncharsdeleted + 1
1175n/a have = len(chars.expandtabs(tabwidth))
1176n/a if have <= want or chars[-1] not in " \t":
1177n/a break
1178n/a text.undo_block_start()
1179n/a text.delete("insert-%dc" % ncharsdeleted, "insert")
1180n/a if have < want:
1181n/a text.insert("insert", ' ' * (want - have))
1182n/a text.undo_block_stop()
1183n/a return "break"
1184n/a
1185n/a def smart_indent_event(self, event):
1186n/a # if intraline selection:
1187n/a # delete it
1188n/a # elif multiline selection:
1189n/a # do indent-region
1190n/a # else:
1191n/a # indent one level
1192n/a text = self.text
1193n/a first, last = self.get_selection_indices()
1194n/a text.undo_block_start()
1195n/a try:
1196n/a if first and last:
1197n/a if index2line(first) != index2line(last):
1198n/a return self.indent_region_event(event)
1199n/a text.delete(first, last)
1200n/a text.mark_set("insert", first)
1201n/a prefix = text.get("insert linestart", "insert")
1202n/a raw, effective = classifyws(prefix, self.tabwidth)
1203n/a if raw == len(prefix):
1204n/a # only whitespace to the left
1205n/a self.reindent_to(effective + self.indentwidth)
1206n/a else:
1207n/a # tab to the next 'stop' within or to right of line's text:
1208n/a if self.usetabs:
1209n/a pad = '\t'
1210n/a else:
1211n/a effective = len(prefix.expandtabs(self.tabwidth))
1212n/a n = self.indentwidth
1213n/a pad = ' ' * (n - effective % n)
1214n/a text.insert("insert", pad)
1215n/a text.see("insert")
1216n/a return "break"
1217n/a finally:
1218n/a text.undo_block_stop()
1219n/a
1220n/a def newline_and_indent_event(self, event):
1221n/a text = self.text
1222n/a first, last = self.get_selection_indices()
1223n/a text.undo_block_start()
1224n/a try:
1225n/a if first and last:
1226n/a text.delete(first, last)
1227n/a text.mark_set("insert", first)
1228n/a line = text.get("insert linestart", "insert")
1229n/a i, n = 0, len(line)
1230n/a while i < n and line[i] in " \t":
1231n/a i = i+1
1232n/a if i == n:
1233n/a # the cursor is in or at leading indentation in a continuation
1234n/a # line; just inject an empty line at the start
1235n/a text.insert("insert linestart", '\n')
1236n/a return "break"
1237n/a indent = line[:i]
1238n/a # strip whitespace before insert point unless it's in the prompt
1239n/a i = 0
1240n/a last_line_of_prompt = sys.ps1.split('\n')[-1]
1241n/a while line and line[-1] in " \t" and line != last_line_of_prompt:
1242n/a line = line[:-1]
1243n/a i = i+1
1244n/a if i:
1245n/a text.delete("insert - %d chars" % i, "insert")
1246n/a # strip whitespace after insert point
1247n/a while text.get("insert") in " \t":
1248n/a text.delete("insert")
1249n/a # start new line
1250n/a text.insert("insert", '\n')
1251n/a
1252n/a # adjust indentation for continuations and block
1253n/a # open/close first need to find the last stmt
1254n/a lno = index2line(text.index('insert'))
1255n/a y = pyparse.Parser(self.indentwidth, self.tabwidth)
1256n/a if not self.context_use_ps1:
1257n/a for context in self.num_context_lines:
1258n/a startat = max(lno - context, 1)
1259n/a startatindex = repr(startat) + ".0"
1260n/a rawtext = text.get(startatindex, "insert")
1261n/a y.set_str(rawtext)
1262n/a bod = y.find_good_parse_start(
1263n/a self.context_use_ps1,
1264n/a self._build_char_in_string_func(startatindex))
1265n/a if bod is not None or startat == 1:
1266n/a break
1267n/a y.set_lo(bod or 0)
1268n/a else:
1269n/a r = text.tag_prevrange("console", "insert")
1270n/a if r:
1271n/a startatindex = r[1]
1272n/a else:
1273n/a startatindex = "1.0"
1274n/a rawtext = text.get(startatindex, "insert")
1275n/a y.set_str(rawtext)
1276n/a y.set_lo(0)
1277n/a
1278n/a c = y.get_continuation_type()
1279n/a if c != pyparse.C_NONE:
1280n/a # The current stmt hasn't ended yet.
1281n/a if c == pyparse.C_STRING_FIRST_LINE:
1282n/a # after the first line of a string; do not indent at all
1283n/a pass
1284n/a elif c == pyparse.C_STRING_NEXT_LINES:
1285n/a # inside a string which started before this line;
1286n/a # just mimic the current indent
1287n/a text.insert("insert", indent)
1288n/a elif c == pyparse.C_BRACKET:
1289n/a # line up with the first (if any) element of the
1290n/a # last open bracket structure; else indent one
1291n/a # level beyond the indent of the line with the
1292n/a # last open bracket
1293n/a self.reindent_to(y.compute_bracket_indent())
1294n/a elif c == pyparse.C_BACKSLASH:
1295n/a # if more than one line in this stmt already, just
1296n/a # mimic the current indent; else if initial line
1297n/a # has a start on an assignment stmt, indent to
1298n/a # beyond leftmost =; else to beyond first chunk of
1299n/a # non-whitespace on initial line
1300n/a if y.get_num_lines_in_stmt() > 1:
1301n/a text.insert("insert", indent)
1302n/a else:
1303n/a self.reindent_to(y.compute_backslash_indent())
1304n/a else:
1305n/a assert 0, "bogus continuation type %r" % (c,)
1306n/a return "break"
1307n/a
1308n/a # This line starts a brand new stmt; indent relative to
1309n/a # indentation of initial line of closest preceding
1310n/a # interesting stmt.
1311n/a indent = y.get_base_indent_string()
1312n/a text.insert("insert", indent)
1313n/a if y.is_block_opener():
1314n/a self.smart_indent_event(event)
1315n/a elif indent and y.is_block_closer():
1316n/a self.smart_backspace_event(event)
1317n/a return "break"
1318n/a finally:
1319n/a text.see("insert")
1320n/a text.undo_block_stop()
1321n/a
1322n/a # Our editwin provides an is_char_in_string function that works
1323n/a # with a Tk text index, but PyParse only knows about offsets into
1324n/a # a string. This builds a function for PyParse that accepts an
1325n/a # offset.
1326n/a
1327n/a def _build_char_in_string_func(self, startindex):
1328n/a def inner(offset, _startindex=startindex,
1329n/a _icis=self.is_char_in_string):
1330n/a return _icis(_startindex + "+%dc" % offset)
1331n/a return inner
1332n/a
1333n/a def indent_region_event(self, event):
1334n/a head, tail, chars, lines = self.get_region()
1335n/a for pos in range(len(lines)):
1336n/a line = lines[pos]
1337n/a if line:
1338n/a raw, effective = classifyws(line, self.tabwidth)
1339n/a effective = effective + self.indentwidth
1340n/a lines[pos] = self._make_blanks(effective) + line[raw:]
1341n/a self.set_region(head, tail, chars, lines)
1342n/a return "break"
1343n/a
1344n/a def dedent_region_event(self, event):
1345n/a head, tail, chars, lines = self.get_region()
1346n/a for pos in range(len(lines)):
1347n/a line = lines[pos]
1348n/a if line:
1349n/a raw, effective = classifyws(line, self.tabwidth)
1350n/a effective = max(effective - self.indentwidth, 0)
1351n/a lines[pos] = self._make_blanks(effective) + line[raw:]
1352n/a self.set_region(head, tail, chars, lines)
1353n/a return "break"
1354n/a
1355n/a def comment_region_event(self, event):
1356n/a head, tail, chars, lines = self.get_region()
1357n/a for pos in range(len(lines) - 1):
1358n/a line = lines[pos]
1359n/a lines[pos] = '##' + line
1360n/a self.set_region(head, tail, chars, lines)
1361n/a
1362n/a def uncomment_region_event(self, event):
1363n/a head, tail, chars, lines = self.get_region()
1364n/a for pos in range(len(lines)):
1365n/a line = lines[pos]
1366n/a if not line:
1367n/a continue
1368n/a if line[:2] == '##':
1369n/a line = line[2:]
1370n/a elif line[:1] == '#':
1371n/a line = line[1:]
1372n/a lines[pos] = line
1373n/a self.set_region(head, tail, chars, lines)
1374n/a
1375n/a def tabify_region_event(self, event):
1376n/a head, tail, chars, lines = self.get_region()
1377n/a tabwidth = self._asktabwidth()
1378n/a if tabwidth is None: return
1379n/a for pos in range(len(lines)):
1380n/a line = lines[pos]
1381n/a if line:
1382n/a raw, effective = classifyws(line, tabwidth)
1383n/a ntabs, nspaces = divmod(effective, tabwidth)
1384n/a lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1385n/a self.set_region(head, tail, chars, lines)
1386n/a
1387n/a def untabify_region_event(self, event):
1388n/a head, tail, chars, lines = self.get_region()
1389n/a tabwidth = self._asktabwidth()
1390n/a if tabwidth is None: return
1391n/a for pos in range(len(lines)):
1392n/a lines[pos] = lines[pos].expandtabs(tabwidth)
1393n/a self.set_region(head, tail, chars, lines)
1394n/a
1395n/a def toggle_tabs_event(self, event):
1396n/a if self.askyesno(
1397n/a "Toggle tabs",
1398n/a "Turn tabs " + ("on", "off")[self.usetabs] +
1399n/a "?\nIndent width " +
1400n/a ("will be", "remains at")[self.usetabs] + " 8." +
1401n/a "\n Note: a tab is always 8 columns",
1402n/a parent=self.text):
1403n/a self.usetabs = not self.usetabs
1404n/a # Try to prevent inconsistent indentation.
1405n/a # User must change indent width manually after using tabs.
1406n/a self.indentwidth = 8
1407n/a return "break"
1408n/a
1409n/a # XXX this isn't bound to anything -- see tabwidth comments
1410n/a## def change_tabwidth_event(self, event):
1411n/a## new = self._asktabwidth()
1412n/a## if new != self.tabwidth:
1413n/a## self.tabwidth = new
1414n/a## self.set_indentation_params(0, guess=0)
1415n/a## return "break"
1416n/a
1417n/a def change_indentwidth_event(self, event):
1418n/a new = self.askinteger(
1419n/a "Indent width",
1420n/a "New indent width (2-16)\n(Always use 8 when using tabs)",
1421n/a parent=self.text,
1422n/a initialvalue=self.indentwidth,
1423n/a minvalue=2,
1424n/a maxvalue=16)
1425n/a if new and new != self.indentwidth and not self.usetabs:
1426n/a self.indentwidth = new
1427n/a return "break"
1428n/a
1429n/a def get_region(self):
1430n/a text = self.text
1431n/a first, last = self.get_selection_indices()
1432n/a if first and last:
1433n/a head = text.index(first + " linestart")
1434n/a tail = text.index(last + "-1c lineend +1c")
1435n/a else:
1436n/a head = text.index("insert linestart")
1437n/a tail = text.index("insert lineend +1c")
1438n/a chars = text.get(head, tail)
1439n/a lines = chars.split("\n")
1440n/a return head, tail, chars, lines
1441n/a
1442n/a def set_region(self, head, tail, chars, lines):
1443n/a text = self.text
1444n/a newchars = "\n".join(lines)
1445n/a if newchars == chars:
1446n/a text.bell()
1447n/a return
1448n/a text.tag_remove("sel", "1.0", "end")
1449n/a text.mark_set("insert", head)
1450n/a text.undo_block_start()
1451n/a text.delete(head, tail)
1452n/a text.insert(head, newchars)
1453n/a text.undo_block_stop()
1454n/a text.tag_add("sel", head, "insert")
1455n/a
1456n/a # Make string that displays as n leading blanks.
1457n/a
1458n/a def _make_blanks(self, n):
1459n/a if self.usetabs:
1460n/a ntabs, nspaces = divmod(n, self.tabwidth)
1461n/a return '\t' * ntabs + ' ' * nspaces
1462n/a else:
1463n/a return ' ' * n
1464n/a
1465n/a # Delete from beginning of line to insert point, then reinsert
1466n/a # column logical (meaning use tabs if appropriate) spaces.
1467n/a
1468n/a def reindent_to(self, column):
1469n/a text = self.text
1470n/a text.undo_block_start()
1471n/a if text.compare("insert linestart", "!=", "insert"):
1472n/a text.delete("insert linestart", "insert")
1473n/a if column:
1474n/a text.insert("insert", self._make_blanks(column))
1475n/a text.undo_block_stop()
1476n/a
1477n/a def _asktabwidth(self):
1478n/a return self.askinteger(
1479n/a "Tab width",
1480n/a "Columns per tab? (2-16)",
1481n/a parent=self.text,
1482n/a initialvalue=self.indentwidth,
1483n/a minvalue=2,
1484n/a maxvalue=16)
1485n/a
1486n/a # Guess indentwidth from text content.
1487n/a # Return guessed indentwidth. This should not be believed unless
1488n/a # it's in a reasonable range (e.g., it will be 0 if no indented
1489n/a # blocks are found).
1490n/a
1491n/a def guess_indent(self):
1492n/a opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1493n/a if opener and indented:
1494n/a raw, indentsmall = classifyws(opener, self.tabwidth)
1495n/a raw, indentlarge = classifyws(indented, self.tabwidth)
1496n/a else:
1497n/a indentsmall = indentlarge = 0
1498n/a return indentlarge - indentsmall
1499n/a
1500n/a# "line.col" -> line, as an int
1501n/adef index2line(index):
1502n/a return int(float(index))
1503n/a
1504n/a# Look at the leading whitespace in s.
1505n/a# Return pair (# of leading ws characters,
1506n/a# effective # of leading blanks after expanding
1507n/a# tabs to width tabwidth)
1508n/a
1509n/adef classifyws(s, tabwidth):
1510n/a raw = effective = 0
1511n/a for ch in s:
1512n/a if ch == ' ':
1513n/a raw = raw + 1
1514n/a effective = effective + 1
1515n/a elif ch == '\t':
1516n/a raw = raw + 1
1517n/a effective = (effective // tabwidth + 1) * tabwidth
1518n/a else:
1519n/a break
1520n/a return raw, effective
1521n/a
1522n/a
1523n/aclass IndentSearcher(object):
1524n/a
1525n/a # .run() chews over the Text widget, looking for a block opener
1526n/a # and the stmt following it. Returns a pair,
1527n/a # (line containing block opener, line containing stmt)
1528n/a # Either or both may be None.
1529n/a
1530n/a def __init__(self, text, tabwidth):
1531n/a self.text = text
1532n/a self.tabwidth = tabwidth
1533n/a self.i = self.finished = 0
1534n/a self.blkopenline = self.indentedline = None
1535n/a
1536n/a def readline(self):
1537n/a if self.finished:
1538n/a return ""
1539n/a i = self.i = self.i + 1
1540n/a mark = repr(i) + ".0"
1541n/a if self.text.compare(mark, ">=", "end"):
1542n/a return ""
1543n/a return self.text.get(mark, mark + " lineend+1c")
1544n/a
1545n/a def tokeneater(self, type, token, start, end, line,
1546n/a INDENT=tokenize.INDENT,
1547n/a NAME=tokenize.NAME,
1548n/a OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1549n/a if self.finished:
1550n/a pass
1551n/a elif type == NAME and token in OPENERS:
1552n/a self.blkopenline = line
1553n/a elif type == INDENT and self.blkopenline:
1554n/a self.indentedline = line
1555n/a self.finished = 1
1556n/a
1557n/a def run(self):
1558n/a save_tabsize = tokenize.tabsize
1559n/a tokenize.tabsize = self.tabwidth
1560n/a try:
1561n/a try:
1562n/a tokens = tokenize.generate_tokens(self.readline)
1563n/a for token in tokens:
1564n/a self.tokeneater(*token)
1565n/a except (tokenize.TokenError, SyntaxError):
1566n/a # since we cut off the tokenizer early, we can trigger
1567n/a # spurious errors
1568n/a pass
1569n/a finally:
1570n/a tokenize.tabsize = save_tabsize
1571n/a return self.blkopenline, self.indentedline
1572n/a
1573n/a### end autoindent code ###
1574n/a
1575n/adef prepstr(s):
1576n/a # Helper to extract the underscore from a string, e.g.
1577n/a # prepstr("Co_py") returns (2, "Copy").
1578n/a i = s.find('_')
1579n/a if i >= 0:
1580n/a s = s[:i] + s[i+1:]
1581n/a return i, s
1582n/a
1583n/a
1584n/akeynames = {
1585n/a 'bracketleft': '[',
1586n/a 'bracketright': ']',
1587n/a 'slash': '/',
1588n/a}
1589n/a
1590n/adef get_accelerator(keydefs, eventname):
1591n/a keylist = keydefs.get(eventname)
1592n/a # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
1593n/a # if not keylist:
1594n/a if (not keylist) or (macosx.isCocoaTk() and eventname in {
1595n/a "<<open-module>>",
1596n/a "<<goto-line>>",
1597n/a "<<change-indentwidth>>"}):
1598n/a return ""
1599n/a s = keylist[0]
1600n/a s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
1601n/a s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1602n/a s = re.sub("Key-", "", s)
1603n/a s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1604n/a s = re.sub("Control-", "Ctrl-", s)
1605n/a s = re.sub("-", "+", s)
1606n/a s = re.sub("><", " ", s)
1607n/a s = re.sub("<", "", s)
1608n/a s = re.sub(">", "", s)
1609n/a return s
1610n/a
1611n/a
1612n/adef fixwordbreaks(root):
1613n/a # Make sure that Tk's double-click and next/previous word
1614n/a # operations use our definition of a word (i.e. an identifier)
1615n/a tk = root.tk
1616n/a tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1617n/a tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1618n/a tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1619n/a
1620n/a
1621n/adef _editor_window(parent): # htest #
1622n/a # error if close master window first - timer event, after script
1623n/a root = parent
1624n/a fixwordbreaks(root)
1625n/a if sys.argv[1:]:
1626n/a filename = sys.argv[1]
1627n/a else:
1628n/a filename = None
1629n/a macosx.setupApp(root, None)
1630n/a edit = EditorWindow(root=root, filename=filename)
1631n/a edit.text.bind("<<close-all-windows>>", edit.close_event)
1632n/a # Does not stop error, neither does following
1633n/a # edit.text.bind("<<close-window>>", edit.close_event)
1634n/a
1635n/aif __name__ == '__main__':
1636n/a import unittest
1637n/a unittest.main('idlelib.idle_test.test_editor', verbosity=2, exit=False)
1638n/a
1639n/a from idlelib.idle_test.htest import run
1640n/a run(_editor_window)