diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index ab4f1a37c168c14..4a079d93564ca8e 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -27,6 +27,7 @@ from idlelib import replace from idlelib import search from idlelib import windows +from idlelib import sidebar # The default tab setting for a Text widget, in average-width characters. TK_TABWIDTH_DEFAULT = 8 @@ -57,7 +58,7 @@ class EditorWindow(object): filesystemencoding = sys.getfilesystemencoding() # for file names help_url = None - def __init__(self, flist=None, filename=None, key=None, root=None): + def __init__(self, flist=None, filename=None, key=None, root=None, has_lineno=False): if EditorWindow.help_url is None: dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html') if sys.platform.count('linux'): @@ -120,7 +121,8 @@ def __init__(self, flist=None, filename=None, key=None, root=None): 'height': idleConf.GetOption( 'main', 'EditorWindow', 'height', type='int'), } - self.text = text = MultiCallCreator(Text)(text_frame, **text_options) + + self.text = text = MultiCallCreator(sidebar.SidebarText)(text_frame, **text_options) self.top.focused_widget = self.text self.createmenubar() @@ -190,6 +192,14 @@ def __init__(self, flist=None, filename=None, key=None, root=None): text['yscrollcommand'] = vbar.set text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow') text_frame.pack(side=LEFT, fill=BOTH, expand=1) + + if has_lineno: + self.sidebar = sidebar.LineNumberSidebar(text_frame, width=48) + self.sidebar.attach(text) + self.sidebar.pack(side=LEFT, fill=Y) + self.text.bind('<>', self.set_sidebar) + self.text.bind('', self.set_sidebar) + text.pack(side=TOP, fill=BOTH, expand=1) text.focus_set() @@ -355,6 +365,9 @@ def set_line_and_column(self, event=None): self.status_bar.set_label('column', 'Col: %s' % column) self.status_bar.set_label('line', 'Ln: %s' % line) + def set_sidebar(self, event=None): + self.sidebar.redraw() + menu_specs = [ ("file", "_File"), ("edit", "_Edit"), diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index 5b0e5b267642ab2..762dd48d4a28c3b 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -112,7 +112,7 @@ class PyShellEditorWindow(EditorWindow): def __init__(self, *args): self.breakpoints = [] - EditorWindow.__init__(self, *args) + EditorWindow.__init__(self, has_lineno=True, *args) self.text.bind("<>", self.set_breakpoint_here) self.text.bind("<>", self.clear_breakpoint_here) self.text.bind("<>", self.flist.open_shell) diff --git a/Lib/idlelib/sidebar.py b/Lib/idlelib/sidebar.py new file mode 100644 index 000000000000000..244991d86c0e3bc --- /dev/null +++ b/Lib/idlelib/sidebar.py @@ -0,0 +1,71 @@ +'''Provide line number sidebar to editor + +Inspire by Bryan Oakley, on stackflow: https://stackoverflow.com/a/16375233 +''' + +import tkinter as tk + + +class LineNumberSidebar(tk.Canvas): + def __init__(self, *args, **kwargs): + tk.Canvas.__init__(self, highlightthickness=0, background='grey93', *args, **kwargs) + + def attach(self, text_widget): + self.text = text_widget + + def redraw(self, *args): + '''redraw line numbers''' + self.delete('all') + + # Get cursor lineno + curline, column = self.text.index(tk.INSERT).split('.') + + # Redraw from top + i = self.text.index('@0,0') + while True: + dline = self.text.dlineinfo(i) + if dline is None: + break + + lineno = i.split('.')[0] + text = self.create_text(45, dline[1], anchor='ne', text=lineno) + + # Highlight cursor line number with white rectangle + if lineno == curline: + n, e, s, w = self.bbox(text) + r = self.create_rectangle((1, e, s + 3, w), fill='white', outline='white') + self.tag_lower(r, text) + + # Update index + i = self.text.index('%s + 1line' % i) + + +class SidebarText(tk.Text): + def __init__(self, *args, **kwargs): + tk.Text.__init__(self, *args, **kwargs) + + self.tk.eval(''' + proc widget_proxy {widget widget_command args} { + + # call the real tk widget command with the real args + set result [uplevel [linsert $args 0 $widget_command]] + + # generate the event for certain types of commands + if {([lindex $args 0] in {insert replace delete}) || + ([lrange $args 0 2] == {mark set insert}) || + ([lrange $args 0 1] == {xview moveto}) || + ([lrange $args 0 1] == {xview scroll}) || + ([lrange $args 0 1] == {yview moveto}) || + ([lrange $args 0 1] == {yview scroll})} { + + event generate $widget <> -when tail + } + + # return the result from the real widget command + return $result + } + ''') + self.tk.eval(''' + rename {widget} _{widget} + interp alias {{}} ::{widget} {{}} widget_proxy {widget} _{widget} + '''.format(widget=str(self)))