first commit

This commit is contained in:
Ayxan
2022-05-23 00:16:32 +04:00
commit d660f2a4ca
24786 changed files with 4428337 additions and 0 deletions

View File

@ -0,0 +1,596 @@
# 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,179 @@
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,361 @@
# 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 importlib.util
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 == importlib.util.MAGIC_NUMBER
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(importlib.util.MAGIC_NUMBER, 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 + "".join(lines), # produces correct tracebacks
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,293 @@
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,569 @@
# 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.tobytes()[:-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.tobytes()[: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,311 @@
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, try utf-8 - if that fails we will
# fallback to latin-1 to treat it as bytes...
if source_encoding is None:
source_encoding = "utf-8"
# 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 = "utf-8"
## 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,500 @@
# 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,692 @@
# 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,191 @@
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,839 @@
# 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()