Uploaded Test files

This commit is contained in:
Batuhan Berk Başoğlu 2020-11-12 11:05:57 -05:00
parent f584ad9d97
commit 2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions

View file

@ -0,0 +1,528 @@
# Code that allows Pythonwin to pretend it is IDLE
# (at least as far as most IDLE extensions are concerned)
import string
import win32api
import win32ui
import win32con
import sys
from pywin.mfc.dialog import GetSimpleInput
from pywin import default_scintilla_encoding
wordchars = string.ascii_uppercase + string.ascii_lowercase + string.digits
class TextError(Exception): # When a TclError would normally be raised.
pass
class EmptyRange(Exception): # Internally raised.
pass
def GetIDLEModule(module):
try:
# First get it from Pythonwin it is exists.
modname = "pywin.idle." + module
__import__(modname)
except ImportError as details:
msg = "The IDLE extension '%s' can not be located.\r\n\r\n" \
"Please correct the installation and restart the" \
" application.\r\n\r\n%s" % (module, details)
win32ui.MessageBox(msg)
return None
mod=sys.modules[modname]
mod.TclError = TextError # A hack that can go soon!
return mod
# A class that is injected into the IDLE auto-indent extension.
# It allows for decent performance when opening a new file,
# as auto-indent uses the tokenizer module to determine indents.
# The default AutoIndent readline method works OK, but it goes through
# this layer of Tk index indirection for every single line. For large files
# without indents (and even small files with indents :-) it was pretty slow!
def fast_readline(self):
if self.finished:
val = ""
else:
if "_scint_lines" not in self.__dict__:
# XXX - note - assumes this is only called once the file is loaded!
self._scint_lines = self.text.edit.GetTextRange().split("\n")
sl = self._scint_lines
i = self.i = self.i + 1
if i >= len(sl):
val = ""
else:
val = sl[i]+"\n"
return val.encode(default_scintilla_encoding)
try:
GetIDLEModule("AutoIndent").IndentSearcher.readline = fast_readline
except AttributeError: # GetIDLEModule may return None
pass
# A class that attempts to emulate an IDLE editor window.
# Construct with a Pythonwin view.
class IDLEEditorWindow:
def __init__(self, edit):
self.edit = edit
self.text = TkText(edit)
self.extensions = {}
self.extension_menus = {}
def close(self):
self.edit = self.text = None
self.extension_menus = None
try:
for ext in self.extensions.values():
closer = getattr(ext, "close", None)
if closer is not None:
closer()
finally:
self.extensions = {}
def IDLEExtension(self, extension):
ext = self.extensions.get(extension)
if ext is not None: return ext
mod = GetIDLEModule(extension)
if mod is None: return None
klass = getattr(mod, extension)
ext = self.extensions[extension] = klass(self)
# Find and bind all the events defined in the extension.
events = [item for item in dir(klass) if item[-6:]=="_event"]
for event in events:
name = "<<%s>>" % (event[:-6].replace("_", "-"), )
self.edit.bindings.bind(name, getattr(ext, event))
return ext
def GetMenuItems(self, menu_name):
# Get all menu items for the menu name (eg, "edit")
bindings = self.edit.bindings
ret = []
for ext in self.extensions.values():
menudefs = getattr(ext, "menudefs", [])
for name, items in menudefs:
if name == menu_name:
for text, event in [item for item in items if item is not None]:
text = text.replace("&", "&&")
text = text.replace("_", "&")
ret.append((text, event))
return ret
######################################################################
# The IDLE "Virtual UI" methods that are exposed to the IDLE extensions.
#
def askinteger(self, caption, prompt, parent=None, initialvalue=0, minvalue=None, maxvalue=None):
while 1:
rc = GetSimpleInput(prompt, str(initialvalue), caption)
if rc is None: return 0 # Correct "cancel" semantics?
err = None
try:
rc = int(rc)
except ValueError:
err = "Please enter an integer"
if not err and minvalue is not None and rc < minvalue:
err = "Please enter an integer greater then or equal to %s" % (minvalue,)
if not err and maxvalue is not None and rc > maxvalue:
err = "Please enter an integer less then or equal to %s" % (maxvalue,)
if err:
win32ui.MessageBox(err, caption, win32con.MB_OK)
continue
return rc
def askyesno(self, caption, prompt, parent=None):
return win32ui.MessageBox(prompt, caption, win32con.MB_YESNO)==win32con.IDYES
######################################################################
# The IDLE "Virtual Text Widget" methods that are exposed to the IDLE extensions.
#
# Is character at text_index in a Python string? Return 0 for
# "guaranteed no", true for anything else.
def is_char_in_string(self, text_index):
# A helper for the code analyser - we need internal knowledge of
# the colorizer to get this information
# This assumes the colorizer has got to this point!
text_index = self.text._getoffset(text_index)
c = self.text.edit._GetColorizer()
if c and c.GetStringStyle(text_index) is None:
return 0
return 1
# 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):
try:
first = self.text.index("sel.first")
last = self.text.index("sel.last")
return first, last
except TextError:
return None, None
def set_tabwidth(self, width ):
self.edit.SCISetTabWidth(width)
def get_tabwidth(self):
return self.edit.GetTabWidth()
# A class providing the generic "Call Tips" interface
class CallTips:
def __init__(self, edit):
self.edit = edit
def showtip(self, tip_text):
self.edit.SCICallTipShow(tip_text)
def hidetip(self):
self.edit.SCICallTipCancel()
########################################
#
# Helpers for the TkText emulation.
def TkOffsetToIndex(offset, edit):
lineoff = 0
# May be 1 > actual end if we pretended there was a trailing '\n'
offset = min(offset, edit.GetTextLength())
line = edit.LineFromChar(offset)
lineIndex = edit.LineIndex(line)
return "%d.%d" % (line+1, offset-lineIndex)
def _NextTok(str, pos):
# Returns (token, endPos)
end = len(str)
if pos>=end: return None, 0
while pos < end and str[pos] in string.whitespace:
pos = pos + 1
# Special case for +-
if str[pos] in '+-':
return str[pos],pos+1
# Digits also a special case.
endPos = pos
while endPos < end and str[endPos] in string.digits+".":
endPos = endPos + 1
if pos!=endPos: return str[pos:endPos], endPos
endPos = pos
while endPos < end and str[endPos] not in string.whitespace + string.digits + "+-":
endPos = endPos + 1
if pos!=endPos: return str[pos:endPos], endPos
return None, 0
def TkIndexToOffset(bm, edit, marks):
base, nextTokPos = _NextTok(bm, 0)
if base is None: raise ValueError("Empty bookmark ID!")
if base.find(".")>0:
try:
line, col = base.split(".", 2)
if col=="first" or col=="last":
# Tag name
if line != "sel": raise ValueError("Tags arent here!")
sel = edit.GetSel()
if sel[0]==sel[1]:
raise EmptyRange
if col=="first":
pos = sel[0]
else:
pos = sel[1]
else:
# Lines are 1 based for tkinter
line = int(line)-1
if line > edit.GetLineCount():
pos = edit.GetTextLength()+1
else:
pos = edit.LineIndex(line)
if pos==-1: pos = edit.GetTextLength()
pos = pos + int(col)
except (ValueError, IndexError):
raise ValueError("Unexpected literal in '%s'" % base)
elif base == 'insert':
pos = edit.GetSel()[0]
elif base=='end':
pos = edit.GetTextLength()
# Pretend there is a trailing '\n' if necessary
if pos and edit.SCIGetCharAt(pos-1) != "\n":
pos = pos+1
else:
try:
pos = marks[base]
except KeyError:
raise ValueError("Unsupported base offset or undefined mark '%s'" % base)
while 1:
word, nextTokPos = _NextTok(bm, nextTokPos)
if word is None: break
if word in ['+','-']:
num, nextTokPos = _NextTok(bm, nextTokPos)
if num is None: raise ValueError("+/- operator needs 2 args")
what, nextTokPos = _NextTok(bm, nextTokPos)
if what is None: raise ValueError("+/- operator needs 2 args")
if what[0] != "c": raise ValueError("+/- only supports chars")
if word=='+':
pos = pos + int(num)
else:
pos = pos - int(num)
elif word=='wordstart':
while pos > 0 and edit.SCIGetCharAt(pos-1) in wordchars:
pos = pos - 1
elif word=='wordend':
end = edit.GetTextLength()
while pos < end and edit.SCIGetCharAt(pos) in wordchars:
pos = pos + 1
elif word=='linestart':
while pos > 0 and edit.SCIGetCharAt(pos-1) not in '\n\r':
pos = pos - 1
elif word=='lineend':
end = edit.GetTextLength()
while pos < end and edit.SCIGetCharAt(pos) not in '\n\r':
pos = pos + 1
else:
raise ValueError("Unsupported relative offset '%s'" % word)
return max(pos, 0) # Tkinter is tollerant of -ve indexes - we aren't
# A class that resembles an IDLE (ie, a Tk) text widget.
# Construct with an edit object (eg, an editor view)
class TkText:
def __init__(self, edit):
self.calltips = None
self.edit = edit
self.marks = {}
## def __getattr__(self, attr):
## if attr=="tk": return self # So text.tk.call works.
## if attr=="master": return None # ditto!
## raise AttributeError, attr
## def __getitem__(self, item):
## if item=="tabs":
## size = self.edit.GetTabWidth()
## if size==8: return "" # Tk default
## return size # correct semantics?
## elif item=="font": # Used for measurements we dont need to do!
## return "Dont know the font"
## raise IndexError, "Invalid index '%s'" % item
def make_calltip_window(self):
if self.calltips is None:
self.calltips = CallTips(self.edit)
return self.calltips
def _getoffset(self, index):
return TkIndexToOffset(index, self.edit, self.marks)
def _getindex(self, off):
return TkOffsetToIndex(off, self.edit)
def _fix_indexes(self, start, end):
# first some magic to handle skipping over utf8 extended chars.
while start > 0 and ord(self.edit.SCIGetCharAt(start)) & 0xC0 == 0x80:
start -= 1
while end < self.edit.GetTextLength() and ord(self.edit.SCIGetCharAt(end)) & 0xC0 == 0x80:
end += 1
# now handling fixing \r\n->\n disparities...
if start>0 and self.edit.SCIGetCharAt(start)=='\n' and self.edit.SCIGetCharAt(start-1)=='\r':
start = start - 1
if end < self.edit.GetTextLength() and self.edit.SCIGetCharAt(end-1)=='\r' and self.edit.SCIGetCharAt(end)=='\n':
end = end + 1
return start, end
## def get_tab_width(self):
## return self.edit.GetTabWidth()
## def call(self, *rest):
## # Crap to support Tk measurement hacks for tab widths
## if rest[0] != "font" or rest[1] != "measure":
## raise ValueError, "Unsupport call type"
## return len(rest[5])
## def configure(self, **kw):
## for name, val in kw.items():
## if name=="tabs":
## self.edit.SCISetTabWidth(int(val))
## else:
## raise ValueError, "Unsupported configuration item %s" % kw
def bind(self, binding, handler):
self.edit.bindings.bind(binding, handler)
def get(self, start, end = None):
try:
start = self._getoffset(start)
if end is None:
end = start+1
else:
end = self._getoffset(end)
except EmptyRange:
return ""
# Simple semantic checks to conform to the Tk text interface
if end <= start: return ""
max = self.edit.GetTextLength()
checkEnd = 0
if end > max:
end = max
checkEnd = 1
start, end = self._fix_indexes(start, end)
ret = self.edit.GetTextRange(start, end)
# pretend a trailing '\n' exists if necessary.
if checkEnd and (not ret or ret[-1] != '\n'): ret = ret + '\n'
return ret.replace("\r", "")
def index(self, spec):
try:
return self._getindex(self._getoffset(spec))
except EmptyRange:
return ""
def insert(self, pos, text):
try:
pos = self._getoffset(pos)
except EmptyRange:
raise TextError("Empty range")
self.edit.SetSel((pos, pos))
# IDLE only deals with "\n" - we will be nicer
bits = text.split('\n')
self.edit.SCIAddText(bits[0])
for bit in bits[1:]:
self.edit.SCINewline()
self.edit.SCIAddText(bit)
def delete(self, start, end=None):
try:
start = self._getoffset(start)
if end is not None: end = self._getoffset(end)
except EmptyRange:
raise TextError("Empty range")
# If end is specified and == start, then we must delete nothing.
if start==end: return
# If end is not specified, delete one char
if end is None:
end = start+1
else:
# Tk says not to delete in this case, but our control would.
if end<start: return
if start==self.edit.GetTextLength(): return # Nothing to delete.
old = self.edit.GetSel()[0] # Lose a selection
# Hack for partial '\r\n' and UTF-8 char removal
start, end = self._fix_indexes(start, end)
self.edit.SetSel((start, end))
self.edit.Clear()
if old>=start and old<end:
old=start
elif old>=end:
old = old - (end-start)
self.edit.SetSel(old)
def bell(self):
win32api.MessageBeep()
def see(self, pos):
# Most commands we use in Scintilla actually force the selection
# to be seen, making this unnecessary.
pass
def mark_set(self, name, pos):
try:
pos = self._getoffset(pos)
except EmptyRange:
raise TextError("Empty range '%s'" % pos)
if name == "insert":
self.edit.SetSel( pos )
else:
self.marks[name]=pos
def tag_add(self, name, start, end):
if name != "sel": raise ValueError("Only sel tag is supported")
try:
start = self._getoffset(start)
end = self._getoffset(end)
except EmptyRange:
raise TextError("Empty range")
self.edit.SetSel( start, end )
def tag_remove(self, name, start, end):
if name !="sel" or start != "1.0" or end != "end":
raise ValueError("Cant remove this tag")
# Turn the sel into a cursor
self.edit.SetSel(self.edit.GetSel()[0])
def compare(self, i1, op, i2):
try:
i1=self._getoffset(i1)
except EmptyRange:
i1 = ""
try:
i2=self._getoffset(i2)
except EmptyRange:
i2 = ""
return eval("%d%s%d" % (i1,op,i2))
def undo_block_start(self):
self.edit.SCIBeginUndoAction()
def undo_block_stop(self):
self.edit.SCIEndUndoAction()
######################################################################
#
# Test related code.
#
######################################################################
def TestCheck(index, edit, expected=None):
rc = TkIndexToOffset(index, edit, {})
if rc != expected:
print("ERROR: Index", index,", expected", expected, "but got", rc)
def TestGet(fr, to, t, expected):
got = t.get(fr, to)
if got != expected:
print("ERROR: get(%s, %s) expected %s, but got %s" % (repr(fr), repr(to), repr(expected), repr(got)))
def test():
import pywin.framework.editor
d=pywin.framework.editor.editorTemplate.OpenDocumentFile(None)
e=d.GetFirstView()
t = TkText(e)
e.SCIAddText("hi there how\nare you today\r\nI hope you are well")
e.SetSel((4,4))
skip = """
TestCheck("insert", e, 4)
TestCheck("insert wordstart", e, 3)
TestCheck("insert wordend", e, 8)
TestCheck("insert linestart", e, 0)
TestCheck("insert lineend", e, 12)
TestCheck("insert + 4 chars", e, 8)
TestCheck("insert +4c", e, 8)
TestCheck("insert - 2 chars", e, 2)
TestCheck("insert -2c", e, 2)
TestCheck("insert-2c", e, 2)
TestCheck("insert-2 c", e, 2)
TestCheck("insert- 2c", e, 2)
TestCheck("1.1", e, 1)
TestCheck("1.0", e, 0)
TestCheck("2.0", e, 13)
try:
TestCheck("sel.first", e, 0)
print "*** sel.first worked with an empty selection"
except TextError:
pass
e.SetSel((4,5))
TestCheck("sel.first- 2c", e, 2)
TestCheck("sel.last- 2c", e, 3)
"""
# Check EOL semantics
e.SetSel((4,4))
TestGet("insert lineend", "insert lineend +1c", t, "\n")
e.SetSel((20, 20))
TestGet("insert lineend", "insert lineend +1c", t, "\n")
e.SetSel((35, 35))
TestGet("insert lineend", "insert lineend +1c", t, "\n")
class IDLEWrapper:
def __init__(self, control):
self.text = control
def IDLETest(extension):
import sys, os
modname = "pywin.idle." + extension
__import__(modname)
mod=sys.modules[modname]
mod.TclError = TextError
klass = getattr(mod, extension)
# Create a new Scintilla Window.
import pywin.framework.editor
d=pywin.framework.editor.editorTemplate.OpenDocumentFile(None)
v=d.GetFirstView()
fname=os.path.splitext(__file__)[0] + ".py"
v.SCIAddText(open(fname).read())
d.SetModifiedFlag(0)
r=klass( IDLEWrapper( TkText(v) ) )
return r
if __name__=='__main__':
test()

View file

@ -0,0 +1 @@
# package init.

View file

