From 698ab602db9395e1d97e1bd2e85718f79e255aa9 Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Wed, 31 May 2017 15:52:12 +0800 Subject: [PATCH 1/5] bpo-30521: IDLE: Add navigate bar and replace current goto dialog --- Lib/idlelib/editor.py | 17 ++-- Lib/idlelib/navigatebar.py | 154 +++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 10 deletions(-) create mode 100644 Lib/idlelib/navigatebar.py diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index ab4f1a37c168c14..6c6d864a6818cd0 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -271,6 +271,9 @@ def __init__(self, flist=None, filename=None, key=None, root=None): self.askinteger = tkSimpleDialog.askinteger self.showerror = tkMessageBox.showerror + # Navigate bar + self.navigatebar = None + def _filename_to_unicode(self, filename): """Return filename as BMP unicode so diplayable in Tk.""" # Decode bytes to unicode. @@ -565,16 +568,10 @@ def replace_event(self, event): return "break" def goto_line_event(self, event): - text = self.text - lineno = tkSimpleDialog.askinteger("Goto", - "Go to line number:",parent=text) - if lineno is None: - return "break" - if lineno <= 0: - text.bell() - return "break" - text.mark_set("insert", "%d.0" % lineno) - text.see("insert") + from idlelib.singlebar import NavigateBar + if not self.navigatebar: + self.navigatebar = NavigateBar(self, self.text) + self.navigatebar.set_goto_line_mode() def open_module(self, event=None): """Get module name from user and open it. diff --git a/Lib/idlelib/navigatebar.py b/Lib/idlelib/navigatebar.py new file mode 100644 index 000000000000000..4aafb56f50e71da --- /dev/null +++ b/Lib/idlelib/navigatebar.py @@ -0,0 +1,154 @@ +"""Navigate bar +""" + +from tkinter import Listbox, Scrollbar, X, Y, END, VERTICAL, RIGHT +from tkinter.ttk import Frame, Entry + + +class NavigateBar(Frame): + """Navigate bar for IDLE. + + Prefix ":" stand for goto lineno + Prefix "@" stand for goto symbol + """ + + def __init__(self, editor, parent): + super().__init__(parent, borderwidth=5) + self.editor = editor + self.parent = parent # Should be text + self.create_widgets() + self.update_idletasks() + self.place(x=parent.winfo_width() / 2 - self.winfo_reqwidth() / 2, y=-2) + self.selection = 0 + self.symbol_line = {} + + def create_widgets(self): + self.parent.bind('', self.winconfig_event) + self.entry = Entry(self, width=50) + self.entry.bind('', self.key_up_event) + self.entry.bind('', self.key_down_event) + self.entry.bind('', self.navigate) + self.entry.bind('', self.ok) + self.entry.focus_set() + self.entry.pack() + + self.scrollbar = Scrollbar(self, orient=VERTICAL) + self.listbox = Listbox(self, yscrollcommand=self.scrollbar.set) + self.scrollbar.config(command=self.listbox.yview) + self.listbox.bind('', self.listselect_event) + self.listbox.bind('', self.key_up_event) + self.listbox.bind('', self.key_down_event) + + self.entry.bind('', self.cancel) + self.parent.bind('', self.cancel) + + def get_entry(self): + entry = self.entry.get().strip() + if not entry: + return '' + return entry + + def ok(self, event=None): + if self.listbox.size(): + self.goto_selection() + self.clear_highlight() + self.editor.navigatebar = None + self.parent.focus_set() + self.destroy() + + def cancel(self, event=None): + self.clear_highlight() + self.editor.navigatebar = None + self.parent.focus_set() + self.destroy() + + def winconfig_event(self, event): + self.update_idletasks() + self.place(x=self.parent.winfo_width() / 2 - self.winfo_reqwidth() / 2, y=-2) + + def listselect_event(self, event=None): + self.goto_selection() + + def key_up_event(self, event=None): + if self.selection > 0: + self.listbox.selection_clear(self.selection) + self.selection -= 1 + self.listbox.select_set(self.selection) + self.listbox.see(self.selection) + + self.goto_selection() + + def key_down_event(self, event=None): + if self.selection < self.listbox.size() - 1: + self.listbox.selection_clear(self.selection) + self.selection += 1 + self.listbox.select_set(self.selection) + self.listbox.see(self.selection) + + self.goto_selection() + + def set_goto_line_mode(self): + self.entry.insert(0, ':') + + def set_goto_symbol_mode(self): + self.entry.insert(0, '@') + + def goto_selection(self, event=None): + if self.listbox.size(): + select = self.listbox.selection_get() + self.goto_lineno(self.symbol_line[select]) + + def goto_lineno(self, lineno): + self.clear_highlight() + self.parent.mark_set('insert', '%d.0' % (lineno)) + self.parent.see('insert') + self.editor.set_line_and_column() + + # Add highlight to target lineno + self.parent.tag_add('BREAK', '%d.0' % lineno, '%d.0' % (lineno + 1)) + + def navigate(self, event=None): + self.clear_listbox() + self.clear_highlight() + + entry = self.get_entry() + if entry.startswith(':'): + try: + lineno = int(entry[1:]) + except ValueError: + return + self.goto_lineno(lineno) + elif entry.startswith('@'): + self.update_symbol(entry[1:]) + + def clear_listbox(self): + """Show off listbox and clear listbox items + """ + self.scrollbar.pack_forget() + self.listbox.pack_forget() + self.listbox.delete(0, END) + + def clear_highlight(self): + self.parent.tag_remove("BREAK", "insert linestart", + "insert lineend +1char") + + def reset_listbox(self): + """Show out listbox and reset selection + """ + self.scrollbar.pack(side=RIGHT, fill=Y) + self.listbox.pack(fill=X) + self.listbox.delete(0, END) + self.selection = 0 + + def update_symbol(self, entry): + self.reset_listbox() + + texts = self.parent.get('1.0', 'end') + for index, text in enumerate(texts.split('\n')): + dedent_text = text.strip() + if dedent_text.startswith('class ') or dedent_text.startswith('def '): + if entry and not dedent_text.strip('class ').strip('def ').startswith(entry): + continue + self.listbox.insert(END, text) + self.symbol_line[text] = index + 1 + self.listbox.select_set(self.selection) From a7b45cdcd687f6af4a4b031e319e59bcafeec737 Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Tue, 6 Jun 2017 11:51:48 +0800 Subject: [PATCH 2/5] Fix import --- Lib/idlelib/editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 6c6d864a6818cd0..6939179cb4ee119 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -568,7 +568,7 @@ def replace_event(self, event): return "break" def goto_line_event(self, event): - from idlelib.singlebar import NavigateBar + from idlelib.navigatebar import NavigateBar if not self.navigatebar: self.navigatebar = NavigateBar(self, self.text) self.navigatebar.set_goto_line_mode() From 76e9e422fdaf38dd1fdfb793583c03e8e862eb9d Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Tue, 6 Jun 2017 11:52:14 +0800 Subject: [PATCH 3/5] Improve navigate bar label --- Lib/idlelib/navigatebar.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Lib/idlelib/navigatebar.py b/Lib/idlelib/navigatebar.py index 4aafb56f50e71da..9308b5c719cb16a 100644 --- a/Lib/idlelib/navigatebar.py +++ b/Lib/idlelib/navigatebar.py @@ -1,8 +1,8 @@ """Navigate bar """ -from tkinter import Listbox, Scrollbar, X, Y, END, VERTICAL, RIGHT -from tkinter.ttk import Frame, Entry +from tkinter import Listbox, Scrollbar, X, Y, END, VERTICAL, LEFT, RIGHT +from tkinter.ttk import Frame, Entry, Label class NavigateBar(Frame): @@ -21,9 +21,12 @@ def __init__(self, editor, parent): self.place(x=parent.winfo_width() / 2 - self.winfo_reqwidth() / 2, y=-2) self.selection = 0 self.symbol_line = {} + self.mode = '' def create_widgets(self): self.parent.bind('', self.winconfig_event) + self.label = Label(self, text='navigatebar') + self.label.pack(side=LEFT) self.entry = Entry(self, width=50) self.entry.bind('', self.key_up_event) self.entry.bind('', self.key_down_event) @@ -88,10 +91,12 @@ def key_down_event(self, event=None): self.goto_selection() def set_goto_line_mode(self): - self.entry.insert(0, ':') + self.mode = 'goto' + self.label.config(text='Goto line: ') def set_goto_symbol_mode(self): - self.entry.insert(0, '@') + self.mode = 'symbol' + self.label.config(text='Find symbol: ') def goto_selection(self, event=None): if self.listbox.size(): @@ -112,14 +117,14 @@ def navigate(self, event=None): self.clear_highlight() entry = self.get_entry() - if entry.startswith(':'): + if self.mode == 'goto': try: - lineno = int(entry[1:]) + lineno = int(entry) except ValueError: return self.goto_lineno(lineno) - elif entry.startswith('@'): - self.update_symbol(entry[1:]) + elif self.mode == 'symbol': + self.update_symbol(entry) def clear_listbox(self): """Show off listbox and clear listbox items From dc449eaa85ac2b7b651031ee8697a493fc2e7324 Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Tue, 6 Jun 2017 14:34:26 +0800 Subject: [PATCH 4/5] Add focus out to navigatebar --- Lib/idlelib/navigatebar.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/idlelib/navigatebar.py b/Lib/idlelib/navigatebar.py index 9308b5c719cb16a..5646889ad8a2751 100644 --- a/Lib/idlelib/navigatebar.py +++ b/Lib/idlelib/navigatebar.py @@ -24,6 +24,7 @@ def __init__(self, editor, parent): self.mode = '' def create_widgets(self): + self.bind('', self.cancel) self.parent.bind('', self.winconfig_event) self.label = Label(self, text='navigatebar') self.label.pack(side=LEFT) From 1a97a09749390598671420d63d4f5def3cee23a5 Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Tue, 6 Jun 2017 15:51:24 +0800 Subject: [PATCH 5/5] Remove unused comment --- Lib/idlelib/navigatebar.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Lib/idlelib/navigatebar.py b/Lib/idlelib/navigatebar.py index 5646889ad8a2751..3e9fbf619b64539 100644 --- a/Lib/idlelib/navigatebar.py +++ b/Lib/idlelib/navigatebar.py @@ -7,9 +7,6 @@ class NavigateBar(Frame): """Navigate bar for IDLE. - - Prefix ":" stand for goto lineno - Prefix "@" stand for goto symbol """ def __init__(self, editor, parent):