c0b66a3aa06a1a233b3820273ec3339c899cef8b
from itertools
import count
from configHandler
import idleConf
import aboutDialog
, textView
, configDialog
# The default tab setting for a Text widget, in average-width characters.
def _find_module(fullname
, path
=None):
"""Version of imp.find_module() that handles hierarchical module names"""
for tgt
in fullname
.split('.'):
file.close() # close intermediate files
(file, filename
, descr
) = imp
.find_module(tgt
, path
)
if descr
[2] == imp
.PY_SOURCE
:
break # find but not load the source file
module
= imp
.load_module(tgt
, file, filename
, descr
)
raise ImportError, 'No source for module ' + module
.__name
__
return file, filename
, descr
from Percolator
import Percolator
from ColorDelegator
import ColorDelegator
from UndoDelegator
import UndoDelegator
from IOBinding
import IOBinding
from Tkinter
import Toplevel
from MultiStatusBar
import MultiStatusBar
def __init__(self
, flist
=None, filename
=None, key
=None, root
=None):
if EditorWindow
.help_url
is None:
dochome
= os
.path
.join(sys
.prefix
, 'Doc', 'index.html')
if sys
.platform
.count('linux'):
# look for html docs in a couple of standard places
pyver
= 'python-docs-' + '%s.%s.%s' % sys
.version_info
[:3]
if os
.path
.isdir('/var/www/html/python/'): # "python2" rpm
dochome
= '/var/www/html/python/index.html'
basepath
= '/usr/share/doc/' # standard location
dochome
= os
.path
.join(basepath
, pyver
,
elif sys
.platform
[:3] == 'win':
chmfile
= os
.path
.join(sys
.prefix
, 'Doc',
'Python%d%d.chm' % sys
.version_info
[:2])
if os
.path
.isfile(chmfile
):
dochome
= os
.path
.normpath(dochome
)
if os
.path
.isfile(dochome
):
EditorWindow
.help_url
= dochome
EditorWindow
.help_url
= "http://www.python.org/doc/current"
currentTheme
=idleConf
.CurrentTheme()
root
= root
or flist
.root
self
.menubar
= Menu(root
)
self
.top
= top
= WindowList
.ListedToplevel(root
, menu
=self
.menubar
)
self
.tkinter_vars
= flist
.vars
#self.top.instance_dict makes flist.inversedict avalable to
#configDialog.py so it can access all EditorWindow instaces
self
.top
.instance_dict
=flist
.inversedict
self
.tkinter_vars
= {} # keys: Tkinter event names
# values: Tkinter variable instances
self
.recent_files_path
=os
.path
.join(idleConf
.GetUserCfgDir(),
self
.vbar
= vbar
= Scrollbar(top
, name
='vbar')
self
.text_frame
= text_frame
= Frame(top
)
self
.width
= idleConf
.GetOption('main','EditorWindow','width')
self
.text
= text
= Text(text_frame
, name
='text', padx
=5, wrap
='none',
foreground
=idleConf
.GetHighlight(currentTheme
,
background
=idleConf
.GetHighlight(currentTheme
,
highlightcolor
=idleConf
.GetHighlight(currentTheme
,
highlightbackground
=idleConf
.GetHighlight(currentTheme
,
insertbackground
=idleConf
.GetHighlight(currentTheme
,
height
=idleConf
.GetOption('main','EditorWindow','height') )
self
.top
.focused_widget
= self
.text
self
.top
.protocol("WM_DELETE_WINDOW", self
.close
)
self
.top
.bind("<<close-window>>", self
.close_event
)
text
.bind("<<cut>>", self
.cut
)
text
.bind("<<copy>>", self
.copy
)
text
.bind("<<paste>>", self
.paste
)
text
.bind("<<center-insert>>", self
.center_insert_event
)
text
.bind("<<help>>", self
.help_dialog
)
text
.bind("<<python-docs>>", self
.python_docs
)
text
.bind("<<about-idle>>", self
.about_dialog
)
text
.bind("<<open-config-dialog>>", self
.config_dialog
)
text
.bind("<<open-module>>", self
.open_module
)
text
.bind("<<do-nothing>>", lambda event
: "break")
text
.bind("<<select-all>>", self
.select_all
)
text
.bind("<<remove-selection>>", self
.remove_selection
)
text
.bind("<<find>>", self
.find_event
)
text
.bind("<<find-again>>", self
.find_again_event
)
text
.bind("<<find-in-files>>", self
.find_in_files_event
)
text
.bind("<<find-selection>>", self
.find_selection_event
)
text
.bind("<<replace>>", self
.replace_event
)
text
.bind("<<goto-line>>", self
.goto_line_event
)
text
.bind("<3>", self
.right_menu_event
)
text
.bind("<<smart-backspace>>",self
.smart_backspace_event
)
text
.bind("<<newline-and-indent>>",self
.newline_and_indent_event
)
text
.bind("<<smart-indent>>",self
.smart_indent_event
)
text
.bind("<<indent-region>>",self
.indent_region_event
)
text
.bind("<<dedent-region>>",self
.dedent_region_event
)
text
.bind("<<comment-region>>",self
.comment_region_event
)
text
.bind("<<uncomment-region>>",self
.uncomment_region_event
)
text
.bind("<<tabify-region>>",self
.tabify_region_event
)
text
.bind("<<untabify-region>>",self
.untabify_region_event
)
text
.bind("<<toggle-tabs>>",self
.toggle_tabs_event
)
text
.bind("<<change-indentwidth>>",self
.change_indentwidth_event
)
text
.bind("<Left>", self
.move_at_edge_if_selection(0))
text
.bind("<Right>", self
.move_at_edge_if_selection(1))
flist
.inversedict
[self
] = key
text
.bind("<<open-new-window>>", self
.new_callback
)
text
.bind("<<close-all-windows>>", self
.flist
.close_all_callback
)
text
.bind("<<open-class-browser>>", self
.open_class_browser
)
text
.bind("<<open-path-browser>>", self
.open_path_browser
)
vbar
['command'] = text
.yview
vbar
.pack(side
=RIGHT
, fill
=Y
)
text
['yscrollcommand'] = vbar
.set
if idleConf
.GetOption('main','EditorWindow','font-bold',type='bool'):
text
.config(font
=(idleConf
.GetOption('main','EditorWindow','font'),
idleConf
.GetOption('main','EditorWindow','font-size'),
text_frame
.pack(side
=LEFT
, fill
=BOTH
, expand
=1)
text
.pack(side
=TOP
, fill
=BOTH
, expand
=1)
self
.per
= per
= self
.Percolator(text
)
if self
.ispythonsource(filename
):
self
.color
= color
= self
.ColorDelegator()
self
.undo
= undo
= self
.UndoDelegator()
text
.undo_block_start
= undo
.undo_block_start
text
.undo_block_stop
= undo
.undo_block_stop
undo
.set_saved_change_hook(self
.saved_change_hook
)
# IOBinding implements file I/O and printing functionality
self
.io
= io
= self
.IOBinding(self
)
io
.set_filename_change_hook(self
.filename_change_hook
)
# Create the recent files submenu
self
.recent_files_menu
= Menu(self
.menubar
)
self
.menudict
['file'].insert_cascade(3, label
='Recent Files',
menu
=self
.recent_files_menu
)
self
.update_recent_files_list()
if os
.path
.exists(filename
) and not os
.path
.isdir(filename
):
io
.set_filename(filename
)
menu
= self
.menudict
.get('windows')
WindowList
.register_callback(self
.postwindowsmenu
)
# Some abstractions so IDLE extensions are cross-IDE
self
.askyesno
= tkMessageBox
.askyesno
self
.askinteger
= tkSimpleDialog
.askinteger
self
.showerror
= tkMessageBox
.showerror
if self
.extensions
.has_key('AutoIndent'):
self
.extensions
['AutoIndent'].set_indentation_params(
self
.ispythonsource(filename
))
def new_callback(self
, event
):
dirname
, basename
= self
.io
.defaultfilename()
def set_status_bar(self
):
self
.status_bar
= self
.MultiStatusBar(self
.top
)
self
.status_bar
.set_label('column', 'Col: ?', side
=RIGHT
)
self
.status_bar
.set_label('line', 'Ln: ?', side
=RIGHT
)
self
.status_bar
.pack(side
=BOTTOM
, fill
=X
)
self
.text
.bind('<KeyRelease>', self
.set_line_and_column
)
self
.text
.bind('<ButtonRelease>', self
.set_line_and_column
)
self
.text
.after_idle(self
.set_line_and_column
)
def set_line_and_column(self
, event
=None):
line
, column
= self
.text
.index(INSERT
).split('.')
self
.status_bar
.set_label('column', 'Col: %s' % column
)
self
.status_bar
.set_label('line', 'Ln: %s' % line
)
self
.menudict
= menudict
= {}
for name
, label
in self
.menu_specs
:
underline
, label
= prepstr(label
)
menudict
[name
] = menu
= Menu(mbar
, name
=name
)
mbar
.add_cascade(label
=label
, menu
=menu
, underline
=underline
)
self
.base_helpmenu_length
= self
.menudict
['help'].index(END
)
self
.reset_help_menu_entries()
def postwindowsmenu(self
):
# Only called when Windows menu exists
menu
= self
.menudict
['windows']
menu
.delete(self
.wmenu_end
+1, end
)
WindowList
.add_windows_to_menu(menu
)
def right_menu_event(self
, event
):
self
.text
.tag_remove("sel", "1.0", "end")
self
.text
.mark_set("insert", "@%d,%d" % (event
.x
, event
.y
))
iswin
= sys
.platform
[:3] == 'win'
self
.text
.config(cursor
="arrow")
rmenu
.tk_popup(event
.x_root
, event
.y_root
)
self
.text
.config(cursor
="ibeam")
# ("Label", "<<virtual-event>>"), ...
("Close", "<<close-window>>"), # Example
rmenu
= Menu(self
.text
, tearoff
=0)
for label
, eventname
in self
.rmenu_specs
:
def command(text
=self
.text
, eventname
=eventname
):
text
.event_generate(eventname
)
rmenu
.add_command(label
=label
, command
=command
)
def about_dialog(self
, event
=None):
aboutDialog
.AboutDialog(self
.top
,'About IDLE')
def config_dialog(self
, event
=None):
configDialog
.ConfigDialog(self
.top
,'Settings')
def help_dialog(self
, event
=None):
fn
=os
.path
.join(os
.path
.abspath(os
.path
.dirname(__file__
)),'help.txt')
textView
.TextViewer(self
.top
,'Help',fn
)
def python_docs(self
, event
=None):
if sys
.platform
[:3] == 'win':
os
.startfile(self
.help_url
)
webbrowser
.open(self
.help_url
)
self
.text
.event_generate("<<Cut>>")
self
.text
.event_generate("<<Copy>>")
self
.text
.event_generate("<<Paste>>")
def select_all(self
, event
=None):
self
.text
.tag_add("sel", "1.0", "end-1c")
self
.text
.mark_set("insert", "1.0")
def remove_selection(self
, event
=None):
self
.text
.tag_remove("sel", "1.0", "end")
def move_at_edge_if_selection(self
, edge_index
):
"""Cursor move begins at start or end of selection
When a left/right cursor key is pressed create and return to Tkinter a
function which causes a cursor move from the associated edge of the
self_text_index
= self
.text
.index
self_text_mark_set
= self
.text
.mark_set
edges_table
= ("sel.first+1c", "sel.last-1c")
if (event
.state
& 5) == 0: # no shift(==1) or control(==4) pressed
self_text_index("sel.first")
self_text_mark_set("insert", edges_table
[edge_index
])
def find_event(self
, event
):
SearchDialog
.find(self
.text
)
def find_again_event(self
, event
):
SearchDialog
.find_again(self
.text
)
def find_selection_event(self
, event
):
SearchDialog
.find_selection(self
.text
)
def find_in_files_event(self
, event
):
GrepDialog
.grep(self
.text
, self
.io
, self
.flist
)
def replace_event(self
, event
):
ReplaceDialog
.replace(self
.text
)
def goto_line_event(self
, event
):
lineno
= tkSimpleDialog
.askinteger("Goto",
"Go to line number:",parent
=text
)
text
.mark_set("insert", "%d.0" % lineno
)
def open_module(self
, event
=None):
# XXX Shouldn't this be in IOBinding or in FileList?
name
= self
.text
.get("sel.first", "sel.last")
name
= tkSimpleDialog
.askstring("Module",
"Enter the name of a Python module\n"
"to search on sys.path and open:",
parent
=self
.text
, initialvalue
=name
)
# XXX Ought to insert current file's directory in front of path
(f
, file, (suffix
, mode
, type)) = _find_module(name
)
except (NameError, ImportError), msg
:
tkMessageBox
.showerror("Import error", str(msg
), parent
=self
.text
)
if type != imp
.PY_SOURCE
:
tkMessageBox
.showerror("Unsupported type",
"%s is not a source module" % name
, parent
=self
.text
)
def open_class_browser(self
, event
=None):
filename
= self
.io
.filename
"This buffer has no associated filename",
head
, tail
= os
.path
.split(filename
)
base
, ext
= os
.path
.splitext(tail
)
ClassBrowser
.ClassBrowser(self
.flist
, base
, [head
])
def open_path_browser(self
, event
=None):
PathBrowser
.PathBrowser(self
.flist
)
def gotoline(self
, lineno
):
if lineno
is not None and lineno
> 0:
self
.text
.mark_set("insert", "%d.0" % lineno
)
self
.text
.tag_remove("sel", "1.0", "end")
self
.text
.tag_add("sel", "insert", "insert +1l")
def ispythonsource(self
, filename
):
base
, ext
= os
.path
.splitext(os
.path
.basename(filename
))
if os
.path
.normcase(ext
) in (".py", ".pyw"):
return line
.startswith('#!') and line
.find('python') >= 0
self
.flist
.close_edit(self
)
def set_close_hook(self
, close_hook
):
self
.close_hook
= close_hook
def filename_change_hook(self
):
self
.flist
.filename_changed_edit(self
)
self
.top
.update_windowlist_registry(self
)
if self
.ispythonsource(self
.io
.filename
):
self
.per
.removefilter(self
.undo
)
self
.color
= self
.ColorDelegator()
self
.per
.insertfilter(self
.color
)
self
.per
.insertfilter(self
.undo
)
self
.per
.removefilter(self
.undo
)
self
.per
.removefilter(self
.color
)
self
.per
.insertfilter(self
.undo
)
def ResetColorizer(self
):
"Update the colour theme if it is changed"
# Called from configDialog.py
self
.color
= self
.ColorDelegator()
self
.per
.insertfilter(self
.color
)
theme
= idleConf
.GetOption('main','Theme','name')
self
.text
.config(idleConf
.GetHighlight(theme
, "normal"))
"Update the text widgets' font if it is changed"
# Called from configDialog.py
if idleConf
.GetOption('main','EditorWindow','font-bold',type='bool'):
self
.text
.config(font
=(idleConf
.GetOption('main','EditorWindow','font'),
idleConf
.GetOption('main','EditorWindow','font-size'),
def ResetKeybindings(self
):
"Update the keybindings if they are changed"
# Called from configDialog.py
self
.Bindings
.default_keydefs
=idleConf
.GetCurrentKeySet()
keydefs
= self
.Bindings
.default_keydefs
for event
, keylist
in keydefs
.items():
self
.text
.event_delete(event
)
#update menu accelerators
for menu
in self
.Bindings
.menudefs
:
menuEventDict
[menu
[0]]={}
menuEventDict
[menu
[0]][prepstr(item
[0])[1]]=item
[1]
for menubarItem
in self
.menudict
.keys():
menu
=self
.menudict
[menubarItem
]
for index
in range(0,end
):
if menu
.type(index
)=='command':
accel
=menu
.entrycget(index
,'accelerator')
itemName
=menu
.entrycget(index
,'label')
if menuEventDict
.has_key(menubarItem
):
if menuEventDict
[menubarItem
].has_key(itemName
):
event
=menuEventDict
[menubarItem
][itemName
]
accel
=get_accelerator(keydefs
, event
)
menu
.entryconfig(index
,accelerator
=accel
)
def reset_help_menu_entries(self
):
"Update the additional help entries on the Help menu"
help_list
= idleConf
.GetAllExtraHelpSourcesList()
helpmenu
= self
.menudict
['help']
# first delete the extra help entries, if any
helpmenu_length
= helpmenu
.index(END
)
if helpmenu_length
> self
.base_helpmenu_length
:
helpmenu
.delete((self
.base_helpmenu_length
+ 1), helpmenu_length
)
cmd
= self
.__extra
_help
_callback
(entry
[1])
helpmenu
.add_command(label
=entry
[0], command
=cmd
)
# and update the menu dictionary
self
.menudict
['help'] = helpmenu
def __extra_help_callback(self
, helpfile
):
"Create a callback with the helpfile value frozen at definition time"
def display_extra_help(helpfile
=helpfile
):
if not (helpfile
.startswith('www') or helpfile
.startswith('http')):
url
= os
.path
.normpath(helpfile
)
if sys
.platform
[:3] == 'win':
webbrowser
.open(helpfile
)
return display_extra_help
def update_recent_files_list(self
, new_file
=None):
"Load and update the recent files list and menus"
if os
.path
.exists(self
.recent_files_path
):
rf_list_file
= open(self
.recent_files_path
,'r')
rf_list
= rf_list_file
.readlines()
new_file
= os
.path
.abspath(new_file
) + '\n'
rf_list
.remove(new_file
) # move to top
rf_list
.insert(0, new_file
)
# clean and save the recent files list
if '\0' in path
or not os
.path
.exists(path
[0:-1]):
rf_list
= [path
for path
in rf_list
if path
not in bad_paths
]
ulchars
= "1234567890ABCDEFGHIJK"
rf_list
= rf_list
[0:len(ulchars
)]
rf_file
= open(self
.recent_files_path
, 'w')
rf_file
.writelines(rf_list
)
# for each edit window instance, construct the recent files menu
for instance
in self
.top
.instance_dict
.keys():
menu
= instance
.recent_files_menu
menu
.delete(1, END
) # clear, and rebuild:
for i
, file in zip(count(), rf_list
):
file_name
= file[0:-1] # zap \n
callback
= instance
.__recent
_file
_callback
(file_name
)
menu
.add_command(label
=ulchars
[i
] + " " + file_name
,
def __recent_file_callback(self
, file_name
):
def open_recent_file(fn_closure
=file_name
):
self
.io
.open(editFile
=fn_closure
)
def saved_change_hook(self
):
short
= self
.short_title()
title
= short
+ " - " + long
icon
= short
or long or title
self
.top
.wm_iconname(icon
)
return self
.undo
.get_saved()
def set_saved(self
, flag
):
self
.undo
.set_saved(flag
)
filename
= self
.io
.filename
filename
= os
.path
.basename(filename
)
return self
.io
.filename
or ""
def center_insert_event(self
, event
):
def center(self
, mark
="insert"):
top
, bot
= self
.getwindowlines()
lineno
= self
.getlineno(mark
)
newtop
= max(1, lineno
- height
//2)
text
.yview(float(newtop
))
def getwindowlines(self
):
top
= self
.getlineno("@0,0")
bot
= self
.getlineno("@0,65535")
if top
== bot
and text
.winfo_height() == 1:
# Geometry manager hasn't run yet
height
= int(text
['height'])
def getlineno(self
, mark
="insert"):
return int(float(text
.index(mark
)))
"Return (width, height, x, y)"
geom
= self
.top
.wm_geometry()
m
= re
.match(r
"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom
)
tuple = (map(int, m
.groups()))
def close_event(self
, event
):
if self
.top
.state()!='normal':
return self
.io
.maybesave()
self
.update_recent_files_list(new_file
=self
.io
.filename
)
WindowList
.unregister_callback(self
.postwindowsmenu
)
self
.io
.close(); self
.io
= None
colorizing
= self
.color
.colorizing
doh
= colorizing
and self
.top
self
.color
.close(doh
) # Cancel colorization
self
.per
.close(); self
.per
= None
def load_extensions(self
):
self
.load_standard_extensions()
def unload_extensions(self
):
for ins
in self
.extensions
.values():
if hasattr(ins
, "close"):
def load_standard_extensions(self
):
for name
in self
.get_standard_extension_names():
self
.load_extension(name
)
print "Failed to load extension", repr(name
)
def get_standard_extension_names(self
):
return idleConf
.GetExtensions(editor_only
=True)
def load_extension(self
, name
):
mod
= __import__(name
, globals(), locals(), [])
print "\nFailed to import extension: ", name
keydefs
= idleConf
.GetExtensionBindings(name
)
if hasattr(cls
, "menudefs"):
self
.fill_menus(cls
.menudefs
, keydefs
)
self
.extensions
[name
] = ins
self
.apply_bindings(keydefs
)
for vevent
in keydefs
.keys():
methodname
= vevent
.replace("-", "_")
while methodname
[:1] == '<':
methodname
= methodname
[1:]
while methodname
[-1:] == '>':
methodname
= methodname
[:-1]
methodname
= methodname
+ "_event"
if hasattr(ins
, methodname
):
self
.text
.bind(vevent
, getattr(ins
, methodname
))
def apply_bindings(self
, keydefs
=None):
keydefs
= self
.Bindings
.default_keydefs
for event
, keylist
in keydefs
.items():
text
.event_add(event
, *keylist
)
def fill_menus(self
, menudefs
=None, keydefs
=None):
"""Add appropriate entries to the menus and submenus
Menus that are absent or None in self.menudict are ignored.
menudefs
= self
.Bindings
.menudefs
keydefs
= self
.Bindings
.default_keydefs
for mname
, entrylist
in menudefs
:
menu
= menudict
.get(mname
)
checkbutton
= (label
[:1] == '!')
underline
, label
= prepstr(label
)
accelerator
= get_accelerator(keydefs
, eventname
)
def command(text
=text
, eventname
=eventname
):
text
.event_generate(eventname
)
var
= self
.get_var_obj(eventname
, BooleanVar
)
menu
.add_checkbutton(label
=label
, underline
=underline
,
command
=command
, accelerator
=accelerator
,
menu
.add_command(label
=label
, underline
=underline
,
var
= self
.get_var_obj(name
)
def setvar(self
, name
, value
, vartype
=None):
var
= self
.get_var_obj(name
, vartype
)
def get_var_obj(self
, name
, vartype
=None):
var
= self
.tkinter_vars
.get(name
)
# create a Tkinter variable object with self.text as master:
self
.tkinter_vars
[name
] = var
= vartype(self
.text
)
# Tk implementations of "virtual text methods" -- each platform
# reusing IDLE's support code needs to define these for its GUI's
# Is character at text_index in a Python string? Return 0 for
# "guaranteed no", true for anything else. This info is expensive
# to compute ab initio, but is probably already known by the
def is_char_in_string(self
, text_index
):
# Return true iff colorizer hasn't (re)gotten this far
# yet, or the character is tagged as being in a string
return self
.text
.tag_prevrange("TODO", text_index
) or \
"STRING" in self
.text
.tag_names(text_index
)
# The colorizer is missing: assume the worst
# If a selection is defined in the text widget, return (start,
# end) as Tkinter text indices, otherwise return (None, None)
def get_selection_indices(self
):
first
= self
.text
.index("sel.first")
last
= self
.text
.index("sel.last")
# Return the text widget's current view of what a tab stop means
# (equivalent width in spaces).
current
= self
.text
['tabs'] or TK_TABWIDTH_DEFAULT
# Set the text widget's current view of what a tab stop means.
def set_tabwidth(self
, newtabwidth
):
if self
.get_tabwidth() != newtabwidth
:
pixels
= text
.tk
.call("font", "measure", text
["font"],
"-displayof", text
.master
,
text
.configure(tabs
=pixels
)
### begin autoindent code ###
# usetabs true -> literal tab characters are used by indent and
# dedent cmds, possibly mixed with spaces if
# indentwidth is not a multiple of tabwidth
# false -> tab characters are converted to spaces by indent
# and dedent cmds, and ditto TAB keystrokes
# indentwidth is the number of characters per logical indent level.
# tabwidth is the display width of a literal tab character.
# CAUTION: telling Tk to use anything other than its default
# tab setting causes it to use an entirely different tabbing algorithm,
# treating tab stops as fixed distances from the left margin.
# Nobody expects this, so for now tabwidth should never be changed.
tabwidth
= 8 # for IDLE use, must remain 8 until Tk is fixed
# If context_use_ps1 is true, parsing searches back for a ps1 line;
# else searches for a popular (if, def, ...) Python stmt.
# When searching backwards for a reliable place to begin parsing,
# first start num_context_lines[0] lines back, then
# num_context_lines[1] lines back if that didn't work, and so on.
# The last value should be huge (larger than the # of lines in a
# Making the initial values larger slows things down more often.
num_context_lines
= 50, 500, 5000000
def config(self
, **options
):
for key
, value
in options
.items():
elif key
== 'indentwidth':
elif key
== 'context_use_ps1':
self
.context_use_ps1
= value
raise KeyError, "bad option name: %r" % (key
,)
# If ispythonsource and guess are true, guess a good value for
# indentwidth based on file content (if possible), and if
# indentwidth != tabwidth set usetabs false.
# In any case, adjust the Text widget's view of what a tab
def set_indentation_params(self
, ispythonsource
, guess
=1):
if guess
and ispythonsource
:
if self
.indentwidth
!= self
.tabwidth
:
self
.set_tabwidth(self
.tabwidth
)
def smart_backspace_event(self
, event
):
first
, last
= self
.get_selection_indices()
text
.mark_set("insert", first
)
# Delete whitespace left, until hitting a real char or closest
# preceding virtual tab stop.
chars
= text
.get("insert linestart", "insert")
if text
.compare("insert", ">", "1.0"):
# easy: delete preceding newline
text
.bell() # at start of buffer
if chars
[-1] not in " \t":
# easy: delete preceding real char
# Ick. It may require *inserting* spaces if we back up over a
# tab character! This is written to be clear, not fast.
have
= len(chars
.expandtabs(tabwidth
))
want
= ((have
- 1) // self
.indentwidth
) * self
.indentwidth
# Debug prompt is multilined....
last_line_of_prompt
= sys
.ps1
.split('\n')[-1]
if chars
== last_line_of_prompt
:
ncharsdeleted
= ncharsdeleted
+ 1
have
= len(chars
.expandtabs(tabwidth
))
if have
<= want
or chars
[-1] not in " \t":
text
.delete("insert-%dc" % ncharsdeleted
, "insert")
text
.insert("insert", ' ' * (want
- have
))
def smart_indent_event(self
, event
):
# if intraline selection:
# elif multiline selection:
# do indent-region & return
first
, last
= self
.get_selection_indices()
if index2line(first
) != index2line(last
):
return self
.indent_region_event(event
)
text
.mark_set("insert", first
)
prefix
= text
.get("insert linestart", "insert")
raw
, effective
= classifyws(prefix
, self
.tabwidth
)
# only whitespace to the left
self
.reindent_to(effective
+ self
.indentwidth
)
effective
= len(prefix
.expandtabs(self
.tabwidth
))
pad
= ' ' * (n
- effective
% n
)
text
.insert("insert", pad
)
def newline_and_indent_event(self
, event
):
first
, last
= self
.get_selection_indices()
text
.mark_set("insert", first
)
line
= text
.get("insert linestart", "insert")
while i
< n
and line
[i
] in " \t":
# the cursor is in or at leading indentation in a continuation
# line; just inject an empty line at the start
text
.insert("insert linestart", '\n')
# strip whitespace before insert point unless it's in the prompt
last_line_of_prompt
= sys
.ps1
.split('\n')[-1]
while line
and line
[-1] in " \t" and line
!= last_line_of_prompt
:
text
.delete("insert - %d chars" % i
, "insert")
# strip whitespace after insert point
while text
.get("insert") in " \t":
text
.insert("insert", '\n')
# adjust indentation for continuations and block
# open/close first need to find the last stmt
lno
= index2line(text
.index('insert'))
y
= PyParse
.Parser(self
.indentwidth
, self
.tabwidth
)
for context
in self
.num_context_lines
:
startat
= max(lno
- context
, 1)
startatindex
= repr(startat
) + ".0"
rawtext
= text
.get(startatindex
, "insert")
bod
= y
.find_good_parse_start(
self
._build
_char
_in
_string
_func
(startatindex
))
if bod
is not None or startat
== 1:
c
= y
.get_continuation_type()
# The current stmt hasn't ended yet.
if c
== PyParse
.C_STRING
:
# inside a string; just mimic the current indent
text
.insert("insert", indent
)
elif c
== PyParse
.C_BRACKET
:
# line up with the first (if any) element of the
# last open bracket structure; else indent one
# level beyond the indent of the line with the
self
.reindent_to(y
.compute_bracket_indent())
elif c
== PyParse
.C_BACKSLASH
:
# if more than one line in this stmt already, just
# mimic the current indent; else if initial line
# has a start on an assignment stmt, indent to
# beyond leftmost =; else to beyond first chunk of
# non-whitespace on initial line
if y
.get_num_lines_in_stmt() > 1:
text
.insert("insert", indent
)
self
.reindent_to(y
.compute_backslash_indent())
assert 0, "bogus continuation type %r" % (c
,)
# This line starts a brand new stmt; indent relative to
# indentation of initial line of closest preceding
indent
= y
.get_base_indent_string()
text
.insert("insert", indent
)
self
.smart_indent_event(event
)
elif indent
and y
.is_block_closer():
self
.smart_backspace_event(event
)
# Our editwin provides a is_char_in_string function that works
# with a Tk text index, but PyParse only knows about offsets into
# a string. This builds a function for PyParse that accepts an
def _build_char_in_string_func(self
, startindex
):
def inner(offset
, _startindex
=startindex
,
_icis
=self
.is_char_in_string
):
return _icis(_startindex
+ "+%dc" % offset
)
def indent_region_event(self
, event
):
head
, tail
, chars
, lines
= self
.get_region()
for pos
in range(len(lines
)):
raw
, effective
= classifyws(line
, self
.tabwidth
)
effective
= effective
+ self
.indentwidth
lines
[pos
] = self
._make
_blanks
(effective
) + line
[raw
:]
self
.set_region(head
, tail
, chars
, lines
)
def dedent_region_event(self
, event
):
head
, tail
, chars
, lines
= self
.get_region()
for pos
in range(len(lines
)):
raw
, effective
= classifyws(line
, self
.tabwidth
)
effective
= max(effective
- self
.indentwidth
, 0)
lines
[pos
] = self
._make
_blanks
(effective
) + line
[raw
:]
self
.set_region(head
, tail
, chars
, lines
)
def comment_region_event(self
, event
):
head
, tail
, chars
, lines
= self
.get_region()
for pos
in range(len(lines
) - 1):
self
.set_region(head
, tail
, chars
, lines
)
def uncomment_region_event(self
, event
):
head
, tail
, chars
, lines
= self
.get_region()
for pos
in range(len(lines
)):
self
.set_region(head
, tail
, chars
, lines
)
def tabify_region_event(self
, event
):
head
, tail
, chars
, lines
= self
.get_region()
tabwidth
= self
._asktabwidth
()
for pos
in range(len(lines
)):
raw
, effective
= classifyws(line
, tabwidth
)
ntabs
, nspaces
= divmod(effective
, tabwidth
)
lines
[pos
] = '\t' * ntabs
+ ' ' * nspaces
+ line
[raw
:]
self
.set_region(head
, tail
, chars
, lines
)
def untabify_region_event(self
, event
):
head
, tail
, chars
, lines
= self
.get_region()
tabwidth
= self
._asktabwidth
()
for pos
in range(len(lines
)):
lines
[pos
] = lines
[pos
].expandtabs(tabwidth
)
self
.set_region(head
, tail
, chars
, lines
)
def toggle_tabs_event(self
, event
):
"Turn tabs " + ("on", "off")[self
.usetabs
] + "?",
self
.usetabs
= not self
.usetabs
# XXX this isn't bound to anything -- see class tabwidth comments
def change_tabwidth_event(self
, event
):
new
= self
._asktabwidth
()
self
.set_indentation_params(0, guess
=0)
def change_indentwidth_event(self
, event
):
"New indent width (2-16)",
initialvalue
=self
.indentwidth
,
if new
and new
!= self
.indentwidth
:
first
, last
= self
.get_selection_indices()
head
= text
.index(first
+ " linestart")
tail
= text
.index(last
+ "-1c lineend +1c")
head
= text
.index("insert linestart")
tail
= text
.index("insert lineend +1c")
chars
= text
.get(head
, tail
)
lines
= chars
.split("\n")
return head
, tail
, chars
, lines
def set_region(self
, head
, tail
, chars
, lines
):
newchars
= "\n".join(lines
)
text
.tag_remove("sel", "1.0", "end")
text
.mark_set("insert", head
)
text
.insert(head
, newchars
)
text
.tag_add("sel", head
, "insert")
# Make string that displays as n leading blanks.
def _make_blanks(self
, n
):
ntabs
, nspaces
= divmod(n
, self
.tabwidth
)
return '\t' * ntabs
+ ' ' * nspaces
# Delete from beginning of line to insert point, then reinsert
# column logical (meaning use tabs if appropriate) spaces.
def reindent_to(self
, column
):
if text
.compare("insert linestart", "!=", "insert"):
text
.delete("insert linestart", "insert")
text
.insert("insert", self
._make
_blanks
(column
))
"Spaces per tab? (2-16)",
initialvalue
=self
.indentwidth
,
maxvalue
=16) or self
.tabwidth
# Guess indentwidth from text content.
# Return guessed indentwidth. This should not be believed unless
# it's in a reasonable range (e.g., it will be 0 if no indented
opener
, indented
= IndentSearcher(self
.text
, self
.tabwidth
).run()
raw
, indentsmall
= classifyws(opener
, self
.tabwidth
)
raw
, indentlarge
= classifyws(indented
, self
.tabwidth
)
indentsmall
= indentlarge
= 0
return indentlarge
- indentsmall
# "line.col" -> line, as an int
# Look at the leading whitespace in s.
# Return pair (# of leading ws characters,
# effective # of leading blanks after expanding
# tabs to width tabwidth)
def classifyws(s
, tabwidth
):
effective
= effective
+ 1
effective
= (effective
// tabwidth
+ 1) * tabwidth
# .run() chews over the Text widget, looking for a block opener
# and the stmt following it. Returns a pair,
# (line containing block opener, line containing stmt)
# Either or both may be None.
def __init__(self
, text
, tabwidth
):
self
.i
= self
.finished
= 0
self
.blkopenline
= self
.indentedline
= None
if self
.text
.compare(mark
, ">=", "end"):
return self
.text
.get(mark
, mark
+ " lineend+1c")
def tokeneater(self
, type, token
, start
, end
, line
,
OPENERS
=('class', 'def', 'for', 'if', 'try', 'while')):
elif type == NAME
and token
in OPENERS
:
elif type == INDENT
and self
.blkopenline
:
save_tabsize
= _tokenize
.tabsize
_tokenize
.tabsize
= self
.tabwidth
_tokenize
.tokenize(self
.readline
, self
.tokeneater
)
except _tokenize
.TokenError
:
# since we cut off the tokenizer early, we can trigger
_tokenize
.tabsize
= save_tabsize
return self
.blkopenline
, self
.indentedline
### end autoindent code ###
# Helper to extract the underscore from a string, e.g.
# prepstr("Co_py") returns (2, "Copy").
def get_accelerator(keydefs
, eventname
):
keylist
= keydefs
.get(eventname
)
s
= re
.sub(r
"-[a-z]\b", lambda m
: m
.group().upper(), s
)
s
= re
.sub(r
"\b\w+\b", lambda m
: keynames
.get(m
.group(), m
.group()), s
)
s
= re
.sub("Key-", "", s
)
s
= re
.sub("Cancel","Ctrl-Break",s
) # dscherer@cmu.edu
s
= re
.sub("Control-", "Ctrl-", s
)
# Make sure that Tk's double-click and next/previous word
# operations use our definition of a word (i.e. an identifier)
tk
.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
tk
.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
tk
.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
edit
= EditorWindow(root
=root
, filename
=filename
)
edit
.set_close_hook(root
.quit
)
if __name__
== '__main__':