@ -0,0 +1,172 @@
from . import IDLEenvironment
import string
import win32ui
import win32api
import win32con
from . import keycodes
import sys
import traceback
HANDLER_ARGS_GUESS=0
HANDLER_ARGS_NATIVE=1
HANDLER_ARGS_IDLE=2
HANDLER_ARGS_EXTENSION=3
next_id = 5000
event_to_commands = {}# dict of integer IDs to event names.
command_to_events = {}# dict of event names to int IDs
def assign_command_id(event, id = 0):
global next_id
if id == 0:
id = event_to_commands.get(event, 0)
if id == 0:
id = next_id
next_id = next_id + 1
# Only map the ones we allocated - specified ones are assumed to have a handler
command_to_events[id] = event
event_to_commands[event] = id
return id
class SendCommandHandler:
def __init__(self, cmd):
self.cmd = cmd
def __call__(self, *args):
win32ui.GetMainFrame().SendMessage(win32con.WM_COMMAND, self.cmd)
class Binding:
def __init__(self, handler, handler_args_type):
self.handler = handler
self.handler_args_type = handler_args_type
class BindingsManager:
def __init__(self, parent_view):
self.parent_view = parent_view
self.bindings = {} # dict of Binding instances.
self.keymap = {}
def prepare_configure(self):
self.keymap = {}
def complete_configure(self):
for id in command_to_events.keys():
self.parent_view.HookCommand(self._OnCommand, id)
def close(self):
self.parent_view = self.bindings = self.keymap = None
def report_error(self, problem):
try:
win32ui.SetStatusText(problem, 1)
except win32ui.error:
# No status bar!
print(problem)
def update_keymap(self, keymap):
self.keymap.update(keymap)
def bind(self, event, handler, handler_args_type = HANDLER_ARGS_GUESS, cid = 0):
if handler is None:
handler = SendCommandHandler(cid)
self.bindings[event] = self._new_binding(handler, handler_args_type)
self.bind_command(event, cid)
def bind_command(self, event, id = 0):
"Binds an event to a Windows control/command ID"
id = assign_command_id(event, id)
return id
def get_command_id(self, event):
id = event_to_commands.get(event)
if id is None:
# See if we even have an event of that name!?
if event not in self.bindings:
return None
id = self.bind_command(event)
return id
def _OnCommand(self, id, code):
event = command_to_events.get(id)
if event is None:
self.report_error("No event associated with event ID %d" % id)
return 1
return self.fire(event)
def _new_binding(self, event, handler_args_type):
return Binding(event, handler_args_type)
def _get_IDLE_handler(self, ext, handler):
try:
instance = self.parent_view.idle.IDLEExtension(ext)
name = handler.replace("-", "_") + "_event"
return getattr(instance, name)
except (ImportError, AttributeError):
msg = "Can not find event '%s' in IDLE extension '%s'" % (handler, ext)
self.report_error(msg)
return None
def fire(self, event, event_param = None):
# Fire the specified event. Result is native Pythonwin result
# (ie, 1==pass one, 0 or None==handled)
# First look up the event directly - if there, we are set.
binding = self.bindings.get(event)
if binding is None:
# If possible, find it!
# A native method name
handler = getattr(self.parent_view, event + "Event", None)
if handler is None:
# Can't decide if I should report an error??
self.report_error("The event name '%s' can not be found." % event)
# Either way, just let the default handlers grab it.
return 1
binding = self._new_binding(handler, HANDLER_ARGS_NATIVE)
# Cache it.
self.bindings[event] = binding
handler_args_type = binding.handler_args_type
# Now actually fire it.
if handler_args_type==HANDLER_ARGS_GUESS:
# Can't be native, as natives are never added with "guess".
# Must be extension or IDLE.
if event[0]=="<":
handler_args_type = HANDLER_ARGS_IDLE
else:
handler_args_type = HANDLER_ARGS_EXTENSION
try:
if handler_args_type==HANDLER_ARGS_EXTENSION:
args = self.parent_view.idle, event_param
else:
args = (event_param,)
rc = binding.handler(*args)
if handler_args_type==HANDLER_ARGS_IDLE:
# Convert to our return code.
if rc in [None, "break"]:
rc = 0
else:
rc = 1
except:
message = "Firing event '%s' failed." % event
print(message)
traceback.print_exc()
self.report_error(message)
rc = 1 # Let any default handlers have a go!
return rc
def fire_key_event(self, msg):
key = msg[2]
keyState = 0
if win32api.GetKeyState(win32con.VK_CONTROL) & 0x8000:
keyState = keyState | win32con.RIGHT_CTRL_PRESSED | win32con.LEFT_CTRL_PRESSED
if win32api.GetKeyState(win32con.VK_SHIFT) & 0x8000:
keyState = keyState | win32con.SHIFT_PRESSED
if win32api.GetKeyState(win32con.VK_MENU) & 0x8000:
keyState = keyState | win32con.LEFT_ALT_PRESSED | win32con.RIGHT_ALT_PRESSED
keyinfo = key, keyState
# Special hacks for the dead-char key on non-US keyboards.
# (XXX - which do not work :-(
event = self.keymap.get( keyinfo )
if event is None:
return 1
return self.fire(event, None)

View file

@ -0,0 +1,322 @@
# config.py - deals with loading configuration information.
# Loads config data from a .cfg file. Also caches the compiled
# data back into a .cfc file.
# If you are wondering how to avoid needing .cfg files (eg,
# if you are freezing Pythonwin etc) I suggest you create a
# .py file, and put the config info in a docstring. Then
# pass a CStringIO file (rather than a filename) to the
# config manager.
import sys
import string
from . import keycodes
import marshal
import stat
import os
import types
import traceback
import pywin
import glob
import imp
import win32api
debugging = 0
if debugging:
import win32traceutil # Some trace statements fire before the interactive window is open.
def trace(*args):
sys.stderr.write(" ".join(map(str, args)) + "\n")
else:
trace = lambda *args: None
compiled_config_version = 3
def split_line(line, lineno):
comment_pos = line.find("#")
if comment_pos>=0: line = line[:comment_pos]
sep_pos = line.rfind("=")
if sep_pos == -1:
if line.strip():
print("Warning: Line %d: %s is an invalid entry" % (lineno, repr(line)))
return None, None
return "", ""
return line[:sep_pos].strip(), line[sep_pos+1:].strip()
def get_section_header(line):
# Returns the section if the line is a section header, else None
if line[0] == "[":
end = line.find("]")
if end==-1: end=len(line)
rc = line[1:end].lower()
try:
i = rc.index(":")
return rc[:i], rc[i+1:]
except ValueError:
return rc, ""
return None, None
def find_config_file(f):
return os.path.join(pywin.__path__[0], f + ".cfg")
def find_config_files():
return [os.path.split(x)[1]
for x in [os.path.splitext(x)[0] for x in glob.glob(os.path.join(pywin.__path__[0], "*.cfg"))]
]
class ConfigManager:
def __init__(self, f):
self.filename = "unknown"
self.last_error = None
self.key_to_events = {}
if hasattr(f, "readline"):
fp = f
self.filename = "<config string>"
compiled_name = None
else:
try:
f = find_config_file(f)
src_stat = os.stat(f)
except os.error:
self.report_error("Config file '%s' not found" % f)
return
self.filename = f
self.basename = os.path.basename(f)
trace("Loading configuration", self.basename)
compiled_name = os.path.splitext(f)[0] + ".cfc"
try:
cf = open(compiled_name, "rb")
try:
ver = marshal.load(cf)
ok = compiled_config_version == ver
if ok:
kblayoutname = marshal.load(cf)
magic = marshal.load(cf)
size = marshal.load(cf)
mtime = marshal.load(cf)
if magic == imp.get_magic() and \
win32api.GetKeyboardLayoutName() == kblayoutname and \
src_stat[stat.ST_MTIME] == mtime and \
src_stat[stat.ST_SIZE] == size:
self.cache = marshal.load(cf)
trace("Configuration loaded cached", compiled_name)
return # We are ready to roll!
finally:
cf.close()
except (os.error, IOError, EOFError):
pass
fp = open(f)
self.cache = {}
lineno = 1
line = fp.readline()
while line:
# Skip to the next section (maybe already there!)
section, subsection = get_section_header(line)
while line and section is None:
line = fp.readline()
if not line: break
lineno = lineno + 1
section, subsection = get_section_header(line)
if not line: break
if section=="keys":
line, lineno = self._load_keys(subsection, fp, lineno)
elif section == "extensions":
line, lineno = self._load_extensions(subsection, fp, lineno)
elif section == "idle extensions":
line, lineno = self._load_idle_extensions(subsection, fp, lineno)
elif section == "general":
line, lineno = self._load_general(subsection, fp, lineno)
else:
self.report_error("Unrecognised section header '%s:%s'" % (section,subsection))
line = fp.readline()
lineno = lineno + 1
# Check critical data.
if not self.cache.get("keys"):
self.report_error("No keyboard definitions were loaded")
if not self.last_error and compiled_name:
try:
cf = open(compiled_name, "wb")
marshal.dump(compiled_config_version, cf)
marshal.dump(win32api.GetKeyboardLayoutName(), cf)
marshal.dump(imp.get_magic(), cf)
marshal.dump(src_stat[stat.ST_SIZE], cf)
marshal.dump(src_stat[stat.ST_MTIME], cf)
marshal.dump(self.cache, cf)
cf.close()
except (IOError, EOFError):
pass # Ignore errors - may be read only.
def configure(self, editor, subsections = None):
# Execute the extension code, and find any events.
# First, we "recursively" connect any we are based on.
if subsections is None: subsections = []
subsections = [''] + subsections
general = self.get_data("general")
if general:
parents = general.get("based on", [])
for parent in parents:
trace("Configuration based on", parent, "- loading.")
parent = self.__class__(parent)
parent.configure(editor, subsections)
if parent.last_error:
self.report_error(parent.last_error)
bindings = editor.bindings
codeob = self.get_data("extension code")
if codeob is not None:
ns = {}
try:
exec(codeob, ns)
except:
traceback.print_exc()
self.report_error("Executing extension code failed")
ns = None
if ns:
num = 0
for name, func in list(ns.items()):
if type(func)==types.FunctionType and name[:1] != '_':
bindings.bind(name, func)
num = num + 1
trace("Configuration Extension code loaded", num, "events")
# Load the idle extensions
for subsection in subsections:
for ext in self.get_data("idle extensions", {}).get(subsection, []):
try:
editor.idle.IDLEExtension(ext)
trace("Loaded IDLE extension", ext)
except:
self.report_error("Can not load the IDLE extension '%s'" % ext)
# Now bind up the key-map (remembering a reverse map
subsection_keymap = self.get_data("keys")
num_bound = 0
for subsection in subsections:
keymap = subsection_keymap.get(subsection, {})
bindings.update_keymap(keymap)
num_bound = num_bound + len(keymap)
trace("Configuration bound", num_bound, "keys")
def get_key_binding(self, event, subsections = None):
if subsections is None: subsections = []
subsections = [''] + subsections
subsection_keymap = self.get_data("keys")
for subsection in subsections:
map = self.key_to_events.get(subsection)
if map is None: # Build it
map = {}
keymap = subsection_keymap.get(subsection, {})
for key_info, map_event in list(keymap.items()):
map[map_event] = key_info
self.key_to_events[subsection] = map
info = map.get(event)
if info is not None:
return keycodes.make_key_name( info[0], info[1] )
return None
def report_error(self, msg):
self.last_error = msg
print("Error in %s: %s" % (self.filename, msg))
def report_warning(self, msg):
print("Warning in %s: %s" % (self.filename, msg))
def _readline(self, fp, lineno, bStripComments = 1):
line = fp.readline()
lineno = lineno + 1
if line:
bBreak = get_section_header(line)[0] is not None # A new section is starting
if bStripComments and not bBreak:
pos = line.find("#")
if pos>=0: line=line[:pos]+"\n"
else:
bBreak=1
return line, lineno, bBreak
def get_data(self, name, default=None):
return self.cache.get(name, default)
def _save_data(self, name, data):
self.cache[name] = data
return data
def _load_general(self, sub_section, fp, lineno):
map = {}
while 1:
line, lineno, bBreak = self._readline(fp, lineno)
if bBreak: break
key, val = split_line(line, lineno)
if not key: continue
key = key.lower()
l = map.get(key, [])
l.append(val)
map[key]=l
self._save_data("general", map)
return line, lineno
def _load_keys(self, sub_section, fp, lineno):
# Builds a nested dictionary of
# (scancode, flags) = event_name
main_map = self.get_data("keys", {})
map = main_map.get(sub_section, {})
while 1:
line, lineno, bBreak = self._readline(fp, lineno)
if bBreak: break
key, event = split_line(line, lineno)
if not event: continue
sc, flag = keycodes.parse_key_name(key)
if sc is None:
self.report_warning("Line %d: Invalid key name '%s'" % (lineno, key))
else:
map[sc, flag] = event
main_map[sub_section] = map
self._save_data("keys", main_map)
return line, lineno
def _load_extensions(self, sub_section, fp, lineno):
start_lineno = lineno
lines = []
while 1:
line, lineno, bBreak = self._readline(fp, lineno, 0)
if bBreak: break
lines.append(line)
try:
c = compile(
"\n" * start_lineno + # produces correct tracebacks
"".join(lines), self.filename, "exec")
self._save_data("extension code", c)
except SyntaxError as details:
errlineno = details.lineno + start_lineno
# Should handle syntax errors better here, and offset the lineno.
self.report_error("Compiling extension code failed:\r\nFile: %s\r\nLine %d\r\n%s" \
% (details.filename, errlineno, details.msg))
return line, lineno
def _load_idle_extensions(self, sub_section, fp, lineno):
extension_map = self.get_data("idle extensions")
if extension_map is None: extension_map = {}
extensions = []
while 1:
line, lineno, bBreak = self._readline(fp, lineno)
if bBreak: break
line = line.strip()
if line:
extensions.append(line)
extension_map[sub_section] = extensions
self._save_data("idle extensions", extension_map)
return line, lineno
def test():
import time
start = time.clock()
f="default"
cm = ConfigManager(f)
map = cm.get_data("keys")
took = time.clock()-start
print("Loaded %s items in %.4f secs" % (len(map), took))
if __name__=='__main__':
test()

View file

