diff -r 9c5e9e2e0a09 Lib/idlelib/EditorWindow.py --- a/Lib/idlelib/EditorWindow.py Fri Jun 20 22:59:32 2014 +0100 +++ b/Lib/idlelib/EditorWindow.py Sun Jun 22 20:06:41 2014 +0530 @@ -13,6 +13,7 @@ import webbrowser from idlelib.MultiCall import MultiCallCreator +from idlelib.LineNumber import LineNumberCanvas, Text from idlelib import idlever from idlelib import WindowList from idlelib import SearchDialog @@ -146,6 +147,7 @@ 'recent-files.lst') self.text_frame = text_frame = Frame(top) self.vbar = vbar = Scrollbar(text_frame, name='vbar') + self.linenumber_canvas = LineNumberCanvas(parent=self.text_frame) self.width = idleConf.GetOption('main', 'EditorWindow', 'width', type='int') text_options = { @@ -227,6 +229,7 @@ vbar['command'] = text.yview vbar.pack(side=RIGHT, fill=Y) text['yscrollcommand'] = vbar.set + self.configure_linenumber() fontWeight = 'normal' if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'): fontWeight='bold' @@ -423,6 +426,28 @@ "", "") self.text.after_idle(self.set_line_and_column) + def configure_linenumber(self): + """ + Attach/detach linenumber canvas based on config values. + """ + if self.__class__.__name__ == 'PyShell': + return + enable = idleConf.GetOption( + 'main', 'EditorWindow', 'linenumber', type='bool') + self.linenumber_canvas.editwin = self + if enable: + linenumber_color = idleConf.GetHighlight(idleConf.CurrentTheme(), + 'linenumber') + breakpoint_color = idleConf.GetHighlight(idleConf.CurrentTheme(), + 'breakpoint') + self.linenumber_canvas.attach(self.text, + font_color=linenumber_color['foreground'], + side='left', fill='y', + background=linenumber_color['background'], + breakpoint_color=breakpoint_color['foreground']) + else: + self.linenumber_canvas.detach() + def set_line_and_column(self, event=None): line, column = self.text.index(INSERT).split('.') self.status_bar.set_label('column', 'Col: %s' % column) diff -r 9c5e9e2e0a09 Lib/idlelib/LineNumber.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/idlelib/LineNumber.py Sun Jun 22 20:06:41 2014 +0530 @@ -0,0 +1,196 @@ +import tkinter as tk + +class LineNumberCanvas(tk.Canvas): + + def __init__(self, parent, *args, **kwargs): + """ + parent - Parent window to which the linenumber canvas has + to be attached + """ + tk.Canvas.__init__(self, parent, *args, **kwargs) + self.is_enabled = False + self.font_color = 'black' + self.breakpoint_color = 'red' + + def attach(self, text, font_color=None, background=None, + breakpoint_color=None, *args, **kwargs): + """ + Attach a linenumber canvas to 'text'. + + text - the text widget to attach to + font_color - color of the linenumbers + background - color of background of canvas + breakpoint_color - color of breakpoints + """ + try: + self.editwin.breakpoints + self.breakpoints_enabled = True + except: + self.breakpoints_enabled = False + if font_color: + self.font_color = font_color + if background: + self.config(background=background) + if breakpoint_color: + self.breakpoint_color = breakpoint_color + self.text = text + self.number_of_digits = 0 + self.re_render('event') + if self.is_enabled: + return + self.text.bind('<>', self.re_render) + self.text.bind('', self.re_render) + self.pack(*args, **kwargs) + try: + text_info = text.pack_info() + text.pack_configure(**text_info) + except: + pass + self.is_enabled = True + + def detach(self): + """ + Remove the linenumber canvas previously attached. + """ + if not self.is_enabled: + return + self.text.unbind('<>') + self.text.unbind('') + self.text = None + self.number_of_digits = None + self.pack_forget() + self.breakpoints_enabled = None + self.is_enabled = False + + def re_render(self, event): + """Re-render the line canvas""" + self.delete('all') + number_of_digits = len(self.text.index('end').split('.')[0]) + if self.number_of_digits != number_of_digits: + self.number_of_digits = number_of_digits + self['width'] = self.number_of_digits * 8 + 10 + + linenum = int(self.text.index('@0,0').split('.')[0]) + while True: + dline = self.text.tk.call(self.text, 'dlineinfo', '%d.0'%linenum) + if not dline: + break + y, height = dline[1], dline[4] + linenumId = self.create_text(self['width'], y + height * 0.75, + text=linenum, anchor='e', + fill=self.font_color) + if self.breakpoints_enabled: + if linenum in self.editwin.breakpoints: + x1, y1, x2, y2 = self.bbox(linenumId) + self.create_oval(3, y1, 8, y2-3, + fill=self.breakpoint_color, + outline=self.breakpoint_color) + self.tag_raise(linenumId) + linenum += 1 + + def get_linenumber(self): + """Return linenumber of the previous mouse-click""" + if self.find_withtag('current'): + i = self.find_withtag('current')[0] + try: + linenum = int(self.itemcget(i, 'text')) + except: + return + return linenum + + def toggle_breakpoint(self, event): + linenum = self.get_linenumber() + if linenum is None: + return + if linenum in self.editwin.breakpoints: + self.editwin.clear_breakpoint(linenum) + else: + self.editwin.set_breakpoint(linenum) + self.re_render('event') + + +class Text(tk.Text): + """ + Modified Text widget to support custom <> virtual event + to be genereated when the following actions take place: + insert, delete, replace, cursor position change and scroll. + The original event is re-generated. + """ + def __init__(self, *args, **kwargs): + tk.Text.__init__(self, *args, **kwargs) + self.tk.eval(""" + proc widget_interceptor {widget command args} { + + set orig_call [uplevel [linsert $args 0 $command]] + + if { + ([lindex $args 0] == "insert") || + ([lindex $args 0] == "delete") || + ([lindex $args 0] == "replace") || + ([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 original command + return $orig_call + } + """) + self.tk.eval(""" + rename {widget} new_{widget} + interp alias {{}} ::{widget} {{}} widget_interceptor {widget} new_{widget} + """.format(widget=str(self))) + +def _linenumber(parent): + import re + + class MyEditwin: + def __init__(self): + self.breakpoints = [] + + def set_breakpoint(self, linenumber): + self.breakpoints.append(linenumber) + + def clear_breakpoint(self, linenumber): + self.breakpoints.remove(linenumber) + + + root = tk.Tk() + root.title('Test Linenumber and breakpoints') + width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) + root.geometry("+%d+%d"%(x, y + 150)) + + text = Text(root) + for i in range(25): + text.insert('insert', 'this is sample text\n') + editwin = MyEditwin() + linenumber_canvas = LineNumberCanvas(parent=root) + linenumber_canvas.editwin = editwin + linenumber_canvas.bind('', linenumber_canvas.toggle_breakpoint) + button_frame = tk.Frame(root) + attach = tk.Button(button_frame, text='Attach', + command=lambda: linenumber_canvas.attach(text, + side='left', + fill='y',)) + detach = tk.Button(button_frame, text='Detach', + command=lambda: linenumber_canvas.detach()) + + button_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=1) + linenumber_canvas.attach(text, + font_color='black', + side='left', + fill='y', + background='gray', + breakpoint_color='red') + text.pack(side=tk.TOP, fill=tk.BOTH, expand=1) + attach.pack(side=tk.LEFT) + detach.pack(side=tk.RIGHT) + root.mainloop() + +if __name__ == '__main__': + from idlelib.idle_test.htest import run + run(_linenumber) diff -r 9c5e9e2e0a09 Lib/idlelib/PyShell.py --- a/Lib/idlelib/PyShell.py Fri Jun 20 22:59:32 2014 +0100 +++ b/Lib/idlelib/PyShell.py Sun Jun 22 20:06:41 2014 +0530 @@ -127,6 +127,9 @@ self.text.bind("<>", self.set_breakpoint_here) self.text.bind("<>", self.clear_breakpoint_here) self.text.bind("<>", self.flist.open_shell) + self.linenumber_canvas.bind('', + self.linenumber_canvas.toggle_breakpoint) + self.breakpointPath = os.path.join(idleConf.GetUserCfgDir(), 'breakpoints.lst') @@ -151,6 +154,9 @@ def set_breakpoint(self, lineno): text = self.text filename = self.io.filename + if not filename: + text.bell() + return text.tag_add("BREAK", "%d.0" % lineno, "%d.0" % (lineno+1)) try: i = self.breakpoints.index(lineno) @@ -161,34 +167,33 @@ debug.set_breakpoint_here(filename, lineno) except: # but debugger may not be active right now.... pass + self.linenumber_canvas.re_render('event') - def set_breakpoint_here(self, event=None): + def clear_breakpoint(self, lineno): text = self.text filename = self.io.filename if not filename: text.bell() return - lineno = int(float(text.index("insert"))) - self.set_breakpoint(lineno) - - def clear_breakpoint_here(self, event=None): - text = self.text - filename = self.io.filename - if not filename: - text.bell() - return - lineno = int(float(text.index("insert"))) + text.tag_remove("BREAK", "%d.0" % lineno, "%d.0" %(lineno+1)) try: self.breakpoints.remove(lineno) except: pass - text.tag_remove("BREAK", "insert linestart",\ - "insert lineend +1char") try: debug = self.flist.pyshell.interp.debugger debug.clear_breakpoint_here(filename, lineno) except: pass + self.linenumber_canvas.re_render('event') + + def set_breakpoint_here(self, event=None): + lineno = int(float(self.text.index("insert"))) + self.set_breakpoint(lineno) + + def clear_breakpoint_here(self, event=None): + lineno = int(float(self.text.index("insert"))) + self.clear_breakpoint(lineno) def clear_file_breaks(self): if self.breakpoints: diff -r 9c5e9e2e0a09 Lib/idlelib/config-highlight.def --- a/Lib/idlelib/config-highlight.def Fri Jun 20 22:59:32 2014 +0100 +++ b/Lib/idlelib/config-highlight.def Sun Jun 22 20:06:41 2014 +0530 @@ -31,6 +31,10 @@ stderr-background= #ffffff console-foreground= #770000 console-background= #ffffff +linenumber-foreground= black +linenumber-background= gray +#breakpoint (only foreground can be set) +breakpoint-foreground= red [IDLE New] normal-foreground= #000000 @@ -62,3 +66,7 @@ stderr-background= #ffffff console-foreground= #770000 console-background= #ffffff +linenumber-foreground= black +linenumber-background= gray +#breakpoint (only foreground can be set) +breakpoint-foreground= red diff -r 9c5e9e2e0a09 Lib/idlelib/config-main.def --- a/Lib/idlelib/config-main.def Fri Jun 20 22:59:32 2014 +0100 +++ b/Lib/idlelib/config-main.def Sun Jun 22 20:06:41 2014 +0530 @@ -57,6 +57,7 @@ font-size= 10 font-bold= 0 encoding= none +linenumber= 1 [FormatParagraph] paragraph=72 diff -r 9c5e9e2e0a09 Lib/idlelib/configDialog.py --- a/Lib/idlelib/configDialog.py Fri Jun 20 22:59:32 2014 +0100 +++ b/Lib/idlelib/configDialog.py Sun Jun 22 20:06:41 2014 +0530 @@ -18,6 +18,7 @@ from idlelib.configHandler import idleConf from idlelib.dynOptionMenuWidget import DynOptionMenu from idlelib.tabbedpages import TabbedPageSet +from idlelib.LineNumber import LineNumberCanvas, Text from idlelib.keybindingDialog import GetKeysDialog from idlelib.configSectionNameDialog import GetCfgSectionNameDialog from idlelib.configHelpSourceEdit import GetHelpSourceDialog @@ -54,6 +55,8 @@ 'Shell Normal Text':('console','10'), 'Shell Stdout Text':('stdout','11'), 'Shell Stderr Text':('stderr','12'), + 'Line Number':('linenumber', '13'), + 'Breakpoints':('breakpoint', '14') } self.ResetChangedItems() #load initial values in changed items dict self.CreateWidgets() @@ -195,6 +198,9 @@ self.textHighlightSample=Text(frameCustom,relief=SOLID,borderwidth=1, font=('courier',12,''),cursor='hand2',width=21,height=11, takefocus=FALSE,highlightthickness=0,wrap=NONE) + self.linenumber_canvas = LineNumberCanvas(parent=frameCustom) + self.linenumber_canvas.editwin = self + self.linenumber_canvas.editwin.breakpoints = [3,6] text=self.textHighlightSample text.bind('',lambda e: 'break') text.bind('',lambda e: 'break') @@ -215,6 +221,8 @@ text.tag_bind(self.themeElements[element][0],'', lambda event,elem=element: event.widget.winfo_toplevel() .highlightTarget.set(elem)) + self.linenumber_canvas.bind('', lambda e:\ + e.widget.winfo_toplevel().highlightTarget.set('Line Number')) text.config(state=DISABLED) self.frameColourSet=Frame(frameCustom,relief=SOLID,borderwidth=1) frameFgBg=Frame(frameCustom) @@ -248,6 +256,8 @@ #frameCustom self.frameColourSet.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=X) frameFgBg.pack(side=TOP,padx=5,pady=0) + self.linenumber_canvas.attach(self.textHighlightSample, + side=LEFT,fill=BOTH) self.textHighlightSample.pack(side=TOP,padx=5,pady=5,expand=TRUE, fill=BOTH) buttonSetColour.pack(side=TOP,expand=TRUE,fill=X,padx=8,pady=4) @@ -338,6 +348,7 @@ self.winWidth=StringVar(self) self.winHeight=StringVar(self) self.paraWidth=StringVar(self) + self.lineNumber = BooleanVar(self) self.startupEdit=IntVar(self) self.autoSave=IntVar(self) self.encoding=StringVar(self) @@ -353,6 +364,8 @@ text=' Autosave Preferences ') frameWinSize=Frame(frame,borderwidth=2,relief=GROOVE) frameParaSize=Frame(frame,borderwidth=2,relief=GROOVE) + frameLineNumber=LabelFrame(frame,borderwidth=2,relief=GROOVE, + text=' Line Number Preferences ') frameHelp=LabelFrame(frame,borderwidth=2,relief=GROOVE, text=' Additional Help Sources ') #frameRun @@ -381,6 +394,9 @@ ' width (in characters)') entryParaWidth=Entry(frameParaSize,textvariable=self.paraWidth, width=3) + #frameLineNumber + checkLineNumber=Checkbutton(frameLineNumber,variable=self.lineNumber, + onvalue=1,offvalue=0,text='Show linenumbers') #frameHelp frameHelpList=Frame(frameHelp) frameHelpListButtons=Frame(frameHelpList) @@ -402,6 +418,7 @@ frameSave.pack(side=TOP,padx=5,pady=5,fill=X) frameWinSize.pack(side=TOP,padx=5,pady=5,fill=X) frameParaSize.pack(side=TOP,padx=5,pady=5,fill=X) + frameLineNumber.pack(side=TOP,padx=5,pady=5,fill=X) frameHelp.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH) #frameRun labelRunChoiceTitle.pack(side=LEFT,anchor=W,padx=5,pady=5) @@ -420,6 +437,8 @@ #paragraphFormatWidth labelParaWidthTitle.pack(side=LEFT,anchor=W,padx=5,pady=5) entryParaWidth.pack(side=RIGHT,anchor=E,padx=10,pady=5) + #frameLineNumber + checkLineNumber.pack(side=LEFT,anchor=W,padx=10,pady=5) #frameHelp frameHelpListButtons.pack(side=RIGHT,padx=5,pady=5,fill=Y) frameHelpList.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH) @@ -447,6 +466,7 @@ self.winWidth.trace_variable('w',self.VarChanged_winWidth) self.winHeight.trace_variable('w',self.VarChanged_winHeight) self.paraWidth.trace_variable('w',self.VarChanged_paraWidth) + self.lineNumber.trace_variable('w',self.VarChanged_lineNumber) self.startupEdit.trace_variable('w',self.VarChanged_startupEdit) self.autoSave.trace_variable('w',self.VarChanged_autoSave) self.encoding.trace_variable('w',self.VarChanged_encoding) @@ -535,6 +555,10 @@ value=self.paraWidth.get() self.AddChangedItem('main','FormatParagraph','paragraph',value) + def VarChanged_lineNumber(self,*params): + value=self.lineNumber.get() + self.AddChangedItem('main','EditorWindow','linenumber',value) + def VarChanged_startupEdit(self,*params): value=self.startupEdit.get() self.AddChangedItem('main','General','editor-on-startup',value) @@ -776,6 +800,16 @@ else: plane='background' sampleElement=self.themeElements[self.highlightTarget.get()][0] self.textHighlightSample.tag_config(sampleElement, **{plane:newColour}) + if sampleElement == 'linenumber': + if plane == 'foreground': + self.linenumber_canvas.attach(text=self.textHighlightSample, + font_color=newColour) + if plane == 'background': + self.linenumber_canvas.attach(text=self.textHighlightSample, + background=newColour) + if sampleElement == 'breakpoint': + self.linenumber_canvas.attach(text=self.textHighlightSample, + breakpoint_color=newColour) theme=self.customTheme.get() themeElement=sampleElement+'-'+plane self.AddChangedItem('highlight',theme,themeElement,newColour) @@ -830,9 +864,11 @@ newFont = (fontName, self.fontSize.get(), fontWeight) self.labelFontSample.config(font=newFont) self.textHighlightSample.configure(font=newFont) + self.linenumber_canvas.re_render('event') def SetHighlightTarget(self): - if self.highlightTarget.get()=='Cursor': #bg not possible + if self.highlightTarget.get()=='Cursor' or \ + self.highlightTarget.get()=='Breakpoints': #bg not possible self.radioFg.config(state=DISABLED) self.radioBg.config(state=DISABLED) self.fgHilite.set(1) @@ -846,7 +882,7 @@ self.SetColourSample() def SetColourSample(self): - #set the colour smaple area + #set the colour sample area tag=self.themeElements[self.highlightTarget.get()][0] if self.fgHilite.get(): plane='foreground' else: plane='background' @@ -872,6 +908,15 @@ if element+'-background' in themeDict: colours['background']=themeDict[element+'-background'] self.textHighlightSample.tag_config(element, **colours) + if elementTitle == 'Line Number': + self.linenumber_canvas.attach( + text=self.textHighlightSample, + font_color=colours['foreground'], + background=colours['background']) + if elementTitle == 'Breakpoints': + self.linenumber_canvas.attach( + text=self.textHighlightSample, + breakpoint_color=colours['foreground']) self.SetColourSample() def HelpSourceSelected(self,event): @@ -1033,8 +1078,11 @@ self.winHeight.set(idleConf.GetOption('main','EditorWindow','height', type='int')) #initial paragraph reformat size - self.paraWidth.set(idleConf.GetOption('main','FormatParagraph','paragraph', - type='int')) + self.paraWidth.set(idleConf.GetOption('main','FormatParagraph', + 'paragraph', type='int')) + #initial linenumber option + self.lineNumber.set(idleConf.GetOption('main','EditorWindow', + 'linenumber', type='int')) # default source encoding self.encoding.set(idleConf.GetOption('main', 'EditorWindow', 'encoding', default='none')) @@ -1128,6 +1176,7 @@ instance.set_notabs_indentwidth() instance.ApplyKeybindings() instance.reset_help_menu_entries() + instance.configure_linenumber() def Cancel(self): self.destroy() diff -r 9c5e9e2e0a09 Lib/idlelib/configHandler.py --- a/Lib/idlelib/configHandler.py Fri Jun 20 22:59:32 2014 +0100 +++ b/Lib/idlelib/configHandler.py Sun Jun 22 20:06:41 2014 +0530 @@ -306,7 +306,8 @@ else: themeDict=self.GetThemeDict('user',theme) fore=themeDict[element+'-foreground'] - if element=='cursor': #there is no config value for cursor bg + if element=='cursor' or element=='breakpoint': + #there is no config value for cursor bg and breakpoint bg back=themeDict['normal-background'] else: back=themeDict[element+'-background'] @@ -368,7 +369,10 @@ 'stderr-foreground':'#000000', 'stderr-background':'#ffffff', 'console-foreground':'#000000', - 'console-background':'#ffffff' } + 'console-background':'#ffffff', + 'linenumber-foreground':'#000000', + 'linenumber-background':'gray', + 'breakpoint-foreground':'#ff0000'} for element in theme: if not cfgParser.has_option(themeName,element): #we are going to return a default, print warning diff -r 9c5e9e2e0a09 Lib/idlelib/idle_test/htest.py --- a/Lib/idlelib/idle_test/htest.py Fri Jun 20 22:59:32 2014 +0100 +++ b/Lib/idlelib/idle_test/htest.py Sun Jun 22 20:06:41 2014 +0530 @@ -171,6 +171,23 @@ " to save the file\n" } +_linenumber_spec = { + 'file': 'LineNumber', + 'kwds': {}, + 'msg': "The text widget and linenumber canvas are scrollable." + "\nAny changes to the text widget like insertion, deletion, " + "resize etc should be reflected in the linenumber canvas." + "Single left mouse button click on any linenumber should display " + "a breakpoint for that line.\nA breakpoint is indicated by a " + "red circle beside that linenumber.\nClicking a linenumber which " + "already has a breakpoint should deactivate the breakpoint for " + "that line.\nClick on the 'Attach'/'Detach' button should display " + "the linenumber canvas if not present/present respectively,\n" + "no action should be performed if already present/not present." + "Breakpoint data should persist across 'Attach' and 'Detach' " + "actions." + } + _multi_call_spec = { 'file': 'MultiCall', 'kwds': {},

AltStyle によって変換されたページ (->オリジナル) /