@ -0,0 +1,266 @@
from pywin.mfc import dialog
import win32api
import win32con
import win32ui
import copy
import string
from . import scintillacon
# Used to indicate that style should use default color
from win32con import CLR_INVALID
######################################################
# Property Page for syntax formatting options
# The standard 16 color VGA palette should always be possible
paletteVGA = (
("Black", win32api.RGB(0,0,0)),
("Navy", win32api.RGB(0,0,128)),
("Green", win32api.RGB(0,128,0)),
("Cyan", win32api.RGB(0,128,128)),
("Maroon", win32api.RGB(128,0,0)),
("Purple", win32api.RGB(128,0,128)),
("Olive", win32api.RGB(128,128,0)),
("Gray", win32api.RGB(128,128,128)),
("Silver", win32api.RGB(192,192,192)),
("Blue", win32api.RGB(0,0,255)),
("Lime", win32api.RGB(0,255,0)),
("Aqua", win32api.RGB(0,255,255)),
("Red", win32api.RGB(255,0,0)),
("Fuchsia", win32api.RGB(255,0,255)),
("Yellow", win32api.RGB(255,255,0)),
("White", win32api.RGB(255,255,255)),
# and a few others will generally be possible.
("DarkGrey", win32api.RGB(64,64,64)),
("PurpleBlue", win32api.RGB(64,64,192)),
("DarkGreen", win32api.RGB(0,96,0)),
("DarkOlive", win32api.RGB(128,128,64)),
("MediumBlue", win32api.RGB(0,0,192)),
("DarkNavy", win32api.RGB(0,0,96)),
("Magenta", win32api.RGB(96,0,96)),
("OffWhite", win32api.RGB(255,255,220)),
("LightPurple", win32api.RGB(220,220,255)),
("<Default>", win32con.CLR_INVALID)
)
class ScintillaFormatPropertyPage(dialog.PropertyPage):
def __init__(self, scintillaClass = None, caption = 0):
self.scintillaClass = scintillaClass
dialog.PropertyPage.__init__(self, win32ui.IDD_PP_FORMAT, caption=caption)
def OnInitDialog(self):
try:
if self.scintillaClass is None:
from . import control
sc = control.CScintillaEdit
else:
sc = self.scintillaClass
self.scintilla = sc()
style = win32con.WS_CHILD | win32con.WS_VISIBLE | win32con.ES_MULTILINE
# Convert the rect size
rect = self.MapDialogRect( (5, 5, 120, 75))
self.scintilla.CreateWindow(style, rect, self, 111)
self.HookNotify(self.OnBraceMatch, scintillacon.SCN_CHECKBRACE)
self.scintilla.HookKeyStroke(self.OnEsc, 27)
self.scintilla.SCISetViewWS(1)
self.pos_bstart = self.pos_bend = self.pos_bbad = 0
colorizer = self.scintilla._GetColorizer()
text = colorizer.GetSampleText()
items = text.split('|', 2)
pos = len(items[0])
self.scintilla.SCIAddText(''.join(items))
self.scintilla.SetSel(pos, pos)
self.scintilla.ApplyFormattingStyles()
self.styles = self.scintilla._GetColorizer().styles
self.cbo = self.GetDlgItem(win32ui.IDC_COMBO1)
for c in paletteVGA:
self.cbo.AddString(c[0])
self.cboBoldItalic = self.GetDlgItem(win32ui.IDC_COMBO2)
for item in ["Bold Italic", "Bold", "Italic", "Regular"]:
self.cboBoldItalic.InsertString(0, item)
self.butIsDefault = self.GetDlgItem(win32ui.IDC_CHECK1)
self.butIsDefaultBackground = self.GetDlgItem(win32ui.IDC_CHECK2)
self.listbox = self.GetDlgItem(win32ui.IDC_LIST1)
self.HookCommand(self.OnListCommand, win32ui.IDC_LIST1)
names = list(self.styles.keys())
names.sort()
for name in names:
if self.styles[name].aliased is None:
self.listbox.AddString(name)
self.listbox.SetCurSel(0)
idc = win32ui.IDC_RADIO1
if not self.scintilla._GetColorizer().bUseFixed: idc = win32ui.IDC_RADIO2
self.GetDlgItem(idc).SetCheck(1)
self.UpdateUIForStyle(self.styles[names[0]])
self.scintilla.HookFormatter(self)
self.HookCommand(self.OnButDefaultFixedFont, win32ui.IDC_BUTTON1)
self.HookCommand(self.OnButDefaultPropFont, win32ui.IDC_BUTTON2)
self.HookCommand(self.OnButThisFont, win32ui.IDC_BUTTON3)
self.HookCommand(self.OnButUseDefaultFont, win32ui.IDC_CHECK1)
self.HookCommand(self.OnButThisBackground, win32ui.IDC_BUTTON4)
self.HookCommand(self.OnButUseDefaultBackground, win32ui.IDC_CHECK2)
self.HookCommand(self.OnStyleUIChanged, win32ui.IDC_COMBO1)
self.HookCommand(self.OnStyleUIChanged, win32ui.IDC_COMBO2)
self.HookCommand(self.OnButFixedOrDefault, win32ui.IDC_RADIO1)
self.HookCommand(self.OnButFixedOrDefault, win32ui.IDC_RADIO2)
except:
import traceback
traceback.print_exc()
def OnEsc(self, ch):
self.GetParent().EndDialog(win32con.IDCANCEL)
def OnBraceMatch(self, std, extra):
import pywin.scintilla.view
pywin.scintilla.view.DoBraceMatch(self.scintilla)
def GetSelectedStyle(self):
return self.styles[self.listbox.GetText(self.listbox.GetCurSel())]
def _DoButDefaultFont(self, extra_flags, attr):
baseFormat = getattr(self.scintilla._GetColorizer(), attr)
flags = extra_flags | win32con.CF_SCREENFONTS | win32con.CF_EFFECTS | win32con.CF_FORCEFONTEXIST
d=win32ui.CreateFontDialog(baseFormat, flags, None, self)
if d.DoModal()==win32con.IDOK:
setattr(self.scintilla._GetColorizer(), attr, d.GetCharFormat())
self.OnStyleUIChanged(0, win32con.BN_CLICKED)
def OnButDefaultFixedFont(self, id, code):
if code==win32con.BN_CLICKED:
self._DoButDefaultFont(win32con.CF_FIXEDPITCHONLY, "baseFormatFixed")
return 1
def OnButDefaultPropFont(self, id, code):
if code==win32con.BN_CLICKED:
self._DoButDefaultFont(win32con.CF_SCALABLEONLY, "baseFormatProp")
return 1
def OnButFixedOrDefault(self, id, code):
if code==win32con.BN_CLICKED:
bUseFixed = id == win32ui.IDC_RADIO1
self.GetDlgItem(win32ui.IDC_RADIO1).GetCheck() != 0
self.scintilla._GetColorizer().bUseFixed = bUseFixed
self.scintilla.ApplyFormattingStyles(0)
return 1
def OnButThisFont(self, id, code):
if code==win32con.BN_CLICKED:
flags = win32con.CF_SCREENFONTS | win32con.CF_EFFECTS | win32con.CF_FORCEFONTEXIST
style = self.GetSelectedStyle()
# If the selected style is based on the default, we need to apply
# the default to it.
def_format = self.scintilla._GetColorizer().GetDefaultFormat()
format = style.GetCompleteFormat(def_format)
d=win32ui.CreateFontDialog(format, flags, None, self)
if d.DoModal()==win32con.IDOK:
style.format = d.GetCharFormat()
self.scintilla.ApplyFormattingStyles(0)
return 1
def OnButUseDefaultFont(self, id, code):
if code == win32con.BN_CLICKED:
isDef = self.butIsDefault.GetCheck()
self.GetDlgItem(win32ui.IDC_BUTTON3).EnableWindow(not isDef)
if isDef: # Being reset to the default font.
style = self.GetSelectedStyle()
style.ForceAgainstDefault()
self.UpdateUIForStyle(style)
self.scintilla.ApplyFormattingStyles(0)
else:
# User wants to override default -
# do nothing!
pass
def OnButThisBackground(self, id, code):
if code==win32con.BN_CLICKED:
style = self.GetSelectedStyle()
bg = win32api.RGB(0xff, 0xff, 0xff)
if style.background != CLR_INVALID:
bg = style.background
d=win32ui.CreateColorDialog(bg, 0, self)
if d.DoModal()==win32con.IDOK:
style.background = d.GetColor()
self.scintilla.ApplyFormattingStyles(0)
return 1
def OnButUseDefaultBackground(self, id, code):
if code == win32con.BN_CLICKED:
isDef = self.butIsDefaultBackground.GetCheck()
self.GetDlgItem(win32ui.IDC_BUTTON4).EnableWindow(not isDef)
if isDef: # Being reset to the default color
style = self.GetSelectedStyle()
style.background = style.default_background
self.UpdateUIForStyle(style)
self.scintilla.ApplyFormattingStyles(0)
else:
# User wants to override default -
# do nothing!
pass
def OnListCommand(self, id, code):
if code==win32con.LBN_SELCHANGE:
style = self.GetSelectedStyle()
self.UpdateUIForStyle(style)
return 1
def UpdateUIForStyle(self, style ):
format = style.format
sel = 0
for c in paletteVGA:
if format[4] == c[1]:
# print "Style", style.name, "is", c[0]
break
sel = sel + 1
else:
sel = -1
self.cbo.SetCurSel(sel)
self.butIsDefault.SetCheck(style.IsBasedOnDefault())
self.GetDlgItem(win32ui.IDC_BUTTON3).EnableWindow(not style.IsBasedOnDefault())
self.butIsDefaultBackground.SetCheck(style.background == style.default_background)
self.GetDlgItem(win32ui.IDC_BUTTON4).EnableWindow(style.background != style.default_background)
bold = format[1] & win32con.CFE_BOLD != 0; italic = format[1] & win32con.CFE_ITALIC != 0
self.cboBoldItalic.SetCurSel( bold*2 + italic )
def OnStyleUIChanged(self, id, code):
if code in [win32con.BN_CLICKED, win32con.CBN_SELCHANGE]:
style = self.GetSelectedStyle()
self.ApplyUIFormatToStyle(style)
self.scintilla.ApplyFormattingStyles(0)
return 0
return 1
def ApplyUIFormatToStyle(self, style):
format = style.format
color = paletteVGA[self.cbo.GetCurSel()]
effect = 0
sel = self.cboBoldItalic.GetCurSel()
if sel==0:
effect = 0
elif sel==1:
effect = win32con.CFE_ITALIC
elif sel==2:
effect = win32con.CFE_BOLD
else:
effect = win32con.CFE_BOLD | win32con.CFE_ITALIC
maskFlags=format[0]|win32con.CFM_COLOR|win32con.CFM_BOLD|win32con.CFM_ITALIC
style.format = (maskFlags, effect, style.format[2], style.format[3], color[1]) + style.format[5:]
def OnOK(self):
self.scintilla._GetColorizer().SavePreferences()
return 1
def test():
page = ColorEditorPropertyPage()
sheet = pywin.mfc.dialog.PropertySheet("Test")
sheet.AddPage(page)
sheet.CreateWindow()

View file

@ -0,0 +1,434 @@
# An Python interface to the Scintilla control.
#
# Exposes Python classes that allow you to use Scintilla as
# a "standard" MFC edit control (eg, control.GetTextLength(), control.GetSel()
# plus many Scintilla specific features (eg control.SCIAddStyledText())
from pywin.mfc import window
from pywin import default_scintilla_encoding
import win32con
import win32ui
import win32api
import array
import struct
import string
import os
from . import scintillacon
# Load Scintilla.dll to get access to the control.
# We expect to find this in the same directory as win32ui.pyd
dllid = None
if win32ui.debug: # If running _d version of Pythonwin...
try:
dllid = win32api.LoadLibrary(os.path.join(os.path.split(win32ui.__file__)[0], "Scintilla_d.DLL"))
except win32api.error: # Not there - we dont _need_ a debug ver, so ignore this error.
pass
if dllid is None:
try:
dllid = win32api.LoadLibrary(os.path.join(os.path.split(win32ui.__file__)[0], "Scintilla.DLL"))
except win32api.error:
pass
if dllid is None:
# Still not there - lets see if Windows can find it by searching?
dllid = win32api.LoadLibrary("Scintilla.DLL")
# null_byte is str in py2k, bytes on py3k
null_byte = "\0".encode('ascii')
## These are from Richedit.h - need to add to win32con or commctrl
EM_GETTEXTRANGE = 1099
EM_EXLINEFROMCHAR = 1078
EM_FINDTEXTEX = 1103
EM_GETSELTEXT = 1086
EM_EXSETSEL = win32con.WM_USER + 55
class ScintillaNotification:
def __init__(self, **args):
self.__dict__.update(args)
class ScintillaControlInterface:
def SCIUnpackNotifyMessage(self, msg):
format = "iiiiPiiiPPiiii"
bytes = win32ui.GetBytes( msg, struct.calcsize(format) )
position, ch, modifiers, modificationType, text_ptr, \
length, linesAdded, msg, wParam, lParam, line, \
foldLevelNow, foldLevelPrev, margin \
= struct.unpack(format, bytes)
return ScintillaNotification(position=position,ch=ch,
modifiers=modifiers, modificationType=modificationType,
text_ptr = text_ptr, length=length, linesAdded=linesAdded,
msg = msg, wParam = wParam, lParam = lParam,
line = line, foldLevelNow = foldLevelNow, foldLevelPrev = foldLevelPrev,
margin = margin)
def SCIAddText(self, text):
self.SendMessage(scintillacon.SCI_ADDTEXT, text.encode(default_scintilla_encoding))
def SCIAddStyledText(self, text, style = None):
# If style is None, text is assumed to be a "native" Scintilla buffer.
# If style is specified, text is a normal string, and the style is
# assumed to apply to the entire string.
if style is not None:
text = list(map(lambda char, style=style: char+chr(style), text))
text = ''.join(text)
self.SendMessage(scintillacon.SCI_ADDSTYLEDTEXT, text.encode(default_scintilla_encoding))
def SCIInsertText(self, text, pos=-1):
# SCIInsertText allows unicode or bytes - but if they are bytes,
# the caller must ensure it is encoded correctly.
if isinstance(text, str):
text = text.encode(default_scintilla_encoding)
self.SendScintilla(scintillacon.SCI_INSERTTEXT, pos, text + null_byte)
def SCISetSavePoint(self):
self.SendScintilla(scintillacon.SCI_SETSAVEPOINT)
def SCISetUndoCollection(self, collectFlag):
self.SendScintilla(scintillacon.SCI_SETUNDOCOLLECTION, collectFlag)
def SCIBeginUndoAction(self):
self.SendScintilla(scintillacon.SCI_BEGINUNDOACTION)
def SCIEndUndoAction(self):
self.SendScintilla(scintillacon.SCI_ENDUNDOACTION)
def SCIGetCurrentPos(self):
return self.SendScintilla(scintillacon.SCI_GETCURRENTPOS)
def SCIGetCharAt(self, pos):
# Must ensure char is unsigned!
return chr(self.SendScintilla(scintillacon.SCI_GETCHARAT, pos) & 0xFF)
def SCIGotoLine(self, line):
self.SendScintilla(scintillacon.SCI_GOTOLINE, line)
def SCIBraceMatch(self, pos, maxReStyle):
return self.SendScintilla(scintillacon.SCI_BRACEMATCH, pos, maxReStyle)
def SCIBraceHighlight(self, pos, posOpposite):
return self.SendScintilla(scintillacon.SCI_BRACEHIGHLIGHT, pos, posOpposite)
def SCIBraceBadHighlight(self, pos):
return self.SendScintilla(scintillacon.SCI_BRACEBADLIGHT, pos)
####################################
# Styling
# def SCIColourise(self, start=0, end=-1):
# NOTE - dependent on of we use builtin lexer, so handled below.
def SCIGetEndStyled(self):
return self.SendScintilla(scintillacon.SCI_GETENDSTYLED)
def SCIStyleSetFore(self, num, v):
return self.SendScintilla(scintillacon.SCI_STYLESETFORE, num, v)
def SCIStyleSetBack(self, num, v):
return self.SendScintilla(scintillacon.SCI_STYLESETBACK, num, v)
def SCIStyleSetEOLFilled(self, num, v):
return self.SendScintilla(scintillacon.SCI_STYLESETEOLFILLED, num, v)
def SCIStyleSetFont(self, num, name, characterset=0):
buff = (name + "\0").encode(default_scintilla_encoding)
self.SendScintilla(scintillacon.SCI_STYLESETFONT, num, buff)
self.SendScintilla(scintillacon.SCI_STYLESETCHARACTERSET, num, characterset)
def SCIStyleSetBold(self, num, bBold):
self.SendScintilla(scintillacon.SCI_STYLESETBOLD, num, bBold)
def SCIStyleSetItalic(self, num, bItalic):
self.SendScintilla(scintillacon.SCI_STYLESETITALIC, num, bItalic)
def SCIStyleSetSize(self, num, size):
self.SendScintilla(scintillacon.SCI_STYLESETSIZE, num, size)
def SCIGetViewWS(self):
return self.SendScintilla(scintillacon.SCI_GETVIEWWS)
def SCISetViewWS(self, val):
self.SendScintilla(scintillacon.SCI_SETVIEWWS, not (val==0))
self.InvalidateRect()
def SCISetIndentationGuides(self, val):
self.SendScintilla(scintillacon.SCI_SETINDENTATIONGUIDES, val)
def SCIGetIndentationGuides(self):
return self.SendScintilla(scintillacon.SCI_GETINDENTATIONGUIDES)
def SCISetIndent(self, val):
self.SendScintilla(scintillacon.SCI_SETINDENT, val)
def SCIGetIndent(self, val):
return self.SendScintilla(scintillacon.SCI_GETINDENT)
def SCIGetViewEOL(self):
return self.SendScintilla(scintillacon.SCI_GETVIEWEOL)
def SCISetViewEOL(self, val):
self.SendScintilla(scintillacon.SCI_SETVIEWEOL, not(val==0))
self.InvalidateRect()
def SCISetTabWidth(self, width):
self.SendScintilla(scintillacon.SCI_SETTABWIDTH, width, 0)
def SCIStartStyling(self, pos, mask):
self.SendScintilla(scintillacon.SCI_STARTSTYLING, pos, mask)
def SCISetStyling(self, pos, attr):
self.SendScintilla(scintillacon.SCI_SETSTYLING, pos, attr)
def SCISetStylingEx(self, ray): # ray is an array.
address, length = ray.buffer_info()
self.SendScintilla(scintillacon.SCI_SETSTYLINGEX, length, address)
def SCIGetStyleAt(self, pos):
return self.SendScintilla(scintillacon.SCI_GETSTYLEAT, pos)
def SCISetMarginWidth(self, width):
self.SendScintilla(scintillacon.SCI_SETMARGINWIDTHN, 1, width)
def SCISetMarginWidthN(self, n, width):
self.SendScintilla(scintillacon.SCI_SETMARGINWIDTHN, n, width)
def SCISetFoldFlags(self, flags):
self.SendScintilla(scintillacon.SCI_SETFOLDFLAGS, flags)
# Markers
def SCIMarkerDefineAll(self, markerNum, markerType, fore, back):
self.SCIMarkerDefine(markerNum, markerType)
self.SCIMarkerSetFore(markerNum, fore)
self.SCIMarkerSetBack(markerNum, back)
def SCIMarkerDefine(self, markerNum, markerType):
self.SendScintilla(scintillacon.SCI_MARKERDEFINE, markerNum, markerType)
def SCIMarkerSetFore(self, markerNum, fore):
self.SendScintilla(scintillacon.SCI_MARKERSETFORE, markerNum, fore)
def SCIMarkerSetBack(self, markerNum, back):
self.SendScintilla(scintillacon.SCI_MARKERSETBACK, markerNum, back)
def SCIMarkerAdd(self, lineNo, markerNum):
self.SendScintilla(scintillacon.SCI_MARKERADD, lineNo, markerNum)
def SCIMarkerDelete(self, lineNo, markerNum):
self.SendScintilla(scintillacon.SCI_MARKERDELETE, lineNo, markerNum)
def SCIMarkerDeleteAll(self, markerNum=-1):
self.SendScintilla(scintillacon.SCI_MARKERDELETEALL, markerNum)
def SCIMarkerGet(self, lineNo):
return self.SendScintilla(scintillacon.SCI_MARKERGET, lineNo)
def SCIMarkerNext(self, lineNo, markerNum):
return self.SendScintilla(scintillacon.SCI_MARKERNEXT, lineNo, markerNum)
def SCICancel(self):
self.SendScintilla(scintillacon.SCI_CANCEL)
# AutoComplete
def SCIAutoCShow(self, text):
if type(text) in [type([]), type(())]:
text = ' '.join(text)
buff = (text + "\0").encode(default_scintilla_encoding)
return self.SendScintilla(scintillacon.SCI_AUTOCSHOW, 0, buff)
def SCIAutoCCancel(self):
self.SendScintilla(scintillacon.SCI_AUTOCCANCEL)
def SCIAutoCActive(self):
return self.SendScintilla(scintillacon.SCI_AUTOCACTIVE)
def SCIAutoCComplete(self):
return self.SendScintilla(scintillacon.SCI_AUTOCCOMPLETE)
def SCIAutoCStops(self, stops):
buff = (stops + "\0").encode(default_scintilla_encoding)
self.SendScintilla(scintillacon.SCI_AUTOCSTOPS, 0, buff)
def SCIAutoCSetAutoHide(self, hide):
self.SendScintilla(scintillacon.SCI_AUTOCSETAUTOHIDE, hide)
def SCIAutoCSetFillups(self, fillups):
self.SendScintilla(scintillacon.SCI_AUTOCSETFILLUPS, fillups)
# Call tips
def SCICallTipShow(self, text, pos=-1):
if pos==-1: pos = self.GetSel()[0]
buff = (text + "\0").encode(default_scintilla_encoding)
self.SendScintilla(scintillacon.SCI_CALLTIPSHOW, pos, buff)
def SCICallTipCancel(self):
self.SendScintilla(scintillacon.SCI_CALLTIPCANCEL)
def SCICallTipActive(self):
return self.SendScintilla(scintillacon.SCI_CALLTIPACTIVE)
def SCICallTipPosStart(self):
return self.SendScintilla(scintillacon.SCI_CALLTIPPOSSTART)
def SCINewline(self):
self.SendScintilla(scintillacon.SCI_NEWLINE)
# Lexer etc
def SCISetKeywords(self, keywords, kw_list_no = 0):
buff = (keywords+"\0").encode(default_scintilla_encoding)
self.SendScintilla(scintillacon.SCI_SETKEYWORDS, kw_list_no, buff)
def SCISetProperty(self, name, value):
name_buff = array.array('b', (name + '\0').encode(default_scintilla_encoding))
val_buff = array.array("b", (str(value)+'\0').encode(default_scintilla_encoding))
address_name_buffer = name_buff.buffer_info()[0]
address_val_buffer = val_buff.buffer_info()[0]
self.SendScintilla(scintillacon.SCI_SETPROPERTY, address_name_buffer, address_val_buffer)
def SCISetStyleBits(self, nbits):
self.SendScintilla(scintillacon.SCI_SETSTYLEBITS, nbits)
# Folding
def SCIGetFoldLevel(self, lineno):
return self.SendScintilla(scintillacon.SCI_GETFOLDLEVEL, lineno)
def SCIToggleFold(self, lineno):
return self.SendScintilla(scintillacon.SCI_TOGGLEFOLD, lineno)
def SCIEnsureVisible(self, lineno):
self.SendScintilla(scintillacon.SCI_ENSUREVISIBLE, lineno)
def SCIGetFoldExpanded(self, lineno):
return self.SendScintilla(scintillacon.SCI_GETFOLDEXPANDED, lineno)
# right edge
def SCISetEdgeColumn(self, edge):
self.SendScintilla(scintillacon.SCI_SETEDGECOLUMN, edge)
def SCIGetEdgeColumn(self):
return self.SendScintilla(scintillacon.SCI_GETEDGECOLUMN)
def SCISetEdgeMode(self, mode):
self.SendScintilla(scintillacon.SCI_SETEDGEMODE, mode)
def SCIGetEdgeMode(self):
return self.SendScintilla(scintillacon.SCI_GETEDGEMODE)
def SCISetEdgeColor(self, color):
self.SendScintilla(scintillacon.SCI_SETEDGECOLOUR, color)
def SCIGetEdgeColor(self):
return self.SendScintilla(scintillacon.SCI_GETEDGECOLOR)
# Multi-doc
def SCIGetDocPointer(self):
return self.SendScintilla(scintillacon.SCI_GETDOCPOINTER)
def SCISetDocPointer(self, p):
return self.SendScintilla(scintillacon.SCI_SETDOCPOINTER, 0, p)
def SCISetWrapMode(self, mode):
return self.SendScintilla(scintillacon.SCI_SETWRAPMODE, mode)
def SCIGetWrapMode(self):
return self.SendScintilla(scintillacon.SCI_GETWRAPMODE)
class CScintillaEditInterface(ScintillaControlInterface):
def close(self):
self.colorizer = None
def Clear(self):
self.SendScintilla(win32con.WM_CLEAR)
def Clear(self):
self.SendScintilla(win32con.WM_CLEAR)
def FindText(self, flags, range, findText):
""" LPARAM for EM_FINDTEXTEX:
typedef struct _findtextex {
CHARRANGE chrg;
LPCTSTR lpstrText;
CHARRANGE chrgText;} FINDTEXTEX;
typedef struct _charrange {
LONG cpMin;
LONG cpMax;} CHARRANGE;
"""
findtextex_fmt='llPll'
## Scintilla does not handle unicode in EM_FINDTEXT msg (FINDTEXTEX struct)
txt_buff = (findText+'\0').encode(default_scintilla_encoding)
txt_array = array.array('b', txt_buff)
ft_buff = struct.pack(findtextex_fmt, range[0], range[1], txt_array.buffer_info()[0], 0, 0)
ft_array = array.array('b', ft_buff)
rc = self.SendScintilla(EM_FINDTEXTEX, flags, ft_array.buffer_info()[0])
ftUnpacked = struct.unpack(findtextex_fmt, ft_array)
return rc, (ftUnpacked[3], ftUnpacked[4])
def GetSel(self):
currentPos = self.SendScintilla(scintillacon.SCI_GETCURRENTPOS)
anchorPos = self.SendScintilla(scintillacon.SCI_GETANCHOR)
if currentPos < anchorPos:
return (currentPos, anchorPos)
else:
return (anchorPos, currentPos)
return currentPos;
def GetSelText(self):
start, end = self.GetSel()
txtBuf = array.array('b', null_byte * (end-start+1))
addressTxtBuf = txtBuf.buffer_info()[0]
# EM_GETSELTEXT is documented as returning the number of chars
# not including the NULL, but scintilla includes the NULL. A
# quick glance at the scintilla impl doesn't make this
# obvious - the NULL is included in the 'selection' object
# and reflected in the length of that 'selection' object.
# I expect that is a bug in scintilla and may be fixed by now,
# but we just blindly assume that the last char is \0 and
# strip it.
self.SendScintilla(EM_GETSELTEXT, 0, addressTxtBuf)
return txtBuf.tostring()[:-1].decode(default_scintilla_encoding)
def SetSel(self, start=0, end=None):
if type(start)==type(()):
assert end is None, "If you pass a point in the first param, the second must be None"
start, end = start
elif end is None:
end = start
if start < 0: start = self.GetTextLength()
if end < 0: end = self.GetTextLength()
assert start <= self.GetTextLength(), "The start postion is invalid (%d/%d)" % (start, self.GetTextLength())
assert end <= self.GetTextLength(), "The end postion is invalid (%d/%d)" % (end, self.GetTextLength())
cr = struct.pack('ll', start, end)
crBuff = array.array('b', cr)
addressCrBuff = crBuff.buffer_info()[0]
rc = self.SendScintilla(EM_EXSETSEL, 0, addressCrBuff)
def GetLineCount(self):
return self.SendScintilla(win32con.EM_GETLINECOUNT)
def LineFromChar(self, charPos=-1):
if charPos==-1: charPos = self.GetSel()[0]
assert charPos >= 0 and charPos <= self.GetTextLength(), "The charPos postion (%s) is invalid (max=%s)" % (charPos, self.GetTextLength())
#return self.SendScintilla(EM_EXLINEFROMCHAR, charPos)
# EM_EXLINEFROMCHAR puts charPos in lParam, not wParam
return self.SendScintilla(EM_EXLINEFROMCHAR, 0, charPos)
def LineIndex(self, line):
return self.SendScintilla(win32con.EM_LINEINDEX, line)
def ScrollCaret(self):
return self.SendScintilla(win32con.EM_SCROLLCARET)
def GetCurLineNumber(self):
return self.LineFromChar(self.SCIGetCurrentPos())
def GetTextLength(self):
return self.SendScintilla(scintillacon.SCI_GETTEXTLENGTH)
def GetTextRange(self, start = 0, end = -1, decode = True):
if end == -1: end = self.SendScintilla(scintillacon.SCI_GETTEXTLENGTH)
assert end>=start, "Negative index requested (%d/%d)" % (start, end)
assert start >= 0 and start <= self.GetTextLength(), "The start postion is invalid"
assert end >= 0 and end <= self.GetTextLength(), "The end postion is invalid"
initer = null_byte * (end - start + 1)
buff = array.array('b', initer)
addressBuffer = buff.buffer_info()[0]
tr = struct.pack('llP', start, end, addressBuffer)
trBuff = array.array('b', tr)
addressTrBuff = trBuff.buffer_info()[0]
num_bytes = self.SendScintilla(EM_GETTEXTRANGE, 0, addressTrBuff)
ret = buff.tostring()[:num_bytes]
if decode:
ret = ret.decode(default_scintilla_encoding)
return ret
def ReplaceSel(self, str):
buff = (str + "\0").encode(default_scintilla_encoding)
self.SendScintilla(scintillacon.SCI_REPLACESEL, 0, buff)
def GetLine(self, line=-1):
if line == -1: line = self.GetCurLineNumber()
start = self.LineIndex(line)
end = self.LineIndex(line+1)
return self.GetTextRange(start, end)
def SetReadOnly(self, flag = 1):
return self.SendScintilla(win32con.EM_SETREADONLY, flag)
def LineScroll(self, lines, cols=0):
return self.SendScintilla(win32con.EM_LINESCROLL, cols, lines)
def GetFirstVisibleLine(self):
return self.SendScintilla(win32con.EM_GETFIRSTVISIBLELINE)
def SetWordWrap(self, mode):
if mode != win32ui.CRichEditView_WrapNone:
raise ValueError("We dont support word-wrap (I dont think :-)")
class CScintillaColorEditInterface(CScintillaEditInterface):
################################
# Plug-in colorizer support
def _GetColorizer(self):
if not hasattr(self, "colorizer"):
self.colorizer = self._MakeColorizer()
return self.colorizer
def _MakeColorizer(self):
# Give parent a chance to hook.
parent_func = getattr(self.GetParentFrame(), "_MakeColorizer", None)
if parent_func is not None:
return parent_func()
from . import formatter
## return formatter.PythonSourceFormatter(self)
return formatter.BuiltinPythonSourceFormatter(self)
def Colorize(self, start=0, end=-1):
c = self._GetColorizer()
if c is not None: c.Colorize(start, end)
def ApplyFormattingStyles(self, bReload=1):
c = self._GetColorizer()
if c is not None: c.ApplyFormattingStyles(bReload)
# The Parent window will normally hook
def HookFormatter(self, parent = None):
c = self._GetColorizer()
if c is not None: # No need if we have no color!
c.HookFormatter(parent)
class CScintillaEdit(window.Wnd, CScintillaColorEditInterface):
def __init__(self, wnd=None):
if wnd is None:
wnd = win32ui.CreateWnd()
window.Wnd.__init__(self, wnd)
def SendScintilla(self, msg, w=0, l=0):
return self.SendMessage(msg, w, l)
def CreateWindow(self, style, rect, parent, id):
self._obj_.CreateWindow(
"Scintilla",
"Scintilla",
style,
rect,
parent,
id,
None)

View file

@ -0,0 +1,274 @@
import win32ui
from pywin.mfc import docview
from pywin import default_scintilla_encoding
from . import scintillacon
import win32con
import string
import os
import codecs
import re
crlf_bytes = "\r\n".encode("ascii")
lf_bytes = "\n".encode("ascii")
# re from pep263 - but we use it both on bytes and strings.
re_encoding_bytes = re.compile("coding[:=]\s*([-\w.]+)".encode("ascii"))
re_encoding_text = re.compile("coding[:=]\s*([-\w.]+)")
ParentScintillaDocument=docview.Document
class CScintillaDocument(ParentScintillaDocument):
"A SyntEdit document. "
def __init__(self, *args):
self.bom = None # the BOM, if any, read from the file.
# the encoding we detected from the source. Might have
# detected via the BOM or an encoding decl. Note that in
# the latter case (ie, while self.bom is None), it can't be
# trusted - the user may have edited the encoding decl between
# open and save.
self.source_encoding = None
ParentScintillaDocument.__init__(self, *args)
def DeleteContents(self):
pass
def OnOpenDocument(self, filename):
# init data members
#print "Opening", filename
self.SetPathName(filename) # Must set this early!
try:
# load the text as binary we can get smart
# about detecting any existing EOL conventions.
f = open(filename, 'rb')
try:
self._LoadTextFromFile(f)
finally:
f.close()
except IOError:
rc = win32ui.MessageBox("Could not load the file from %s\n\nDo you want to create a new file?" % filename,
"Pythonwin", win32con.MB_YESNO | win32con.MB_ICONWARNING)
if rc == win32con.IDNO:
return 0
assert rc == win32con.IDYES, rc
try:
f = open(filename, 'wb+')
try:
self._LoadTextFromFile(f)
finally:
f.close()
except IOError as e:
rc = win32ui.MessageBox("Cannot create the file %s" % filename)
return 1
def SaveFile(self, fileName, encoding=None):
view = self.GetFirstView()
ok = view.SaveTextFile(fileName, encoding=encoding)
if ok:
view.SCISetSavePoint()
return ok
def ApplyFormattingStyles(self):
self._ApplyOptionalToViews("ApplyFormattingStyles")
# #####################
# File related functions
# Helper to transfer text from the MFC document to the control.
def _LoadTextFromFile(self, f):
# detect EOL mode - we don't support \r only - so find the
# first '\n' and guess based on the char before.
l = f.readline()
l2 = f.readline()
# If line ends with \r\n or has no line ending, use CRLF.
if l.endswith(crlf_bytes) or not l.endswith(lf_bytes):
eol_mode = scintillacon.SC_EOL_CRLF
else:
eol_mode = scintillacon.SC_EOL_LF
# Detect the encoding - first look for a BOM, and if not found,
# look for a pep263 encoding declaration.
for bom, encoding in (
(codecs.BOM_UTF8, "utf8"),
(codecs.BOM_UTF16_LE, "utf_16_le"),
(codecs.BOM_UTF16_BE, "utf_16_be"),
):
if l.startswith(bom):
self.bom = bom
self.source_encoding = encoding
l = l[len(bom):] # remove it.
break
else:
# no bom detected - look for pep263 encoding decl.
for look in (l, l2):
# Note we are looking at raw bytes here: so
# both the re itself uses bytes and the result
# is bytes - but we need the result as a string.
match = re_encoding_bytes.search(look)
if match is not None:
self.source_encoding = match.group(1).decode("ascii")
break
# reading by lines would be too slow? Maybe we can use the
# incremental encoders? For now just stick with loading the
# entire file in memory.
text = l + l2 + f.read()
# Translate from source encoding to UTF-8 bytes for Scintilla
source_encoding = self.source_encoding
# If we don't know an encoding, just use latin-1 to treat
# it as bytes...
if source_encoding is None:
source_encoding = 'latin1'
# we could optimize this by avoiding utf8 to-ing and from-ing,
# but then we would lose the ability to handle invalid utf8
# (and even then, the use of encoding aliases makes this tricky)
# To create an invalid utf8 file:
# >>> open(filename, "wb").write(codecs.BOM_UTF8+"bad \xa9har\r\n")
try:
dec = text.decode(source_encoding)
except UnicodeError:
print("WARNING: Failed to decode bytes from '%s' encoding - treating as latin1" % source_encoding)
dec = text.decode('latin1')
except LookupError:
print("WARNING: Invalid encoding '%s' specified - treating as latin1" % source_encoding)
dec = text.decode('latin1')
# and put it back as utf8 - this shouldn't fail.
text = dec.encode(default_scintilla_encoding)
view = self.GetFirstView()
if view.IsWindow():
# Turn off undo collection while loading
view.SendScintilla(scintillacon.SCI_SETUNDOCOLLECTION, 0, 0)
# Make sure the control isnt read-only
view.SetReadOnly(0)
view.SendScintilla(scintillacon.SCI_CLEARALL)
view.SendMessage(scintillacon.SCI_ADDTEXT, text)
view.SendScintilla(scintillacon.SCI_SETUNDOCOLLECTION, 1, 0)
view.SendScintilla(win32con.EM_EMPTYUNDOBUFFER, 0, 0)
# set EOL mode
view.SendScintilla(scintillacon.SCI_SETEOLMODE, eol_mode)
def _SaveTextToFile(self, view, filename, encoding=None):
s = view.GetTextRange() # already decoded from scintilla's encoding
source_encoding = encoding
if source_encoding is None:
if self.bom:
source_encoding = self.source_encoding
else:
# no BOM - look for an encoding.
bits = re.split("[\r\n]*", s, 3)
for look in bits[:-1]:
match = re_encoding_text.search(look)
if match is not None:
source_encoding = match.group(1)
self.source_encoding = source_encoding
break
if source_encoding is None:
source_encoding = 'latin1'
## encode data before opening file so script is not lost if encoding fails
file_contents = s.encode(source_encoding)
# Open in binary mode as scintilla itself ensures the
# line endings are already appropriate
f = open(filename, 'wb')
try:
if self.bom:
f.write(self.bom)
f.write(file_contents)
finally:
f.close()
self.SetModifiedFlag(0)
def FinalizeViewCreation(self, view):
pass
def HookViewNotifications(self, view):
parent = view.GetParentFrame()
parent.HookNotify(ViewNotifyDelegate(self, "OnBraceMatch"), scintillacon.SCN_CHECKBRACE)
parent.HookNotify(ViewNotifyDelegate(self, "OnMarginClick"), scintillacon.SCN_MARGINCLICK)
parent.HookNotify(ViewNotifyDelegate(self, "OnNeedShown"), scintillacon.SCN_NEEDSHOWN)
parent.HookNotify(DocumentNotifyDelegate(self, "OnSavePointReached"), scintillacon.SCN_SAVEPOINTREACHED)
parent.HookNotify(DocumentNotifyDelegate(self, "OnSavePointLeft"), scintillacon.SCN_SAVEPOINTLEFT)
parent.HookNotify(DocumentNotifyDelegate(self, "OnModifyAttemptRO"), scintillacon.SCN_MODIFYATTEMPTRO)
# Tell scintilla what characters should abort auto-complete.
view.SCIAutoCStops(string.whitespace+"()[]:;+-/*=\\?'!#@$%^&,<>\"'|" )
if view != self.GetFirstView():
view.SCISetDocPointer(self.GetFirstView().SCIGetDocPointer())
def OnSavePointReached(self, std, extra):
self.SetModifiedFlag(0)
def OnSavePointLeft(self, std, extra):
self.SetModifiedFlag(1)
def OnModifyAttemptRO(self, std, extra):
self.MakeDocumentWritable()
# All Marker functions are 1 based.
def MarkerAdd( self, lineNo, marker ):
self.GetEditorView().SCIMarkerAdd(lineNo-1, marker)
def MarkerCheck(self, lineNo, marker ):
v = self.GetEditorView()
lineNo = lineNo - 1 # Make 0 based
markerState = v.SCIMarkerGet(lineNo)
return markerState & (1<<marker) != 0
def MarkerToggle( self, lineNo, marker ):
v = self.GetEditorView()
if self.MarkerCheck(lineNo, marker):
v.SCIMarkerDelete(lineNo-1, marker)
else:
v.SCIMarkerAdd(lineNo-1, marker)
def MarkerDelete( self, lineNo, marker ):
self.GetEditorView().SCIMarkerDelete(lineNo-1, marker)
def MarkerDeleteAll( self, marker ):
self.GetEditorView().SCIMarkerDeleteAll(marker)
def MarkerGetNext(self, lineNo, marker):
return self.GetEditorView().SCIMarkerNext( lineNo-1, 1 << marker )+1
def MarkerAtLine(self, lineNo, marker):
markerState = self.GetEditorView().SCIMarkerGet(lineNo-1)
return markerState & (1<<marker)
# Helper for reflecting functions to views.
def _ApplyToViews(self, funcName, *args):
for view in self.GetAllViews():
func = getattr(view, funcName)
func(*args)
def _ApplyOptionalToViews(self, funcName, *args):
for view in self.GetAllViews():
func = getattr(view, funcName, None)
if func is not None:
func(*args)
def GetEditorView(self):
# Find the first frame with a view,
# then ask it to give the editor view
# as it knows which one is "active"
try:
frame_gev = self.GetFirstView().GetParentFrame().GetEditorView
except AttributeError:
return self.GetFirstView()
return frame_gev()
# Delegate to the correct view, based on the control that sent it.
class ViewNotifyDelegate:
def __init__(self, doc, name):
self.doc = doc
self.name = name
def __call__(self, std, extra):
(hwndFrom, idFrom, code) = std
for v in self.doc.GetAllViews():
if v.GetSafeHwnd() == hwndFrom:
return getattr(v, self.name)(*(std, extra))
# Delegate to the document, but only from a single view (as each view sends it seperately)
class DocumentNotifyDelegate:
def __init__(self, doc, name):
self.doc = doc
self.delegate = getattr(doc, name)
def __call__(self, std, extra):
(hwndFrom, idFrom, code) = std
if hwndFrom == self.doc.GetEditorView().GetSafeHwnd():
self.delegate(*(std, extra))

View file

@ -0,0 +1,334 @@
# find.py - Find and Replace
import win32con, win32api
import win32ui
from pywin.mfc import dialog
import afxres
from pywin.framework import scriptutils
FOUND_NOTHING=0
FOUND_NORMAL=1
FOUND_LOOPED_BACK=2
FOUND_NEXT_FILE=3
class SearchParams:
def __init__(self, other=None):
if other is None:
self.__dict__['findText'] = ""
self.__dict__['replaceText'] = ""
self.__dict__['matchCase'] = 0
self.__dict__['matchWords'] = 0
self.__dict__['acrossFiles'] = 0
self.__dict__['remember'] = 1
self.__dict__['sel'] = (-1,-1)
self.__dict__['keepDialogOpen']=0
else:
self.__dict__.update(other.__dict__)
# Helper so we cant misspell attributes :-)
def __setattr__(self, attr, val):
if not hasattr(self, attr):
raise AttributeError(attr)
self.__dict__[attr]=val
curDialog = None
lastSearch = defaultSearch = SearchParams()
searchHistory = []
def ShowFindDialog():
_ShowDialog(FindDialog)
def ShowReplaceDialog():
_ShowDialog(ReplaceDialog)
def _ShowDialog(dlgClass):
global curDialog
if curDialog is not None:
if curDialog.__class__ != dlgClass:
curDialog.DestroyWindow()
curDialog = None
else:
curDialog.SetFocus()
if curDialog is None:
curDialog = dlgClass()
curDialog.CreateWindow()
def FindNext():
params = SearchParams(lastSearch)
params.sel = (-1,-1)
if not params.findText:
ShowFindDialog()
else:
return _FindIt(None, params)
def _GetControl(control=None):
if control is None:
control = scriptutils.GetActiveEditControl()
return control
def _FindIt(control, searchParams):
global lastSearch, defaultSearch
control = _GetControl(control)
if control is None: return FOUND_NOTHING
# Move to the next char, so we find the next one.
flags = 0
if searchParams.matchWords: flags = flags | win32con.FR_WHOLEWORD
if searchParams.matchCase: flags = flags | win32con.FR_MATCHCASE
if searchParams.sel == (-1,-1):
sel = control.GetSel()
# If the position is the same as we found last time,
# then we assume it is a "FindNext"
if sel==lastSearch.sel:
sel = sel[0]+1, sel[0]+1
else:
sel = searchParams.sel
if sel[0]==sel[1]: sel=sel[0], control.GetTextLength()
rc = FOUND_NOTHING
# (Old edit control will fail here!)
posFind, foundSel = control.FindText(flags, sel, searchParams.findText)
lastSearch = SearchParams(searchParams)
if posFind >= 0:
rc = FOUND_NORMAL
lineno = control.LineFromChar(posFind)
control.SCIEnsureVisible(lineno)
control.SetSel(foundSel)
control.SetFocus()
win32ui.SetStatusText(win32ui.LoadString(afxres.AFX_IDS_IDLEMESSAGE))
if rc == FOUND_NOTHING and lastSearch.acrossFiles:
# Loop around all documents. First find this document.
try:
try:
doc = control.GetDocument()
except AttributeError:
try:
doc = control.GetParent().GetDocument()
except AttributeError:
print("Cant find a document for the control!")
doc = None
if doc is not None:
template = doc.GetDocTemplate()
alldocs = template.GetDocumentList()
mypos = lookpos = alldocs.index(doc)
while 1:
lookpos = (lookpos+1) % len(alldocs)
if lookpos == mypos:
break
view = alldocs[lookpos].GetFirstView()
posFind, foundSel = view.FindText(flags, (0, view.GetTextLength()), searchParams.findText)
if posFind >= 0:
nChars = foundSel[1]-foundSel[0]
lineNo = view.LineFromChar(posFind) # zero based.
lineStart = view.LineIndex(lineNo)
colNo = posFind - lineStart # zero based.
scriptutils.JumpToDocument(alldocs[lookpos].GetPathName(), lineNo+1, colNo+1, nChars)
rc = FOUND_NEXT_FILE
break
except win32ui.error:
pass
if rc == FOUND_NOTHING:
# Loop around this control - attempt to find from the start of the control.
posFind, foundSel = control.FindText(flags, (0, sel[0]-1), searchParams.findText)
if posFind >= 0:
control.SCIEnsureVisible(control.LineFromChar(foundSel[0]))
control.SetSel(foundSel)
control.SetFocus()
win32ui.SetStatusText("Not found! Searching from the top of the file.")
rc = FOUND_LOOPED_BACK
else:
lastSearch.sel=-1,-1
win32ui.SetStatusText("Can not find '%s'" % searchParams.findText )
if rc != FOUND_NOTHING:
lastSearch.sel = foundSel
if lastSearch.remember:
defaultSearch = lastSearch
# track search history
try:
ix = searchHistory.index(searchParams.findText)
except ValueError:
if len(searchHistory) > 50:
searchHistory[50:] = []
else:
del searchHistory[ix]
searchHistory.insert(0, searchParams.findText)
return rc
def _ReplaceIt(control):
control = _GetControl(control)
statusText = "Can not find '%s'." % lastSearch.findText
rc = FOUND_NOTHING
if control is not None and lastSearch.sel != (-1,-1):
control.ReplaceSel(lastSearch.replaceText)
rc = FindNext()
if rc !=FOUND_NOTHING:
statusText = win32ui.LoadString(afxres.AFX_IDS_IDLEMESSAGE)
win32ui.SetStatusText(statusText)
return rc
class FindReplaceDialog(dialog.Dialog):
def __init__(self):
dialog.Dialog.__init__(self,self._GetDialogTemplate())
self.HookCommand(self.OnFindNext, 109)
def OnInitDialog(self):
self.editFindText = self.GetDlgItem(102)
self.butMatchWords = self.GetDlgItem(105)
self.butMatchCase = self.GetDlgItem(107)
self.butKeepDialogOpen = self.GetDlgItem(115)
self.butAcrossFiles = self.GetDlgItem(116)
self.butRemember = self.GetDlgItem(117)
self.editFindText.SetWindowText(defaultSearch.findText)
control = _GetControl()
# _GetControl only gets normal MDI windows; if the interactive
# window is docked and no document open, we get None.
if control:
# If we have a selection, default to that.
sel = control.GetSelText()
if (len(sel) != 0):
self.editFindText.SetWindowText(sel)
if (defaultSearch.remember):
defaultSearch.findText = sel
for hist in searchHistory:
self.editFindText.AddString(hist)
if hasattr(self.editFindText, 'SetEditSel'):
self.editFindText.SetEditSel(0, -2)
else:
self.editFindText.SetSel(0, -2)
self.editFindText.SetFocus()
self.butMatchWords.SetCheck(defaultSearch.matchWords)
self.butMatchCase.SetCheck(defaultSearch.matchCase)
self.butKeepDialogOpen.SetCheck(defaultSearch.keepDialogOpen)
self.butAcrossFiles.SetCheck(defaultSearch.acrossFiles)
self.butRemember.SetCheck(defaultSearch.remember)
return dialog.Dialog.OnInitDialog(self)
def OnDestroy(self, msg):
global curDialog
curDialog = None
return dialog.Dialog.OnDestroy(self, msg)
def DoFindNext(self):
params = SearchParams()
params.findText = self.editFindText.GetWindowText()
params.matchCase = self.butMatchCase.GetCheck()
params.matchWords = self.butMatchWords.GetCheck()
params.acrossFiles = self.butAcrossFiles.GetCheck()
params.remember = self.butRemember.GetCheck()
return _FindIt(None, params)
def OnFindNext(self, id, code):
if not self.editFindText.GetWindowText():
win32api.MessageBeep()
return
if self.DoFindNext() != FOUND_NOTHING:
if not self.butKeepDialogOpen.GetCheck():
self.DestroyWindow()
class FindDialog(FindReplaceDialog):
def _GetDialogTemplate(self):
style = win32con.DS_MODALFRAME | win32con.WS_POPUP | win32con.WS_VISIBLE | win32con.WS_CAPTION | win32con.WS_SYSMENU | win32con.DS_SETFONT
visible = win32con.WS_CHILD | win32con.WS_VISIBLE
dt = [
["Find", (0, 2, 240, 75), style, None, (8, "MS Sans Serif")],
["Static", "Fi&nd What:", 101, (5, 8, 40, 10), visible],
["ComboBox", "", 102, (50, 7, 120, 120), visible | win32con.WS_BORDER | win32con.WS_TABSTOP |
win32con.WS_VSCROLL |win32con.CBS_DROPDOWN |win32con.CBS_AUTOHSCROLL],
["Button", "Match &whole word only", 105, (5, 23, 100, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
["Button", "Match &case", 107, (5, 33, 100, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
["Button", "Keep &dialog open", 115, (5, 43, 100, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
["Button", "Across &open files", 116, (5, 52, 100, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
["Button", "&Remember as default search", 117, (5, 61, 150, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
["Button", "&Find Next", 109, (185, 5, 50, 14), visible | win32con.BS_DEFPUSHBUTTON | win32con.WS_TABSTOP],
["Button", "Cancel", win32con.IDCANCEL, (185, 23, 50, 14), visible | win32con.WS_TABSTOP],
]
return dt
class ReplaceDialog(FindReplaceDialog):
def _GetDialogTemplate(self):
style = win32con.DS_MODALFRAME | win32con.WS_POPUP | win32con.WS_VISIBLE | win32con.WS_CAPTION | win32con.WS_SYSMENU | win32con.DS_SETFONT
visible = win32con.WS_CHILD | win32con.WS_VISIBLE
dt = [
["Replace", (0, 2, 240, 95), style, 0, (8, "MS Sans Serif")],
["Static", "Fi&nd What:", 101, (5, 8, 40, 10), visible],
["ComboBox", "", 102, (60, 7, 110, 120), visible | win32con.WS_BORDER | win32con.WS_TABSTOP |
win32con.WS_VSCROLL |win32con.CBS_DROPDOWN |win32con.CBS_AUTOHSCROLL],
["Static", "Re&place with:", 103, (5, 25, 50, 10), visible],
["ComboBox", "", 104, (60, 24, 110, 120), visible | win32con.WS_BORDER | win32con.WS_TABSTOP |
win32con.WS_VSCROLL |win32con.CBS_DROPDOWN |win32con.CBS_AUTOHSCROLL],
["Button", "Match &whole word only", 105, (5, 42, 100, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
["Button", "Match &case", 107, (5, 52, 100, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
["Button", "Keep &dialog open", 115, (5, 62, 100, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
["Button", "Across &open files", 116, (5, 72, 100, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
["Button", "&Remember as default search", 117, (5, 81, 150, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
["Button", "&Find Next", 109, (185, 5, 50, 14), visible | win32con.BS_DEFPUSHBUTTON | win32con.WS_TABSTOP],
["Button", "&Replace", 110, (185, 23, 50, 14), visible | win32con.WS_TABSTOP],
["Button", "Replace &All", 111, (185, 41, 50, 14), visible | win32con.WS_TABSTOP],
["Button", "Cancel", win32con.IDCANCEL, (185, 59, 50, 14), visible | win32con.WS_TABSTOP],
]
return dt
def OnInitDialog(self):
rc = FindReplaceDialog.OnInitDialog(self)
self.HookCommand(self.OnReplace, 110)
self.HookCommand(self.OnReplaceAll, 111)
self.HookMessage(self.OnActivate, win32con.WM_ACTIVATE)
self.editReplaceText = self.GetDlgItem(104)
self.editReplaceText.SetWindowText(lastSearch.replaceText)
if hasattr(self.editReplaceText, 'SetEditSel'):
self.editReplaceText.SetEditSel(0, -2)
else:
self.editReplaceText.SetSel(0, -2)
self.butReplace = self.GetDlgItem(110)
self.butReplaceAll = self.GetDlgItem(111)
self.CheckButtonStates()
return rc
def CheckButtonStates(self):
# We can do a "Replace" or "Replace All" if the current selection
# is the same as the search text.
ft = self.editFindText.GetWindowText()
control = _GetControl()
# bCanReplace = len(ft)>0 and control.GetSelText() == ft
bCanReplace = control is not None and lastSearch.sel == control.GetSel()
self.butReplace.EnableWindow(bCanReplace)
# self.butReplaceAll.EnableWindow(bCanReplace)
def OnActivate(self, msg):
wparam = msg[2]
fActive = win32api.LOWORD(wparam)
if fActive != win32con.WA_INACTIVE:
self.CheckButtonStates()
def OnFindNext(self, id, code):
self.DoFindNext()
self.CheckButtonStates()
def OnReplace(self, id, code):
lastSearch.replaceText = self.editReplaceText.GetWindowText()
_ReplaceIt(None)
def OnReplaceAll(self, id, code):
control = _GetControl(None)
if control is not None:
control.SetSel(0)
num = 0
if self.DoFindNext() == FOUND_NORMAL:
num = 1
lastSearch.replaceText = self.editReplaceText.GetWindowText()
while _ReplaceIt(control) == FOUND_NORMAL:
num = num + 1
win32ui.SetStatusText("Replaced %d occurrences" % num)
if num > 0 and not self.butKeepDialogOpen.GetCheck():
self.DestroyWindow()
if __name__=='__main__':
ShowFindDialog()

View file

@ -0,0 +1,587 @@
# Does Python source formatting for Scintilla controls.
import win32ui
import win32api
import win32con
import winerror
import string
import array
from . import scintillacon
WM_KICKIDLE = 0x036A
# Used to indicate that style should use default color
from win32con import CLR_INVALID
debugging = 0
if debugging:
# Output must go to another process else the result of
# the printing itself will trigger again trigger a trace.
import sys, win32traceutil, win32trace
def trace(*args):
win32trace.write(' '.join(map(str, args)) + "\n")
else:
trace = lambda *args: None
class Style:
"""Represents a single format
"""
def __init__(self, name, format, background = CLR_INVALID):
self.name = name # Name the format representes eg, "String", "Class"
# Default background for each style is only used when there are no
# saved settings (generally on first startup)
self.background = self.default_background = background
if type(format)==type(''):
self.aliased = format
self.format = None
else:
self.format = format
self.aliased = None
self.stylenum = None # Not yet registered.
def IsBasedOnDefault(self):
return len(self.format)==5
# If the currently extended font defintion matches the
# default format, restore the format to the "simple" format.
def NormalizeAgainstDefault(self, defaultFormat):
if self.IsBasedOnDefault():
return 0 # No more to do, and not changed.
bIsDefault = self.format[7] == defaultFormat[7] and \
self.format[2] == defaultFormat[2]
if bIsDefault:
self.ForceAgainstDefault()
return bIsDefault
def ForceAgainstDefault(self):
self.format = self.format[:5]
def GetCompleteFormat(self, defaultFormat):
# Get the complete style after applying any relevant defaults.
if len(self.format)==5: # It is a default one
fmt = self.format + defaultFormat[5:]
else:
fmt = self.format
flags = win32con.CFM_BOLD | win32con.CFM_CHARSET | win32con.CFM_COLOR | win32con.CFM_FACE | win32con.CFM_ITALIC | win32con.CFM_SIZE
return (flags,) + fmt[1:]
# The Formatter interface
# used primarily when the actual formatting is done by Scintilla!
class FormatterBase:
def __init__(self, scintilla):
self.scintilla = scintilla
self.baseFormatFixed = (-402653169, 0, 200, 0, 0, 0, 49, 'Courier New')
self.baseFormatProp = (-402653169, 0, 200, 0, 0, 0, 49, 'Arial')
self.bUseFixed = 1
self.styles = {} # Indexed by name
self.styles_by_id = {} # Indexed by allocated ID.
self.SetStyles()
def HookFormatter(self, parent = None):
raise NotImplementedError()
# Used by the IDLE extensions to quickly determine if a character is a string.
def GetStringStyle(self, pos):
try:
style = self.styles_by_id[self.scintilla.SCIGetStyleAt(pos)]
except KeyError:
# A style we dont know about - probably not even a .py file - can't be a string
return None
if style.name in self.string_style_names:
return style
return None
def RegisterStyle(self, style, stylenum):
assert stylenum is not None, "We must have a style number"
assert style.stylenum is None, "Style has already been registered"
assert stylenum not in self.styles, "We are reusing a style number!"
style.stylenum = stylenum
self.styles[style.name] = style
self.styles_by_id[stylenum] = style
def SetStyles(self):
raise NotImplementedError()
def GetSampleText(self):
return "Sample Text for the Format Dialog"
def GetDefaultFormat(self):
if self.bUseFixed:
return self.baseFormatFixed
return self.baseFormatProp
# Update the control with the new style format.
def _ReformatStyle(self, style):
## Selection (background only for now)
## Passing False for WPARAM to SCI_SETSELBACK is documented as resetting to scintilla default,
## but does not work - selection background is not visible at all.
## Default value in SPECIAL_STYLES taken from scintilla source.
if style.name == STYLE_SELECTION:
clr = style.background
self.scintilla.SendScintilla(scintillacon.SCI_SETSELBACK, True, clr)
## Can't change font for selection, but could set color
## However, the font color dropbox has no option for default, and thus would
## always override syntax coloring
## clr = style.format[4]
## self.scintilla.SendScintilla(scintillacon.SCI_SETSELFORE, clr != CLR_INVALID, clr)
return
assert style.stylenum is not None, "Unregistered style."
#print "Reformat style", style.name, style.stylenum
scintilla=self.scintilla
stylenum = style.stylenum
# Now we have the style number, indirect for the actual style.
if style.aliased is not None:
style = self.styles[style.aliased]
f=style.format
if style.IsBasedOnDefault():
baseFormat = self.GetDefaultFormat()
else: baseFormat = f
scintilla.SCIStyleSetFore(stylenum, f[4])
scintilla.SCIStyleSetFont(stylenum, baseFormat[7], baseFormat[5])
if f[1] & 1: scintilla.SCIStyleSetBold(stylenum, 1)
else: scintilla.SCIStyleSetBold(stylenum, 0)
if f[1] & 2: scintilla.SCIStyleSetItalic(stylenum, 1)
else: scintilla.SCIStyleSetItalic(stylenum, 0)
scintilla.SCIStyleSetSize(stylenum, int(baseFormat[2]/20))
scintilla.SCIStyleSetEOLFilled(stylenum, 1) # Only needed for unclosed strings.
## Default style background to whitespace background if set,
## otherwise use system window color
bg = style.background
if bg == CLR_INVALID:
bg = self.styles[STYLE_DEFAULT].background
if bg == CLR_INVALID:
bg = win32api.GetSysColor(win32con.COLOR_WINDOW)
scintilla.SCIStyleSetBack(stylenum, bg)
def GetStyleByNum(self, stylenum):
return self.styles_by_id[stylenum]
def ApplyFormattingStyles(self, bReload=1):
if bReload:
self.LoadPreferences()
baseFormat = self.GetDefaultFormat()
defaultStyle = Style("default", baseFormat)
defaultStyle.stylenum = scintillacon.STYLE_DEFAULT
self._ReformatStyle(defaultStyle)
for style in list(self.styles.values()):
if style.aliased is None:
style.NormalizeAgainstDefault(baseFormat)
self._ReformatStyle(style)
self.scintilla.InvalidateRect()
# Some functions for loading and saving preferences. By default
# an INI file (well, MFC maps this to the registry) is used.
def LoadPreferences(self):
self.baseFormatFixed = eval(self.LoadPreference("Base Format Fixed", str(self.baseFormatFixed)))
self.baseFormatProp = eval(self.LoadPreference("Base Format Proportional", str(self.baseFormatProp)))
self.bUseFixed = int(self.LoadPreference("Use Fixed", 1))
for style in list(self.styles.values()):
new = self.LoadPreference(style.name, str(style.format))
try:
style.format = eval(new)
except:
print("Error loading style data for", style.name)
# Use "vanilla" background hardcoded in PYTHON_STYLES if no settings in registry
style.background = int(self.LoadPreference(style.name + " background", style.default_background))
def LoadPreference(self, name, default):
return win32ui.GetProfileVal("Format", name, default)
def SavePreferences(self):
self.SavePreference("Base Format Fixed", str(self.baseFormatFixed))
self.SavePreference("Base Format Proportional", str(self.baseFormatProp))
self.SavePreference("Use Fixed", self.bUseFixed)
for style in list(self.styles.values()):
if style.aliased is None:
self.SavePreference(style.name, str(style.format))
bg_name = style.name + " background"
self.SavePreference(bg_name, style.background)
def SavePreference(self, name, value):
win32ui.WriteProfileVal("Format", name, value)
# An abstract formatter
# For all formatters we actually implement here.
# (as opposed to those formatters built in to Scintilla)
class Formatter(FormatterBase):
def __init__(self, scintilla):
self.bCompleteWhileIdle = 0
self.bHaveIdleHandler = 0 # Dont currently have an idle handle
self.nextstylenum = 0
FormatterBase.__init__(self, scintilla)
def HookFormatter(self, parent = None):
if parent is None: parent = self.scintilla.GetParent() # was GetParentFrame()!?
parent.HookNotify(self.OnStyleNeeded, scintillacon.SCN_STYLENEEDED)
def OnStyleNeeded(self, std, extra):
notify = self.scintilla.SCIUnpackNotifyMessage(extra)
endStyledChar = self.scintilla.SendScintilla(scintillacon.SCI_GETENDSTYLED)
lineEndStyled = self.scintilla.LineFromChar(endStyledChar)
endStyled = self.scintilla.LineIndex(lineEndStyled)
#print "enPosPaint %d endStyledChar %d lineEndStyled %d endStyled %d" % (endPosPaint, endStyledChar, lineEndStyled, endStyled)
self.Colorize(endStyled, notify.position)
def ColorSeg(self, start, end, styleName):
end = end+1
# assert end-start>=0, "Can't have negative styling"
stylenum = self.styles[styleName].stylenum
while start<end:
self.style_buffer[start]=stylenum
start = start+1
#self.scintilla.SCISetStyling(end - start + 1, stylenum)
def RegisterStyle(self, style, stylenum = None):
if stylenum is None:
stylenum = self.nextstylenum
self.nextstylenum = self.nextstylenum + 1
FormatterBase.RegisterStyle(self, style, stylenum)
def ColorizeString(self, str, charStart, styleStart):
raise RuntimeError("You must override this method")
def Colorize(self, start=0, end=-1):
scintilla = self.scintilla
# scintilla's formatting is all done in terms of utf, so
# we work with utf8 bytes instead of unicode. This magically
# works as any extended chars found in the utf8 don't change
# the semantics.
stringVal = scintilla.GetTextRange(start, end, decode=False)
if start > 0:
stylenum = scintilla.SCIGetStyleAt(start - 1)
styleStart = self.GetStyleByNum(stylenum).name
else:
styleStart = None
# trace("Coloring", start, end, end-start, len(stringVal), styleStart, self.scintilla.SCIGetCharAt(start))
scintilla.SCIStartStyling(start, 31)
self.style_buffer = array.array("b", (0,)*len(stringVal))
self.ColorizeString(stringVal, styleStart)
scintilla.SCISetStylingEx(self.style_buffer)
self.style_buffer = None
# trace("After styling, end styled is", self.scintilla.SCIGetEndStyled())
if self.bCompleteWhileIdle and not self.bHaveIdleHandler and end!=-1 and end < scintilla.GetTextLength():
self.bHaveIdleHandler = 1
win32ui.GetApp().AddIdleHandler(self.DoMoreColoring)
# Kicking idle makes the app seem slower when initially repainting!
# win32ui.GetMainFrame().PostMessage(WM_KICKIDLE, 0, 0)
def DoMoreColoring(self, handler, count):
try:
scintilla = self.scintilla
endStyled = scintilla.SCIGetEndStyled()
lineStartStyled = scintilla.LineFromChar(endStyled)
start = scintilla.LineIndex(lineStartStyled)
end = scintilla.LineIndex(lineStartStyled+1)
textlen = scintilla.GetTextLength()
if end < 0: end = textlen
finished = end >= textlen
self.Colorize(start, end)
except (win32ui.error, AttributeError):
# Window may have closed before we finished - no big deal!
finished = 1
if finished:
self.bHaveIdleHandler = 0
win32ui.GetApp().DeleteIdleHandler(handler)
return not finished
# A Formatter that knows how to format Python source
from keyword import iskeyword, kwlist
wordstarts = '_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
wordchars = '._0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
operators = '%^&*()-+=|{}[]:;<>,/?!.~'
STYLE_DEFAULT = "Whitespace"
STYLE_COMMENT = "Comment"
STYLE_COMMENT_BLOCK = "Comment Blocks"
STYLE_NUMBER = "Number"
STYLE_STRING = "String"
STYLE_SQSTRING = "SQ String"
STYLE_TQSSTRING = "TQS String"
STYLE_TQDSTRING = "TQD String"
STYLE_KEYWORD = "Keyword"
STYLE_CLASS = "Class"
STYLE_METHOD = "Method"
STYLE_OPERATOR = "Operator"
STYLE_IDENTIFIER = "Identifier"
STYLE_BRACE = "Brace/Paren - matching"
STYLE_BRACEBAD = "Brace/Paren - unmatched"
STYLE_STRINGEOL = "String with no terminator"
STYLE_LINENUMBER = "Line numbers"
STYLE_INDENTGUIDE = "Indent guide"
STYLE_SELECTION = "Selection"
STRING_STYLES = [STYLE_STRING, STYLE_SQSTRING, STYLE_TQSSTRING, STYLE_TQDSTRING, STYLE_STRINGEOL]
# These styles can have any ID - they are not special to scintilla itself.
# However, if we use the built-in lexer, then we must use its style numbers
# so in that case, they _are_ special.
# (name, format, background, scintilla id)
PYTHON_STYLES = [
(STYLE_DEFAULT, (0, 0, 200, 0, 0x808080), CLR_INVALID, scintillacon.SCE_P_DEFAULT ),
(STYLE_COMMENT, (0, 2, 200, 0, 0x008000), CLR_INVALID, scintillacon.SCE_P_COMMENTLINE ),
(STYLE_COMMENT_BLOCK,(0, 2, 200, 0, 0x808080), CLR_INVALID, scintillacon.SCE_P_COMMENTBLOCK ),
(STYLE_NUMBER, (0, 0, 200, 0, 0x808000), CLR_INVALID, scintillacon.SCE_P_NUMBER ),
(STYLE_STRING, (0, 0, 200, 0, 0x008080), CLR_INVALID, scintillacon.SCE_P_STRING ),
(STYLE_SQSTRING, STYLE_STRING, CLR_INVALID, scintillacon.SCE_P_CHARACTER ),
(STYLE_TQSSTRING, STYLE_STRING, CLR_INVALID, scintillacon.SCE_P_TRIPLE ),
(STYLE_TQDSTRING, STYLE_STRING, CLR_INVALID, scintillacon.SCE_P_TRIPLEDOUBLE),
(STYLE_STRINGEOL, (0, 0, 200, 0, 0x000000), 0x008080, scintillacon.SCE_P_STRINGEOL),
(STYLE_KEYWORD, (0, 1, 200, 0, 0x800000), CLR_INVALID, scintillacon.SCE_P_WORD),
(STYLE_CLASS, (0, 1, 200, 0, 0xFF0000), CLR_INVALID, scintillacon.SCE_P_CLASSNAME ),
(STYLE_METHOD, (0, 1, 200, 0, 0x808000), CLR_INVALID, scintillacon.SCE_P_DEFNAME),
(STYLE_OPERATOR, (0, 0, 200, 0, 0x000000), CLR_INVALID, scintillacon.SCE_P_OPERATOR),
(STYLE_IDENTIFIER, (0, 0, 200, 0, 0x000000), CLR_INVALID, scintillacon.SCE_P_IDENTIFIER ),
]
# These styles _always_ have this specific style number, regardless of
# internal or external formatter.
SPECIAL_STYLES = [
(STYLE_BRACE, (0, 0, 200, 0, 0x000000), 0xffff80, scintillacon.STYLE_BRACELIGHT),
(STYLE_BRACEBAD, (0, 0, 200, 0, 0x000000), 0x8ea5f2, scintillacon.STYLE_BRACEBAD),
(STYLE_LINENUMBER, (0, 0, 200, 0, 0x000000), win32api.GetSysColor(win32con.COLOR_3DFACE), scintillacon.STYLE_LINENUMBER),
(STYLE_INDENTGUIDE, (0, 0, 200, 0, 0x000000), CLR_INVALID, scintillacon.STYLE_INDENTGUIDE),
## Not actually a style; requires special handling to send appropriate messages to scintilla
(STYLE_SELECTION, (0, 0, 200, 0, CLR_INVALID), win32api.RGB(0xc0, 0xc0, 0xc0), 999999),
]
PythonSampleCode = """\
# Some Python
class Sample(Super):
def Fn(self):
\tself.v = 1024
dest = 'dest.html'
x = func(a + 1)|)
s = "I forget...
## A large
## comment block"""
class PythonSourceFormatter(Formatter):
string_style_names = STRING_STYLES
def GetSampleText(self):
return PythonSampleCode
def LoadStyles(self):
pass
def SetStyles(self):
for name, format, bg, ignore in PYTHON_STYLES:
self.RegisterStyle( Style(name, format, bg) )
for name, format, bg, sc_id in SPECIAL_STYLES:
self.RegisterStyle( Style(name, format, bg), sc_id )
def ClassifyWord(self, cdoc, start, end, prevWord):
word = cdoc[start:end+1].decode('latin-1')
attr = STYLE_IDENTIFIER
if prevWord == "class":
attr = STYLE_CLASS
elif prevWord == "def":
attr = STYLE_METHOD
elif word[0] in string.digits:
attr = STYLE_NUMBER
elif iskeyword(word):
attr = STYLE_KEYWORD
self.ColorSeg(start, end, attr)
return word
def ColorizeString(self, str, styleStart):
if styleStart is None: styleStart = STYLE_DEFAULT
return self.ColorizePythonCode(str, 0, styleStart)
def ColorizePythonCode(self, cdoc, charStart, styleStart):
# Straight translation of C++, should do better
lengthDoc = len(cdoc)
if lengthDoc <= charStart: return
prevWord = ""
state = styleStart
chPrev = chPrev2 = chPrev3 = ' '
chNext2 = chNext = cdoc[charStart:charStart+1].decode('latin-1')
startSeg = i = charStart
while i < lengthDoc:
ch = chNext
chNext = ' '
if i+1 < lengthDoc: chNext = cdoc[i+1:i+2].decode('latin-1')
chNext2 = ' '
if i+2 < lengthDoc: chNext2 = cdoc[i+2:i+3].decode('latin-1')
if state == STYLE_DEFAULT:
if ch in wordstarts:
self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
state = STYLE_KEYWORD
startSeg = i
elif ch == '#':
self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
if chNext == '#':
state = STYLE_COMMENT_BLOCK
else:
state = STYLE_COMMENT
startSeg = i
elif ch == '\"':
self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
startSeg = i
state = STYLE_COMMENT
if chNext == '\"' and chNext2 == '\"':
i = i + 2
state = STYLE_TQDSTRING
ch = ' '
chPrev = ' '
chNext = ' '
if i+1 < lengthDoc: chNext = cdoc[i+1]
else:
state = STYLE_STRING
elif ch == '\'':
self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
startSeg = i
state = STYLE_COMMENT
if chNext == '\'' and chNext2 == '\'':
i = i + 2
state = STYLE_TQSSTRING
ch = ' '
chPrev = ' '
chNext = ' '
if i+1 < lengthDoc: chNext = cdoc[i+1]
else:
state = STYLE_SQSTRING
elif ch in operators:
self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
self.ColorSeg(i, i, STYLE_OPERATOR)
startSeg = i+1
elif state == STYLE_KEYWORD:
if ch not in wordchars:
prevWord = self.ClassifyWord(cdoc, startSeg, i-1, prevWord)
state = STYLE_DEFAULT
startSeg = i
if ch == '#':
if chNext == '#':
state = STYLE_COMMENT_BLOCK
else:
state = STYLE_COMMENT
elif ch == '\"':
if chNext == '\"' and chNext2 == '\"':
i = i + 2
state = STYLE_TQDSTRING
ch = ' '
chPrev = ' '
chNext = ' '
if i+1 < lengthDoc: chNext = cdoc[i+1]
else:
state = STYLE_STRING
elif ch == '\'':
if chNext == '\'' and chNext2 == '\'':
i = i + 2
state = STYLE_TQSSTRING
ch = ' '
chPrev = ' '
chNext = ' '
if i+1 < lengthDoc: chNext = cdoc[i+1]
else:
state = STYLE_SQSTRING
elif ch in operators:
self.ColorSeg(startSeg, i, STYLE_OPERATOR)
startSeg = i+1
elif state == STYLE_COMMENT or state == STYLE_COMMENT_BLOCK:
if ch == '\r' or ch == '\n':
self.ColorSeg(startSeg, i-1, state)
state = STYLE_DEFAULT
startSeg = i
elif state == STYLE_STRING:
if ch == '\\':
if chNext == '\"' or chNext == '\'' or chNext == '\\':
i = i + 1
ch = chNext
chNext = ' '
if i+1 < lengthDoc: chNext = cdoc[i+1]
elif ch == '\"':
self.ColorSeg(startSeg, i, STYLE_STRING)
state = STYLE_DEFAULT
startSeg = i+1
elif state == STYLE_SQSTRING:
if ch == '\\':
if chNext == '\"' or chNext == '\'' or chNext == '\\':
i = i+1
ch = chNext
chNext = ' '
if i+1 < lengthDoc: chNext = cdoc[i+1]
elif ch == '\'':
self.ColorSeg(startSeg, i, STYLE_SQSTRING)
state = STYLE_DEFAULT
startSeg = i+1
elif state == STYLE_TQSSTRING:
if ch == '\'' and chPrev == '\'' and chPrev2 == '\'' and chPrev3 != '\\':
self.ColorSeg(startSeg, i, STYLE_TQSSTRING)
state = STYLE_DEFAULT
startSeg = i+1
elif state == STYLE_TQDSTRING and ch == '\"' and chPrev == '\"' and chPrev2 == '\"' and chPrev3 != '\\':
self.ColorSeg(startSeg, i, STYLE_TQDSTRING)
state = STYLE_DEFAULT
startSeg = i+1
chPrev3 = chPrev2
chPrev2 = chPrev
chPrev = ch
i = i + 1
if startSeg < lengthDoc:
if state == STYLE_KEYWORD:
self.ClassifyWord(cdoc, startSeg, lengthDoc-1, prevWord)
else:
self.ColorSeg(startSeg, lengthDoc-1, state)
# These taken from the SciTE properties file.
source_formatter_extensions = [
( ".py .pys .pyw".split(), scintillacon.SCLEX_PYTHON ),
( ".html .htm .asp .shtml".split(), scintillacon.SCLEX_HTML ),
( "c .cc .cpp .cxx .h .hh .hpp .hxx .idl .odl .php3 .phtml .inc .js".split(), scintillacon.SCLEX_CPP ),
( ".vbs .frm .ctl .cls".split(), scintillacon.SCLEX_VB ),
( ".pl .pm .cgi .pod".split(), scintillacon.SCLEX_PERL ),
( ".sql .spec .body .sps .spb .sf .sp".split(), scintillacon.SCLEX_SQL ),
( ".tex .sty".split(), scintillacon.SCLEX_LATEX ),
( ".xml .xul".split(), scintillacon.SCLEX_XML ),
( ".err".split(), scintillacon.SCLEX_ERRORLIST ),
( ".mak".split(), scintillacon.SCLEX_MAKEFILE ),
( ".bat .cmd".split(), scintillacon.SCLEX_BATCH ),
]
class BuiltinSourceFormatter(FormatterBase):
# A class that represents a formatter built-in to Scintilla
def __init__(self, scintilla, ext):
self.ext = ext
FormatterBase.__init__(self, scintilla)
def Colorize(self, start=0, end=-1):
self.scintilla.SendScintilla(scintillacon.SCI_COLOURISE, start, end)
def RegisterStyle(self, style, stylenum = None):
assert style.stylenum is None, "Style has already been registered"
if stylenum is None:
stylenum = self.nextstylenum
self.nextstylenum = self.nextstylenum + 1
assert self.styles.get(stylenum) is None, "We are reusing a style number!"
style.stylenum = stylenum
self.styles[style.name] = style
self.styles_by_id[stylenum] = style
def HookFormatter(self, parent = None):
sc = self.scintilla
for exts, formatter in source_formatter_extensions:
if self.ext in exts:
formatter_use = formatter
break
else:
formatter_use = scintillacon.SCLEX_PYTHON
sc.SendScintilla(scintillacon.SCI_SETLEXER, formatter_use)
keywords = ' '.join(kwlist)
sc.SCISetKeywords(keywords)
class BuiltinPythonSourceFormatter(BuiltinSourceFormatter):
sci_lexer_name = scintillacon.SCLEX_PYTHON
string_style_names = STRING_STYLES
def __init__(self, sc, ext = ".py"):
BuiltinSourceFormatter.__init__(self, sc, ext)
def SetStyles(self):
for name, format, bg, sc_id in PYTHON_STYLES:
self.RegisterStyle( Style(name, format, bg), sc_id )
for name, format, bg, sc_id in SPECIAL_STYLES:
self.RegisterStyle( Style(name, format, bg), sc_id )
def GetSampleText(self):
return PythonSampleCode

View file

@ -0,0 +1,179 @@
import string
import win32con
import win32api
import win32ui
MAPVK_VK_TO_CHAR = 2
key_name_to_vk = {}
key_code_to_name = {}
_better_names = {
"escape": "esc",
"return": "enter",
"back": "pgup",
"next": "pgdn",
}
def _fillvkmap():
# Pull the VK_names from win32con
names = [entry for entry in win32con.__dict__ if entry.startswith("VK_")]
for name in names:
code = getattr(win32con, name)
n = name[3:].lower()
key_name_to_vk[n] = code
if n in _better_names:
n = _better_names[n]
key_name_to_vk[n] = code
key_code_to_name[code] = n
_fillvkmap()
def get_vk(chardesc):
if len(chardesc)==1:
# it is a character.
info = win32api.VkKeyScan(chardesc)
if info==-1:
# Note: returning None, None causes an error when keyboard layout is non-English, see the report below
# https://stackoverflow.com/questions/45138084/pythonwin-occasionally-gives-an-error-on-opening
return 0, 0
vk = win32api.LOBYTE(info)
state = win32api.HIBYTE(info)
modifiers = 0
if state & 0x1:
modifiers |= win32con.SHIFT_PRESSED
if state & 0x2:
modifiers |= win32con.LEFT_CTRL_PRESSED | win32con.RIGHT_CTRL_PRESSED
if state & 0x4:
modifiers |= win32con.LEFT_ALT_PRESSED | win32con.RIGHT_ALT_PRESSED
return vk, modifiers
# must be a 'key name'
return key_name_to_vk.get(chardesc.lower()), 0
modifiers = {
"alt" : win32con.LEFT_ALT_PRESSED | win32con.RIGHT_ALT_PRESSED,
"lalt" : win32con.LEFT_ALT_PRESSED,
"ralt" : win32con.RIGHT_ALT_PRESSED,
"ctrl" : win32con.LEFT_CTRL_PRESSED | win32con.RIGHT_CTRL_PRESSED,
"ctl" : win32con.LEFT_CTRL_PRESSED | win32con.RIGHT_CTRL_PRESSED,
"control" : win32con.LEFT_CTRL_PRESSED | win32con.RIGHT_CTRL_PRESSED,
"lctrl" : win32con.LEFT_CTRL_PRESSED,
"lctl" : win32con.LEFT_CTRL_PRESSED,
"rctrl" : win32con.RIGHT_CTRL_PRESSED,
"rctl" : win32con.RIGHT_CTRL_PRESSED,
"shift" : win32con.SHIFT_PRESSED,
"key" : 0, # ignore key tag.
}
def parse_key_name(name):
name = name + "-" # Add a sentinal
start = pos = 0
max = len(name)
toks = []
while pos<max:
if name[pos] in "+-":
tok = name[start:pos]
# use the ascii lower() version of tok, so ascii chars require
# an explicit shift modifier - ie 'Ctrl+G' should be treated as
# 'ctrl+g' - 'ctrl+shift+g' would be needed if desired.
# This is mainly to avoid changing all the old keystroke defs
toks.append(tok.lower())
pos += 1 # skip the sep
start = pos
pos += 1
flags = 0
# do the modifiers
for tok in toks[:-1]:
mod = modifiers.get(tok.lower())
if mod is not None:
flags |= mod
# the key name
vk, this_flags = get_vk(toks[-1])
return vk, flags | this_flags
_checks = [
[ # Shift
("Shift", win32con.SHIFT_PRESSED),
],
[ # Ctrl key
("Ctrl", win32con.LEFT_CTRL_PRESSED | win32con.RIGHT_CTRL_PRESSED),
("LCtrl", win32con.LEFT_CTRL_PRESSED),
("RCtrl", win32con.RIGHT_CTRL_PRESSED),
],
[ # Alt key
("Alt", win32con.LEFT_ALT_PRESSED | win32con.RIGHT_ALT_PRESSED),
("LAlt", win32con.LEFT_ALT_PRESSED),
("RAlt", win32con.RIGHT_ALT_PRESSED),
],
]
def make_key_name(vk, flags):
# Check alt keys.
flags_done = 0
parts = []
for moddata in _checks:
for name, checkflag in moddata:
if flags & checkflag:
parts.append(name)
flags_done = flags_done & checkflag
break
if flags_done & flags:
parts.append(hex( flags & ~flags_done ) )
# Now the key name.
if vk is None:
parts.append("<Unknown scan code>")
else:
try:
parts.append(key_code_to_name[vk])
except KeyError:
# Not in our virtual key map - ask Windows what character this
# key corresponds to.
scancode = win32api.MapVirtualKey(vk, MAPVK_VK_TO_CHAR)
parts.append(chr(scancode))
sep = "+"
if sep in parts: sep = "-"
return sep.join([p.capitalize() for p in parts])
def _psc(char):
sc, mods = get_vk(char)
print("Char %s -> %d -> %s" % (repr(char), sc, key_code_to_name.get(sc)))
def test1():
for ch in """aA0/?[{}];:'"`~_-+=\\|,<.>/?""":
_psc(ch)
for code in ["Home", "End", "Left", "Right", "Up", "Down", "Menu", "Next"]:
_psc(code)
def _pkn(n):
vk, flags = parse_key_name(n)
print("%s -> %s,%s -> %s" % (n, vk, flags, make_key_name(vk, flags)))
def test2():
_pkn("ctrl+alt-shift+x")
_pkn("ctrl-home")
_pkn("Shift-+")
_pkn("Shift--")
_pkn("Shift+-")
_pkn("Shift++")
_pkn("LShift-+")
_pkn("ctl+home")
_pkn("ctl+enter")
_pkn("alt+return")
_pkn("Alt+/")
_pkn("Alt+BadKeyName")
_pkn("A") # an ascii char - should be seen as 'a'
_pkn("a")
_pkn("Shift-A")
_pkn("Shift-a")
_pkn("a")
_pkn("(")
_pkn("Ctrl+(")
_pkn("Ctrl+Shift-8")
_pkn("Ctrl+*")
_pkn("{")
_pkn("!")
_pkn(".")
if __name__=='__main__':
test2()

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,722 @@
# A general purpose MFC CCtrlView view that uses Scintilla.
from . import control
from . import IDLEenvironment # IDLE emulation.
from pywin.mfc import docview
from pywin.mfc import dialog
from . import scintillacon
import win32con
import win32ui
import afxres
import string
import array
import sys
import types
import __main__ # for attribute lookup
from . import bindings
from . import keycodes
import struct
import re
import os
PRINTDLGORD = 1538
IDC_PRINT_MAG_EDIT = 1010
EM_FORMATRANGE = win32con.WM_USER+57
wordbreaks = "._" + string.ascii_uppercase + string.ascii_lowercase + string.digits
patImport=re.compile('import (?P<name>.*)')
_event_commands = [
# File menu
"win32ui.ID_FILE_LOCATE", "win32ui.ID_FILE_CHECK", "afxres.ID_FILE_CLOSE",
"afxres.ID_FILE_NEW", "afxres.ID_FILE_OPEN", "afxres.ID_FILE_SAVE",
"afxres.ID_FILE_SAVE_AS", "win32ui.ID_FILE_SAVE_ALL",
# Edit menu
"afxres.ID_EDIT_UNDO", "afxres.ID_EDIT_REDO", "afxres.ID_EDIT_CUT",
"afxres.ID_EDIT_COPY", "afxres.ID_EDIT_PASTE", "afxres.ID_EDIT_SELECT_ALL",
"afxres.ID_EDIT_FIND", "afxres.ID_EDIT_REPEAT", "afxres.ID_EDIT_REPLACE",
# View menu
"win32ui.ID_VIEW_WHITESPACE", "win32ui.ID_VIEW_FIXED_FONT",
"win32ui.ID_VIEW_BROWSE", "win32ui.ID_VIEW_INTERACTIVE",
# Window menu
"afxres.ID_WINDOW_ARRANGE", "afxres.ID_WINDOW_CASCADE",
"afxres.ID_WINDOW_NEW", "afxres.ID_WINDOW_SPLIT",
"afxres.ID_WINDOW_TILE_HORZ", "afxres.ID_WINDOW_TILE_VERT",
# Others
"afxres.ID_APP_EXIT", "afxres.ID_APP_ABOUT",
]
_extra_event_commands = [
("EditDelete", afxres.ID_EDIT_CLEAR),
("LocateModule", win32ui.ID_FILE_LOCATE),
("GotoLine", win32ui.ID_EDIT_GOTO_LINE),
("DbgBreakpointToggle", win32ui.IDC_DBG_ADD),
("DbgGo", win32ui.IDC_DBG_GO),
("DbgStepOver", win32ui.IDC_DBG_STEPOVER),
("DbgStep", win32ui.IDC_DBG_STEP),
("DbgStepOut", win32ui.IDC_DBG_STEPOUT),
("DbgBreakpointClearAll", win32ui.IDC_DBG_CLEAR),
("DbgClose", win32ui.IDC_DBG_CLOSE),
]
event_commands = []
def _CreateEvents():
for name in _event_commands:
val = eval(name)
name_parts = name.split("_")[1:]
name_parts = [p.capitalize() for p in name_parts]
event = ''.join(name_parts)
event_commands.append((event, val))
for name, id in _extra_event_commands:
event_commands.append((name, id))
_CreateEvents()
del _event_commands; del _extra_event_commands
command_reflectors = [
(win32ui.ID_EDIT_UNDO, win32con.WM_UNDO),
(win32ui.ID_EDIT_REDO, scintillacon.SCI_REDO),
(win32ui.ID_EDIT_CUT, win32con.WM_CUT),
(win32ui.ID_EDIT_COPY, win32con.WM_COPY),
(win32ui.ID_EDIT_PASTE, win32con.WM_PASTE),
(win32ui.ID_EDIT_CLEAR, win32con.WM_CLEAR),
(win32ui.ID_EDIT_SELECT_ALL, scintillacon.SCI_SELECTALL),
]
def DoBraceMatch(control):
curPos = control.SCIGetCurrentPos()
charBefore = ' '
if curPos: charBefore = control.SCIGetCharAt(curPos-1)
charAt = control.SCIGetCharAt(curPos)
braceAtPos = braceOpposite = -1
if charBefore in "[](){}": braceAtPos = curPos-1
if braceAtPos==-1:
if charAt in "[](){}": braceAtPos = curPos
if braceAtPos != -1:
braceOpposite = control.SCIBraceMatch(braceAtPos, 0)
if braceAtPos != -1 and braceOpposite==-1:
control.SCIBraceBadHighlight(braceAtPos)
else:
# either clear them both or set them both.
control.SCIBraceHighlight(braceAtPos, braceOpposite)
def _get_class_attributes(ob):
# Recurse into base classes looking for attributes
items = []
try:
items = items + dir(ob)
for i in ob.__bases__:
for item in _get_class_attributes(i):
if item not in items:
items.append(item)
except AttributeError:
pass
return items
# Supposed to look like an MFC CEditView, but
# also supports IDLE extensions and other source code generic features.
class CScintillaView(docview.CtrlView, control.CScintillaColorEditInterface):
def __init__(self, doc):
docview.CtrlView.__init__(self, doc, "Scintilla", win32con.WS_CHILD | win32con.WS_VSCROLL | win32con.WS_HSCROLL | win32con.WS_CLIPCHILDREN | win32con.WS_VISIBLE)
self._tabWidth = 8 # Mirror of what we send to Scintilla - never change this directly
self.bAutoCompleteAttributes = 1
self.bShowCallTips = 1
self.bMatchBraces = 0 # Editor option will default this to true later!
self.bindings = bindings.BindingsManager(self)
self.idle = IDLEenvironment.IDLEEditorWindow(self)
self.idle.IDLEExtension("AutoExpand")
# SendScintilla is called so frequently it is worth optimizing.
self.SendScintilla = self._obj_.SendMessage
def OnDestroy(self, msg):
self.SendScintilla = None
return docview.CtrlView.OnDestroy(self, msg)
def _MakeColorizer(self):
ext = os.path.splitext(self.GetDocument().GetPathName())[1]
from . import formatter
return formatter.BuiltinPythonSourceFormatter(self, ext)
# def SendScintilla(self, msg, w=0, l=0):
# return self._obj_.SendMessage(msg, w, l)
def SCISetTabWidth(self, width):
# I need to remember the tab-width for the AutoIndent extension. This may go.
self._tabWidth = width
control.CScintillaEditInterface.SCISetTabWidth(self, width)
def GetTabWidth(self):
return self._tabWidth
def HookHandlers(self):
# Create events for all the menu names.
for name, val in event_commands:
# handler = lambda id, code, tosend=val, parent=parent: parent.OnCommand(tosend, 0) and 0
self.bindings.bind(name, None, cid=val)
# Hook commands that do nothing other than send Scintilla messages.
for command, reflection in command_reflectors:
handler = lambda id, code, ss=self.SendScintilla, tosend=reflection: ss(tosend) and 0
self.HookCommand(handler, command)
self.HookCommand(self.OnCmdViewWS, win32ui.ID_VIEW_WHITESPACE)
self.HookCommandUpdate(self.OnUpdateViewWS, win32ui.ID_VIEW_WHITESPACE)
self.HookCommand(self.OnCmdViewIndentationGuides, win32ui.ID_VIEW_INDENTATIONGUIDES)
self.HookCommandUpdate(self.OnUpdateViewIndentationGuides, win32ui.ID_VIEW_INDENTATIONGUIDES)
self.HookCommand(self.OnCmdViewRightEdge, win32ui.ID_VIEW_RIGHT_EDGE)
self.HookCommandUpdate(self.OnUpdateViewRightEdge, win32ui.ID_VIEW_RIGHT_EDGE)
self.HookCommand(self.OnCmdViewEOL, win32ui.ID_VIEW_EOL)
self.HookCommandUpdate(self.OnUpdateViewEOL, win32ui.ID_VIEW_EOL)
self.HookCommand(self.OnCmdViewFixedFont, win32ui.ID_VIEW_FIXED_FONT)
self.HookCommandUpdate(self.OnUpdateViewFixedFont, win32ui.ID_VIEW_FIXED_FONT)
self.HookCommand(self.OnCmdFileLocate, win32ui.ID_FILE_LOCATE)
self.HookCommand(self.OnCmdEditFind, win32ui.ID_EDIT_FIND)
self.HookCommand(self.OnCmdEditRepeat, win32ui.ID_EDIT_REPEAT)
self.HookCommand(self.OnCmdEditReplace, win32ui.ID_EDIT_REPLACE)
self.HookCommand(self.OnCmdGotoLine, win32ui.ID_EDIT_GOTO_LINE)
self.HookCommand(self.OnFilePrint, afxres.ID_FILE_PRINT)
self.HookCommand(self.OnFilePrint, afxres.ID_FILE_PRINT_DIRECT)
self.HookCommand(self.OnFilePrintPreview,
win32ui.ID_FILE_PRINT_PREVIEW)
# Key bindings.
self.HookMessage(self.OnKeyDown, win32con.WM_KEYDOWN)
self.HookMessage(self.OnKeyDown, win32con.WM_SYSKEYDOWN)
# Hook wheeley mouse events
# self.HookMessage(self.OnMouseWheel, win32con.WM_MOUSEWHEEL)
self.HookFormatter()
def OnInitialUpdate(self):
doc = self.GetDocument()
# Enable Unicode
self.SendScintilla(scintillacon.SCI_SETCODEPAGE, scintillacon.SC_CP_UTF8, 0)
self.SendScintilla(scintillacon.SCI_SETKEYSUNICODE, 1, 0)
# Create margins
self.SendScintilla(scintillacon.SCI_SETMARGINTYPEN, 1, scintillacon.SC_MARGIN_SYMBOL);
self.SendScintilla(scintillacon.SCI_SETMARGINMASKN, 1, 0xF);
self.SendScintilla(scintillacon.SCI_SETMARGINTYPEN, 2, scintillacon.SC_MARGIN_SYMBOL);
self.SendScintilla(scintillacon.SCI_SETMARGINMASKN, 2, scintillacon.SC_MASK_FOLDERS);
self.SendScintilla(scintillacon.SCI_SETMARGINSENSITIVEN, 2, 1);
self.GetDocument().HookViewNotifications(self) # is there an MFC way to grab this?
self.HookHandlers()
# Load the configuration information.
self.OnWinIniChange(None)
self.SetSel()
self.GetDocument().FinalizeViewCreation(self) # is there an MFC way to grab this?
def _GetSubConfigNames(self):
return None # By default we use only sections without sub-sections.
def OnWinIniChange(self, section = None):
self.bindings.prepare_configure()
try:
self.DoConfigChange()
finally:
self.bindings.complete_configure()
def DoConfigChange(self):
# Bit of a hack I dont kow what to do about - these should be "editor options"
from pywin.framework.editor import GetEditorOption
self.bAutoCompleteAttributes = GetEditorOption("Autocomplete Attributes", 1)
self.bShowCallTips = GetEditorOption("Show Call Tips", 1)
# Update the key map and extension data.
configManager.configure(self, self._GetSubConfigNames())
if configManager.last_error:
win32ui.MessageBox(configManager.last_error, "Configuration Error")
self.bMatchBraces = GetEditorOption("Match Braces", 1)
self.ApplyFormattingStyles(1)
def OnDestroy(self, msg):
self.bindings.close()
self.bindings = None
self.idle.close()
self.idle = None
control.CScintillaColorEditInterface.close(self)
return docview.CtrlView.OnDestroy(self, msg)
def OnMouseWheel(self, msg):
zDelta = msg[2] >> 16
vpos = self.GetScrollPos(win32con.SB_VERT)
vpos = vpos - zDelta/40 # 3 lines per notch
self.SetScrollPos(win32con.SB_VERT, vpos)
self.SendScintilla(win32con.WM_VSCROLL,
(vpos<<16) | win32con.SB_THUMBPOSITION,
0)
def OnBraceMatch(self, std, extra):
if not self.bMatchBraces: return
DoBraceMatch(self)
def OnNeedShown(self, std, extra):
notify = self.SCIUnpackNotifyMessage(extra)
# OnNeedShown is called before an edit operation when
# text is folded (as it is possible the text insertion will happen
# in a folded region.) As this happens _before_ the insert,
# we ignore the length (if we are at EOF, pos + length may
# actually be beyond the end of buffer)
self.EnsureCharsVisible(notify.position)
def EnsureCharsVisible(self, start, end = None):
if end is None: end = start
lineStart = self.LineFromChar(min(start, end))
lineEnd = self.LineFromChar(max(start, end))
while lineStart <= lineEnd:
self.SCIEnsureVisible(lineStart)
lineStart = lineStart + 1
# Helper to add an event to a menu.
def AppendMenu(self, menu, text="", event=None, flags = None, checked=0):
if event is None:
assert flags is not None, "No event or custom flags!"
cmdid = 0
else:
cmdid = self.bindings.get_command_id(event)
if cmdid is None:
# No event of that name - no point displaying it.
print('View.AppendMenu(): Unknown event "%s" specified for menu text "%s" - ignored' % (event, text))
return
keyname = configManager.get_key_binding( event, self._GetSubConfigNames() )
if keyname is not None:
text = text + "\t" + keyname
if flags is None: flags = win32con.MF_STRING|win32con.MF_ENABLED
if checked: flags = flags | win32con.MF_CHECKED
menu.AppendMenu(flags, cmdid, text)
def OnKeyDown(self, msg):
return self.bindings.fire_key_event( msg )
def GotoEndOfFileEvent(self, event):
self.SetSel(-1)
def KeyDotEvent(self, event):
## Don't trigger autocomplete if any text is selected
s,e = self.GetSel()
if s!=e:
return 1
self.SCIAddText(".")
if self.bAutoCompleteAttributes:
self._AutoComplete()
# View Whitespace/EOL/Indentation UI.
def OnCmdViewWS(self, cmd, code): # Handle the menu command
viewWS = self.SCIGetViewWS()
self.SCISetViewWS(not viewWS)
def OnUpdateViewWS(self, cmdui): # Update the tick on the UI.
cmdui.SetCheck(self.SCIGetViewWS())
cmdui.Enable()
def OnCmdViewIndentationGuides(self, cmd, code): # Handle the menu command
viewIG = self.SCIGetIndentationGuides()
self.SCISetIndentationGuides(not viewIG)
def OnUpdateViewIndentationGuides(self, cmdui): # Update the tick on the UI.
cmdui.SetCheck(self.SCIGetIndentationGuides())
cmdui.Enable()
def OnCmdViewRightEdge(self, cmd, code): # Handle the menu command
if self.SCIGetEdgeMode() == scintillacon.EDGE_NONE:
mode = scintillacon.EDGE_BACKGROUND
else:
mode = scintillacon.EDGE_NONE
self.SCISetEdgeMode(mode)
def OnUpdateViewRightEdge(self, cmdui): # Update the tick on the UI.
cmdui.SetCheck(self.SCIGetEdgeMode() != scintillacon.EDGE_NONE)
cmdui.Enable()
def OnCmdViewEOL(self, cmd, code): # Handle the menu command
viewEOL = self.SCIGetViewEOL()
self.SCISetViewEOL(not viewEOL)
def OnUpdateViewEOL(self, cmdui): # Update the tick on the UI.
cmdui.SetCheck(self.SCIGetViewEOL())
cmdui.Enable()
def OnCmdViewFixedFont(self, cmd, code): # Handle the menu command
self._GetColorizer().bUseFixed = not self._GetColorizer().bUseFixed
self.ApplyFormattingStyles(0)
# Ensure the selection is visible!
self.ScrollCaret()
def OnUpdateViewFixedFont(self, cmdui): # Update the tick on the UI.
c = self._GetColorizer()
if c is not None: cmdui.SetCheck(c.bUseFixed)
cmdui.Enable(c is not None)
def OnCmdEditFind(self, cmd, code):
from . import find
find.ShowFindDialog()
def OnCmdEditRepeat(self, cmd, code):
from . import find
find.FindNext()
def OnCmdEditReplace(self, cmd, code):
from . import find
find.ShowReplaceDialog()
def OnCmdFileLocate(self, cmd, id):
line = self.GetLine().strip()
import pywin.framework.scriptutils
m = patImport.match(line)
if m:
# Module name on this line - locate that!
modName = m.group('name')
fileName = pywin.framework.scriptutils.LocatePythonFile(modName)
if fileName is None:
win32ui.SetStatusText("Can't locate module %s" % modName)
return 1 # Let the default get it.
else:
win32ui.GetApp().OpenDocumentFile(fileName)
else:
# Just to a "normal" locate - let the default handler get it.
return 1
return 0
def OnCmdGotoLine(self, cmd, id):
try:
lineNo = int(input("Enter Line Number"))-1
except (ValueError, KeyboardInterrupt):
return 0
self.SCIEnsureVisible(lineNo)
self.SCIGotoLine(lineNo)
return 0
def SaveTextFile(self, filename, encoding=None):
doc = self.GetDocument()
doc._SaveTextToFile(self, filename, encoding=encoding)
doc.SetModifiedFlag(0)
return 1
def _AutoComplete(self):
def list2dict(l):
ret={}
for i in l:
ret[i] = None
return ret
self.SCIAutoCCancel() # Cancel old auto-complete lists.
# First try and get an object without evaluating calls
ob = self._GetObjectAtPos(bAllowCalls = 0)
# If that failed, try and process call or indexing to get the object.
if ob is None:
ob = self._GetObjectAtPos(bAllowCalls = 1)
items_dict = {}
if ob is not None:
try: # Catch unexpected errors when fetching attribute names from the object
# extra attributes of win32ui objects
if hasattr(ob, "_obj_"):
try:
items_dict.update(list2dict(dir(ob._obj_)))
except AttributeError:
pass # object has no __dict__
# normal attributes
try:
items_dict.update(list2dict(dir(ob)))
except AttributeError:
pass # object has no __dict__
if hasattr(ob, "__class__"):
items_dict.update(list2dict(_get_class_attributes(ob.__class__)))
# The object may be a COM object with typelib support - lets see if we can get its props.
# (contributed by Stefan Migowsky)
try:
# Get the automation attributes
items_dict.update(ob.__class__._prop_map_get_)
# See if there is an write only property
# could be optimized
items_dict.update(ob.__class__._prop_map_put_)
# append to the already evaluated list
except AttributeError:
pass
# The object might be a pure COM dynamic dispatch with typelib support - lets see if we can get its props.
if hasattr(ob, "_oleobj_"):
try:
for iTI in range(0,ob._oleobj_.GetTypeInfoCount()):
typeInfo = ob._oleobj_.GetTypeInfo(iTI)
self._UpdateWithITypeInfo (items_dict, typeInfo)
except:
pass
except:
win32ui.SetStatusText("Error attempting to get object attributes - %s" % (repr(sys.exc_info()[0]),))
# ensure all keys are strings.
items = [str(k) for k in items_dict.keys()]
# All names that start with "_" go!
items = [k for k in items if not k.startswith('_')]
if not items:
# Heuristics a-la AutoExpand
# The idea is to find other usages of the current binding
# and assume, that it refers to the same object (or at least,
# to an object of the same type)
# Contributed by Vadim Chugunov [vadimch@yahoo.com]
left, right = self._GetWordSplit()
if left=="": # Ignore standalone dots
return None
# We limit our search to the current class, if that
# information is available
minline, maxline, curclass = self._GetClassInfoFromBrowser()
endpos = self.LineIndex(maxline)
text = self.GetTextRange(self.LineIndex(minline),endpos)
try:
l = re.findall(r"\b"+left+"\.\w+",text)
except re.error:
# parens etc may make an invalid RE, but this code wouldnt
# benefit even if the RE did work :-)
l = []
prefix = len(left)+1
unique = {}
for li in l:
unique[li[prefix:]] = 1
# Assuming traditional usage of self...
if curclass and left=="self":
self._UpdateWithClassMethods(unique,curclass)
items = [word for word in unique.keys() if word[:2]!='__' or word[-2:]!='__']
# Ignore the word currently to the right of the dot - probably a red-herring.
try:
items.remove(right[1:])
except ValueError:
pass
if items:
items.sort()
self.SCIAutoCSetAutoHide(0)
self.SCIAutoCShow(items)
def _UpdateWithITypeInfo (self, items_dict, typeInfo):
import pythoncom
typeInfos = [typeInfo]
# suppress IDispatch and IUnknown methods
inspectedIIDs = {pythoncom.IID_IDispatch:None}
while len(typeInfos)>0:
typeInfo = typeInfos.pop()
typeAttr = typeInfo.GetTypeAttr()
if typeAttr.iid not in inspectedIIDs:
inspectedIIDs[typeAttr.iid] = None
for iFun in range(0,typeAttr.cFuncs):
funDesc = typeInfo.GetFuncDesc(iFun)
funName = typeInfo.GetNames(funDesc.memid)[0]
if funName not in items_dict:
items_dict[funName] = None
# Inspect the type info of all implemented types
# E.g. IShellDispatch5 implements IShellDispatch4 which implements IShellDispatch3 ...
for iImplType in range(0,typeAttr.cImplTypes):
iRefType = typeInfo.GetRefTypeOfImplType(iImplType)
refTypeInfo = typeInfo.GetRefTypeInfo(iRefType)
typeInfos.append(refTypeInfo)
# TODO: This is kinda slow. Probably need some kind of cache
# here that is flushed upon file save
# Or maybe we don't need the superclass methods at all ?
def _UpdateWithClassMethods(self,dict,classinfo):
if not hasattr(classinfo,"methods"):
# No 'methods' - probably not what we think it is.
return
dict.update(classinfo.methods)
for super in classinfo.super:
if hasattr(super,"methods"):
self._UpdateWithClassMethods(dict,super)
# Find which class definition caret is currently in and return
# indexes of the the first and the last lines of that class definition
# Data is obtained from module browser (if enabled)
def _GetClassInfoFromBrowser(self,pos=-1):
minline = 0
maxline = self.GetLineCount()-1
doc = self.GetParentFrame().GetActiveDocument()
browser = None
try:
if doc is not None:
browser = doc.GetAllViews()[1]
except IndexError:
pass
if browser is None:
return (minline,maxline,None) # Current window has no browser
if not browser.list: return (minline,maxline,None) # Not initialized
path = self.GetDocument().GetPathName()
if not path: return (minline,maxline,None) # No current path
import pywin.framework.scriptutils
curmodule, path = pywin.framework.scriptutils.GetPackageModuleName(path)
try:
clbrdata = browser.list.root.clbrdata
except AttributeError:
return (minline,maxline,None) # No class data for this module.
curline = self.LineFromChar(pos)
curclass = None
# Find out which class we are in
for item in clbrdata.values():
if item.module==curmodule:
item_lineno = item.lineno - 1 # Scintilla counts lines from 0, whereas pyclbr - from 1
if minline < item_lineno <= curline:
minline = item_lineno
curclass = item
if curline < item_lineno < maxline:
maxline = item_lineno
return (minline,maxline,curclass)
def _GetObjectAtPos(self, pos = -1, bAllowCalls = 0):
left, right = self._GetWordSplit(pos, bAllowCalls)
if left: # It is an attribute lookup
# How is this for a hack!
namespace = sys.modules.copy()
namespace.update(__main__.__dict__)
# Get the debugger's context.
try:
from pywin.framework import interact
if interact.edit is not None and interact.edit.currentView is not None:
globs, locs = interact.edit.currentView.GetContext()[:2]
if globs: namespace.update(globs)
if locs: namespace.update(locs)
except ImportError:
pass
try:
return eval(left, namespace)
except:
pass
return None
def _GetWordSplit(self, pos = -1, bAllowCalls = 0):
if pos==-1: pos = self.GetSel()[0]-1 # Character before current one
limit = self.GetTextLength()
before = []
after = []
index = pos-1
wordbreaks_use = wordbreaks
if bAllowCalls: wordbreaks_use = wordbreaks_use + "()[]"
while index>=0:
char = self.SCIGetCharAt(index)
if char not in wordbreaks_use: break
before.insert(0, char)
index = index-1
index = pos
while index<=limit:
char = self.SCIGetCharAt(index)
if char not in wordbreaks_use: break
after.append(char)
index=index+1
return ''.join(before), ''.join(after)
def OnPrepareDC (self, dc, pInfo):
# print "OnPrepareDC for page", pInfo.GetCurPage(), "of", pInfo.GetFromPage(), "to", pInfo.GetToPage(), ", starts=", self.starts
if dc.IsPrinting():
# Check if we are beyond the end.
# (only do this when actually printing, else messes up print preview!)
if not pInfo.GetPreview() and self.starts is not None:
prevPage = pInfo.GetCurPage() - 1
if prevPage > 0 and self.starts[prevPage] >= self.GetTextLength():
# All finished.
pInfo.SetContinuePrinting(0)
return
dc.SetMapMode(win32con.MM_TEXT);
def OnPreparePrinting(self, pInfo):
flags = win32ui.PD_USEDEVMODECOPIES | \
win32ui.PD_ALLPAGES | \
win32ui.PD_NOSELECTION # Dont support printing just a selection.
# NOTE: Custom print dialogs are stopping the user's values from coming back :-(
# self.prtDlg = PrintDialog(pInfo, PRINTDLGORD, flags)
# pInfo.SetPrintDialog(self.prtDlg)
pInfo.SetMinPage(1)
# max page remains undefined for now.
pInfo.SetFromPage(1)
pInfo.SetToPage(1)
ret = self.DoPreparePrinting(pInfo)
return ret
def OnBeginPrinting(self, dc, pInfo):
self.starts = None
return self._obj_.OnBeginPrinting(dc, pInfo)
def CalculatePageRanges(self, dc, pInfo):
# Calculate page ranges and max page
self.starts = {0:0}
metrics = dc.GetTextMetrics()
left, top, right, bottom = pInfo.GetDraw()
# Leave space at the top for the header.
rc = (left, top + int((9*metrics['tmHeight'])/2), right, bottom)
pageStart = 0
maxPage = 0
textLen = self.GetTextLength()
while pageStart < textLen:
pageStart = self.FormatRange(dc, pageStart, textLen, rc, 0)
maxPage = maxPage + 1
self.starts[maxPage] = pageStart
# And a sentinal for one page past the end
self.starts[maxPage+1] = textLen
# When actually printing, maxPage doesnt have any effect at this late state.
# but is needed to make the Print Preview work correctly.
pInfo.SetMaxPage(maxPage)
def OnFilePrintPreview(self, *arg):
self._obj_.OnFilePrintPreview()
def OnFilePrint(self, *arg):
self._obj_.OnFilePrint()
def FormatRange(self, dc, pageStart, lengthDoc, rc, draw):
"""
typedef struct _formatrange {
HDC hdc;
HDC hdcTarget;
RECT rc;
RECT rcPage;
CHARRANGE chrg;} FORMATRANGE;
"""
fmt='PPIIIIIIIIll'
hdcRender = dc.GetHandleOutput()
hdcFormat = dc.GetHandleAttrib()
fr = struct.pack(fmt, hdcRender, hdcFormat, rc[0], rc[1], rc[2], rc[3], rc[0], rc[1], rc[2], rc[3], pageStart, lengthDoc)
nextPageStart = self.SendScintilla(EM_FORMATRANGE, draw, fr)
return nextPageStart
def OnPrint(self, dc, pInfo):
metrics = dc.GetTextMetrics()
# print "dev", w, h, l, metrics['tmAscent'], metrics['tmDescent']
if self.starts is None:
self.CalculatePageRanges(dc, pInfo)
pageNum = pInfo.GetCurPage() - 1
# Setup the header of the page - docname on left, pagenum on right.
doc = self.GetDocument()
cxChar = metrics['tmAveCharWidth']
cyChar = metrics['tmHeight']
left, top, right, bottom = pInfo.GetDraw()
dc.TextOut(0, 2*cyChar, doc.GetTitle())
pagenum_str = win32ui.LoadString(afxres.AFX_IDS_PRINTPAGENUM) % (pageNum+1,)
dc.SetTextAlign(win32con.TA_RIGHT)
dc.TextOut(right, 2*cyChar, pagenum_str)
dc.SetTextAlign(win32con.TA_LEFT)
top = top + int((7*cyChar)/2)
dc.MoveTo(left, top)
dc.LineTo(right, top)
top = top + cyChar
rc = (left, top, right, bottom)
nextPageStart = self.FormatRange(dc, self.starts[pageNum], self.starts[pageNum+1], rc, 1)
def LoadConfiguration():
global configManager
# Bit of a hack I dont kow what to do about?
from .config import ConfigManager
configName = rc = win32ui.GetProfileVal("Editor", "Keyboard Config", "default")
configManager = ConfigManager(configName)
if configManager.last_error:
bTryDefault = 0
msg = "Error loading configuration '%s'\n\n%s" % (configName, configManager.last_error)
if configName != "default":
msg = msg + "\n\nThe default configuration will be loaded."
bTryDefault = 1
win32ui.MessageBox(msg)
if bTryDefault:
configManager = ConfigManager("default")
if configManager.last_error:
win32ui.MessageBox("Error loading configuration 'default'\n\n%s" % (configManager.last_error))
configManager = None
LoadConfiguration()