play around with Scintilla and Lexilla

This commit is contained in:
2024-07-02 23:47:26 +08:00
parent d7c71f41b2
commit 727a2ec214
992 changed files with 281111 additions and 195 deletions

View File

@ -0,0 +1,64 @@
# List many windows message numbers
msgs = {
"WM_ACTIVATE":6,
"WM_ACTIVATEAPP":28,
"WM_CAPTURECHANGED":533,
"WM_CHAR":258,
"WM_CLOSE":16,
"WM_CREATE":1,
"WM_COMMAND":273,
"WM_DESTROY":2,
"WM_ENTERSIZEMOVE":561,
"WM_ERASEBKGND":20,
"WM_EXITSIZEMOVE":562,
"WM_GETMINMAXINFO":36,
"WM_GETTEXT":13,
"WM_GETTEXTLENGTH":14,
"WM_IME_SETCONTEXT":0x0281,
"WM_IME_NOTIFY":0x0282,
"WM_KEYDOWN":256,
"WM_KEYUP":257,
"WM_KILLFOCUS":8,
"WM_LBUTTONDOWN":513,
"WM_LBUTTONUP":514,
"WM_MBUTTONDOWN":519,
"WM_MBUTTONUP":520,
"WM_MBUTTONDBLCLK":521,
"WM_MOUSEACTIVATE":33,
"WM_MOUSEMOVE":512,
"WM_MOVE":3,
"WM_MOVING":534,
"WM_NCACTIVATE":134,
"WM_NCCALCSIZE":131,
"WM_NCCREATE":129,
"WM_NCDESTROY":130,
"WM_NCHITTEST":132,
"WM_NCLBUTTONDBLCLK":163,
"WM_NCLBUTTONDOWN":161,
"WM_NCLBUTTONUP":162,
"WM_NCMOUSEMOVE":160,
"WM_NCPAINT":133,
"WM_PAINT":15,
"WM_PARENTNOTIFY":528,
"WM_SETCURSOR":32,
"WM_SETFOCUS":7,
"WM_SETFONT":48,
"WM_SETTEXT":12,
"WM_SHOWWINDOW":24,
"WM_SIZE":5,
"WM_SIZING":532,
"WM_SYNCPAINT":136,
"WM_SYSCOMMAND":274,
"WM_SYSKEYDOWN":260,
"WM_TIMER":275,
"WM_USER":1024,
"WM_USER+1":1025,
"WM_WINDOWPOSCHANGED":71,
"WM_WINDOWPOSCHANGING":70,
}
sgsm={}
for k,v in msgs.items():
sgsm[v] = k

View File

@ -0,0 +1,23 @@
The test directory contains some unit and performance tests for Scintilla.
The tests can only be run on Windows using Python 2.7 or 3.x.
Python 2.7+ is required because the bytes string type and literals are available.
Scintilla must be built before running any tests.
Lexilla may be built before running tests but lexing tests will be skipped if Lexilla not available.
A test application for Windows only is in xite.py and this can be run to experiment:
pythonw xite.py
To run the basic tests:
python simpleTests.py
To check for performance regressions:
python performanceTests.py
While each test run will be different and the timer has only limited granularity, some results
from a 2 GHz Athlon with a DEBUG build are:
0.187 testAddLine
. 0.203 testAddLineMiddle
. 0.171 testHuge
. 0.203 testHugeInserts
. 0.312 testHugeReplace
.

View File

@ -0,0 +1,213 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import ctypes
from ctypes import c_int, c_char_p, c_long, c_ssize_t
def IsEnumeration(t):
return t[:1].isupper()
basicTypes = ["bool", "int", "position", "line", "pointer", "colour", "colouralpha"]
def BasicTypeOrEnumeration(t):
return t in basicTypes or IsEnumeration(t)
class TEXTRANGE(ctypes.Structure):
_fields_= (\
('cpMin', c_long),
('cpMax', c_long),
('lpstrText', ctypes.POINTER(ctypes.c_char)),
)
class TEXTRANGEFULL(ctypes.Structure):
_fields_= (\
('cpMin', c_ssize_t),
('cpMax', c_ssize_t),
('lpstrText', ctypes.POINTER(ctypes.c_char)),
)
class FINDTEXT(ctypes.Structure):
_fields_= (\
('cpMin', c_long),
('cpMax', c_long),
('lpstrText', c_char_p),
('cpMinText', c_long),
('cpMaxText', c_long),
)
class FINDTEXTFULL(ctypes.Structure):
_fields_= (\
('cpMin', c_ssize_t),
('cpMax', c_ssize_t),
('lpstrText', c_char_p),
('cpMinText', c_ssize_t),
('cpMaxText', c_ssize_t),
)
class SciCall:
def __init__(self, fn, ptr, msg, stringResult=False):
self._fn = fn
self._ptr = ptr
self._msg = msg
self._stringResult = stringResult
def __call__(self, w=0, lp=0):
ww = ctypes.cast(w, c_char_p)
if self._stringResult:
lengthBytes = self._fn(self._ptr, self._msg, ww, None)
if lengthBytes == 0:
return bytearray()
result = (ctypes.c_byte * lengthBytes)(0)
lengthBytes2 = self._fn(self._ptr, self._msg, ww, ctypes.cast(result, c_char_p))
assert lengthBytes == lengthBytes2
return bytearray(result)[:lengthBytes]
else:
ll = ctypes.cast(lp, c_char_p)
return self._fn(self._ptr, self._msg, ww, ll)
sciFX = ctypes.CFUNCTYPE(c_ssize_t, c_char_p, c_int, c_char_p, c_char_p)
class ScintillaCallable:
def __init__(self, face, scifn, sciptr):
self.__dict__["face"] = face
self.__dict__["used"] = set()
self.__dict__["all"] = set()
# The k member is for accessing constants as a dictionary
self.__dict__["k"] = {}
for f in face.features:
self.all.add(f)
if face.features[f]["FeatureType"] == "val":
self.k[f] = int(self.face.features[f]["Value"], 0)
elif face.features[f]["FeatureType"] == "evt":
self.k["SCN_"+f] = int(self.face.features[f]["Value"], 0)
scifn = sciFX(scifn)
self.__dict__["_scifn"] = scifn
self.__dict__["_sciptr"] = sciptr
def __getattr__(self, name):
if name in self.face.features:
self.used.add(name)
feature = self.face.features[name]
value = int(feature["Value"], 0)
#~ print("Feature", name, feature)
if feature["FeatureType"] == "val":
self.__dict__[name] = value
return value
else:
if feature["Param2Type"] == "stringresult" and \
name not in ["GetText", "GetLine", "GetCurLine"]:
return SciCall(self._scifn, self._sciptr, value, True)
else:
return SciCall(self._scifn, self._sciptr, value)
elif ("Get" + name) in self.face.features:
self.used.add("Get" + name)
feature = self.face.features["Get" + name]
value = int(feature["Value"], 0)
if feature["FeatureType"] == "get" and \
not name.startswith("Get") and \
not feature["Param1Type"] and \
not feature["Param2Type"] and \
BasicTypeOrEnumeration(feature["ReturnType"]):
#~ print("property", feature)
return self._scifn(self._sciptr, value, None, None)
elif name.startswith("SCN_") and name in self.k:
self.used.add(name)
feature = self.face.features[name[4:]]
value = int(feature["Value"], 0)
#~ print("Feature", name, feature)
if feature["FeatureType"] == "val":
return value
raise AttributeError(name)
def __setattr__(self, name, val):
if ("Set" + name) in self.face.features:
self.used.add("Set" + name)
feature = self.face.features["Set" + name]
value = int(feature["Value"], 0)
#~ print("setproperty", feature)
if feature["FeatureType"] == "set" and not name.startswith("Set"):
if BasicTypeOrEnumeration(feature["Param1Type"]):
return self._scifn(self._sciptr, value, c_char_p(val), None)
elif feature["Param2Type"] in ["string"]:
return self._scifn(self._sciptr, value, None, c_char_p(val))
raise AttributeError(name)
raise AttributeError(name)
def getvalue(self, name):
if name in self.face.features:
feature = self.face.features[name]
if feature["FeatureType"] != "evt":
try:
return int(feature["Value"], 0)
except ValueError:
return -1
return -1
def ByteRange(self, start, end):
tr = TEXTRANGE()
tr.cpMin = start
tr.cpMax = end
length = end - start
tr.lpstrText = ctypes.create_string_buffer(length + 1)
self.GetTextRange(0, ctypes.byref(tr))
text = tr.lpstrText[:length]
text += b"\0" * (length - len(text))
return text
def ByteRangeFull(self, start, end):
tr = TEXTRANGEFULL()
tr.cpMin = start
tr.cpMax = end
length = end - start
tr.lpstrText = ctypes.create_string_buffer(length + 1)
self.GetTextRangeFull(0, ctypes.byref(tr))
text = tr.lpstrText[:length]
text += b"\0" * (length - len(text))
return text
def StyledTextRange(self, start, end):
tr = TEXTRANGE()
tr.cpMin = start
tr.cpMax = end
length = 2 * (end - start)
tr.lpstrText = ctypes.create_string_buffer(length + 2)
self.GetStyledText(0, ctypes.byref(tr))
styledText = tr.lpstrText[:length]
styledText += b"\0" * (length - len(styledText))
return styledText
def StyledTextRangeFull(self, start, end):
tr = TEXTRANGEFULL()
tr.cpMin = start
tr.cpMax = end
length = 2 * (end - start)
tr.lpstrText = ctypes.create_string_buffer(length + 2)
self.GetStyledTextFull(0, ctypes.byref(tr))
styledText = tr.lpstrText[:length]
styledText += b"\0" * (length - len(styledText))
return styledText
def FindBytes(self, start, end, s, flags):
ft = FINDTEXT()
ft.cpMin = start
ft.cpMax = end
ft.lpstrText = s
ft.cpMinText = 0
ft.cpMaxText = 0
pos = self.FindText(flags, ctypes.byref(ft))
#~ print(start, end, ft.cpMinText, ft.cpMaxText)
return pos
def FindBytesFull(self, start, end, s, flags):
ft = FINDTEXTFULL()
ft.cpMin = start
ft.cpMax = end
ft.lpstrText = s
ft.cpMinText = 0
ft.cpMaxText = 0
pos = self.FindTextFull(flags, ctypes.byref(ft))
#~ print(start, end, ft.cpMinText, ft.cpMaxText)
return pos
def Contents(self):
return self.ByteRange(0, self.Length)
def SetContents(self, s):
self.TargetStart = 0
self.TargetEnd = self.Length
self.ReplaceTarget(len(s), s)

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
""" Define the menu structure used by the Pentacle applications """
MenuStructure = [
["&File", [
["&New", "<control>N"],
["&Open...", "<control>O"],
["&Save", "<control>S"],
["Save &As...", "<control><shift>S"],
["Test", ""],
["Exercised", ""],
["Uncalled", ""],
["-", ""],
["&Exit", ""]]],
[ "&Edit", [
["&Undo", "<control>Z"],
["&Redo", "<control>Y"],
["-", ""],
["Cu&t", "<control>X"],
["&Copy", "<control>C"],
["&Paste", "<control>V"],
["&Delete", "Del"],
["Select &All", "<control>A"],
]],
]

View File

@ -0,0 +1,577 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Requires Python 3.6 or later
from __future__ import with_statement
from __future__ import unicode_literals
import os, platform, sys, unittest
import ctypes
from ctypes import c_int, c_char_p, c_wchar_p, c_ushort, c_uint
from ctypes.wintypes import HWND, WPARAM, LPARAM, HANDLE, HBRUSH, LPCWSTR
user32=ctypes.windll.user32
gdi32=ctypes.windll.gdi32
kernel32=ctypes.windll.kernel32
from MessageNumbers import msgs, sgsm
import ScintillaCallable
import XiteMenu
scintillaIncludesLexers = False
# Lexilla may optionally be tested it is built and can be loaded
lexillaAvailable = False
scintillaDirectory = ".."
scintillaIncludeDirectory = os.path.join(scintillaDirectory, "include")
scintillaScriptsDirectory = os.path.join(scintillaDirectory, "scripts")
sys.path.append(scintillaScriptsDirectory)
import Face
scintillaBinDirectory = os.path.join(scintillaDirectory, "bin")
lexillaDirectory = os.path.join(scintillaDirectory, "..", "lexilla")
lexillaBinDirectory = os.path.join(lexillaDirectory, "bin")
lexillaIncludeDirectory = os.path.join(lexillaDirectory, "include")
lexName = "Lexilla.DLL"
try:
lexillaDLLPath = os.path.join(lexillaBinDirectory, lexName)
lexillaLibrary = ctypes.cdll.LoadLibrary(lexillaDLLPath)
createLexer = lexillaLibrary.CreateLexer
createLexer.restype = ctypes.c_void_p
lexillaAvailable = True
print("Found Lexilla")
except OSError:
print("Can't find " + lexName)
print("Python is built for " + " ".join(platform.architecture()))
WFUNC = ctypes.WINFUNCTYPE(c_int, HWND, c_uint, WPARAM, LPARAM)
WS_CHILD = 0x40000000
WS_CLIPCHILDREN = 0x2000000
WS_OVERLAPPEDWINDOW = 0xcf0000
WS_VISIBLE = 0x10000000
WS_HSCROLL = 0x100000
WS_VSCROLL = 0x200000
WA_INACTIVE = 0
MF_POPUP = 16
MF_SEPARATOR = 0x800
IDYES = 6
OFN_HIDEREADONLY = 4
MB_OK = 0
MB_YESNOCANCEL = 3
MF_CHECKED = 8
MF_UNCHECKED = 0
SW_SHOW = 5
PM_REMOVE = 1
VK_SHIFT = 16
VK_CONTROL = 17
VK_MENU = 18
class OPENFILENAME(ctypes.Structure):
_fields_ = (("lStructSize", c_int),
("hwndOwner", c_int),
("hInstance", c_int),
("lpstrFilter", c_wchar_p),
("lpstrCustomFilter", c_char_p),
("nMaxCustFilter", c_int),
("nFilterIndex", c_int),
("lpstrFile", c_wchar_p),
("nMaxFile", c_int),
("lpstrFileTitle", c_wchar_p),
("nMaxFileTitle", c_int),
("lpstrInitialDir", c_wchar_p),
("lpstrTitle", c_wchar_p),
("flags", c_int),
("nFileOffset", c_ushort),
("nFileExtension", c_ushort),
("lpstrDefExt", c_char_p),
("lCustData", c_int),
("lpfnHook", c_char_p),
("lpTemplateName", c_char_p),
("pvReserved", c_char_p),
("dwReserved", c_int),
("flagsEx", c_int))
def __init__(self, win, title):
ctypes.Structure.__init__(self)
self.lStructSize = ctypes.sizeof(OPENFILENAME)
self.nMaxFile = 1024
self.hwndOwner = win
self.lpstrTitle = title
self.Flags = OFN_HIDEREADONLY
trace = False
#~ trace = True
def WindowSize(w):
rc = ctypes.wintypes.RECT()
user32.GetClientRect(w, ctypes.byref(rc))
return rc.right - rc.left, rc.bottom - rc.top
def IsKeyDown(key):
return (user32.GetKeyState(key) & 0x8000) != 0
def KeyTranslate(w):
tr = { 9: "Tab", 0xD:"Enter", 0x1B: "Esc" }
if w in tr:
return tr[w]
elif ord("A") <= w <= ord("Z"):
return chr(w)
elif 0x70 <= w <= 0x7b:
return "F" + str(w-0x70+1)
else:
return "Unknown_" + hex(w)
class WNDCLASS(ctypes.Structure):
_fields_= (\
('style', c_int),
('lpfnWndProc', WFUNC),
('cls_extra', c_int),
('wnd_extra', c_int),
('hInst', HANDLE),
('hIcon', HANDLE),
('hCursor', HANDLE),
('hbrBackground', HBRUSH),
('menu_name', LPCWSTR),
('lpzClassName', LPCWSTR),
)
hinst = ctypes.windll.kernel32.GetModuleHandleW(0)
def RegisterClass(name, func, background = 0):
# register a window class for toplevel windows.
wc = WNDCLASS()
wc.style = 0
wc.lpfnWndProc = func
wc.cls_extra = 0
wc.wnd_extra = 0
wc.hInst = hinst
wc.hIcon = 0
wc.hCursor = 0
wc.hbrBackground = background
wc.menu_name = None
wc.lpzClassName = name
user32.RegisterClassW(ctypes.byref(wc))
class XiteWin():
def __init__(self, test=""):
self.face = Face.Face()
self.face.ReadFromFile(os.path.join(scintillaIncludeDirectory, "Scintilla.iface"))
try:
faceLex = Face.Face()
faceLex.ReadFromFile(os.path.join(lexillaIncludeDirectory, "LexicalStyles.iface"))
self.face.features.update(faceLex.features)
except FileNotFoundError:
print("Can't find " + "LexicalStyles.iface")
if scintillaIncludesLexers:
sciName = "SciLexer.DLL"
else:
sciName = "Scintilla.DLL"
try:
scintillaDLLPath = os.path.join(scintillaBinDirectory, sciName)
ctypes.cdll.LoadLibrary(scintillaDLLPath)
except OSError:
print("Can't find " + sciName)
print("Python is built for " + " ".join(platform.architecture()))
sys.exit()
self.titleDirty = True
self.fullPath = ""
self.test = test
self.appName = "xite"
self.large = "-large" in sys.argv
self.cmds = {}
self.windowName = "XiteWindow"
self.wfunc = WFUNC(self.WndProc)
RegisterClass(self.windowName, self.wfunc)
user32.CreateWindowExW(0, self.windowName, self.appName, \
WS_VISIBLE | WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, \
0, 0, 500, 700, 0, 0, hinst, 0)
args = [a for a in sys.argv[1:] if not a.startswith("-")]
self.SetMenus()
if args:
self.GrabFile(args[0])
self.FocusOnEditor()
self.ed.GotoPos(self.ed.Length)
if self.test:
print(self.test)
for k in self.cmds:
if self.cmds[k] == "Test":
user32.PostMessageW(self.win, msgs["WM_COMMAND"], k, 0)
def FocusOnEditor(self):
user32.SetFocus(self.sciHwnd)
def OnSize(self):
width, height = WindowSize(self.win)
user32.SetWindowPos(self.sciHwnd, 0, 0, 0, width, height, 0)
user32.InvalidateRect(self.win, 0, 0)
def OnCreate(self, hwnd):
self.win = hwnd
self.sciHwnd = user32.CreateWindowExW(0,
"Scintilla", "Source",
WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_CLIPCHILDREN,
0, 0, 100, 100, self.win, 0, hinst, 0)
user32.ShowWindow(self.sciHwnd, SW_SHOW)
user32.SendMessageW.restype = WPARAM
scifn = user32.SendMessageW(self.sciHwnd,
int(self.face.features["GetDirectFunction"]["Value"], 0), 0,0)
sciptr = c_char_p(user32.SendMessageW(self.sciHwnd,
int(self.face.features["GetDirectPointer"]["Value"], 0), 0,0))
self.ed = ScintillaCallable.ScintillaCallable(self.face, scifn, sciptr)
if self.large:
doc = self.ed.CreateDocument(10, 0x100)
self.ed.SetDocPointer(0, doc)
self.FocusOnEditor()
def ChooseLexer(self, lexer):
if scintillaIncludesLexers:
self.ed.LexerLanguage = lexer
elif lexillaAvailable:
pLexilla = createLexer(lexer)
self.ed.SetILexer(0, pLexilla)
else: # No lexers available
pass
def Invalidate(self):
user32.InvalidateRect(self.win, 0, 0)
def WndProc(self, h, m, wp, lp):
user32.DefWindowProcW.argtypes = [HWND, c_uint, WPARAM, LPARAM]
ms = sgsm.get(m, "XXX")
if trace:
print("%s %s %s %s" % (hex(h)[2:],ms,wp,lp))
if ms == "WM_CLOSE":
user32.PostQuitMessage(0)
elif ms == "WM_CREATE":
self.OnCreate(h)
return 0
elif ms == "WM_SIZE":
# Work out size
if wp != 1:
self.OnSize()
return 0
elif ms == "WM_COMMAND":
cmdCode = wp & 0xffff
if cmdCode in self.cmds:
self.Command(self.cmds[cmdCode])
return 0
elif ms == "WM_ACTIVATE":
if wp != WA_INACTIVE:
self.FocusOnEditor()
return 0
else:
return user32.DefWindowProcW(h, m, wp, lp)
return 0
def Command(self, name):
name = name.replace(" ", "")
method = "Cmd" + name
cmd = None
try:
cmd = getattr(self, method)
except AttributeError:
return
if cmd:
cmd()
def KeyDown(self, w, prefix = ""):
keyName = prefix
if IsKeyDown(VK_CONTROL):
keyName += "<control>"
if IsKeyDown(VK_SHIFT):
keyName += "<shift>"
keyName += KeyTranslate(w)
if trace:
print("Key:", keyName)
if keyName in self.keys:
method = "Cmd" + self.keys[keyName]
getattr(self, method)()
return True
#~ print("UKey:", keyName)
return False
def Accelerator(self, msg):
ms = sgsm.get(msg.message, "XXX")
if ms == "WM_KEYDOWN":
return self.KeyDown(msg.wParam)
elif ms == "WM_SYSKEYDOWN":
return self.KeyDown(msg.wParam, "<alt>")
return False
def AppLoop(self):
msg = ctypes.wintypes.MSG()
lpmsg = ctypes.byref(msg)
while user32.GetMessageW(lpmsg, 0, 0, 0):
if trace and msg.message != msgs["WM_TIMER"]:
print('mm', hex(msg.hWnd)[2:],sgsm.get(msg.message, "XXX"))
if not self.Accelerator(msg):
user32.TranslateMessage(lpmsg)
user32.DispatchMessageW(lpmsg)
def DoEvents(self):
msg = ctypes.wintypes.MSG()
lpmsg = ctypes.byref(msg)
cont = True
while cont:
cont = user32.PeekMessageW(lpmsg, 0, 0, 0, PM_REMOVE)
if cont:
if not self.Accelerator(msg):
user32.TranslateMessage(lpmsg)
user32.DispatchMessageW(lpmsg)
def SetTitle(self, changePath):
if changePath or self.titleDirty != self.ed.Modify:
self.titleDirty = self.ed.Modify
self.title = self.fullPath
if self.titleDirty:
self.title += " * "
else:
self.title += " - "
self.title += self.appName
if self.win:
user32.SetWindowTextW(self.win, self.title)
def Open(self):
ofx = OPENFILENAME(self.win, "Open File")
opath = ctypes.create_unicode_buffer(1024)
ofx.lpstrFile = ctypes.addressof(opath)
filters = ["Python (.py;.pyw)|*.py;*.pyw|All|*.*"]
filterText = "\0".join([f.replace("|", "\0") for f in filters])+"\0\0"
ofx.lpstrFilter = filterText
if ctypes.windll.comdlg32.GetOpenFileNameW(ctypes.byref(ofx)):
absPath = opath.value.replace("\0", "")
self.GrabFile(absPath)
self.FocusOnEditor()
self.ed.LexerLanguage = b"python"
self.ed.Lexer = self.ed.SCLEX_PYTHON
self.ed.SetKeyWords(0, b"class def else for from if import print return while")
for style in [k for k in self.ed.k if k.startswith("SCE_P_")]:
self.ed.StyleSetFont(self.ed.k[style], b"Verdana")
if "COMMENT" in style:
self.ed.StyleSetFore(self.ed.k[style], 127 * 256)
self.ed.StyleSetFont(self.ed.k[style], b"Comic Sans MS")
elif "OPERATOR" in style:
self.ed.StyleSetBold(self.ed.k[style], 1)
self.ed.StyleSetFore(self.ed.k[style], 127 * 256 * 256)
elif "WORD" in style:
self.ed.StyleSetItalic(self.ed.k[style], 255)
self.ed.StyleSetFore(self.ed.k[style], 255 * 256 * 256)
elif "TRIPLE" in style:
self.ed.StyleSetFore(self.ed.k[style], 0xA0A0)
elif "STRING" in style or "CHARACTER" in style:
self.ed.StyleSetFore(self.ed.k[style], 0xA000A0)
else:
self.ed.StyleSetFore(self.ed.k[style], 0)
def SaveAs(self):
ofx = OPENFILENAME(self.win, "Save File")
opath = "\0" * 1024
ofx.lpstrFile = opath
if ctypes.windll.comdlg32.GetSaveFileNameW(ctypes.byref(ofx)):
self.fullPath = opath.replace("\0", "")
self.Save()
self.SetTitle(1)
self.FocusOnEditor()
def SetMenus(self):
ui = XiteMenu.MenuStructure
self.cmds = {}
self.keys = {}
cmdId = 0
self.menuBar = user32.CreateMenu()
for name, contents in ui:
cmdId += 1
menu = user32.CreateMenu()
for item in contents:
text, key = item
cmdText = text.replace("&", "")
cmdText = cmdText.replace("...", "")
cmdText = cmdText.replace(" ", "")
cmdId += 1
if key:
keyText = key.replace("<control>", "Ctrl+")
keyText = keyText.replace("<shift>", "Shift+")
text += "\t" + keyText
if text == "-":
user32.AppendMenuW(menu, MF_SEPARATOR, cmdId, text)
else:
user32.AppendMenuW(menu, 0, cmdId, text)
self.cmds[cmdId] = cmdText
self.keys[key] = cmdText
#~ print(cmdId, item)
user32.AppendMenuW(self.menuBar, MF_POPUP, menu, name)
user32.SetMenu(self.win, self.menuBar)
self.CheckMenuItem("Wrap", True)
user32.ShowWindow(self.win, SW_SHOW)
def CheckMenuItem(self, name, val):
#~ print(name, val)
if self.cmds:
for k,v in self.cmds.items():
if v == name:
#~ print(name, k)
user32.CheckMenuItem(user32.GetMenu(self.win), \
k, [MF_UNCHECKED, MF_CHECKED][val])
def Exit(self):
sys.exit(0)
def DisplayMessage(self, msg, ask):
return IDYES == user32.MessageBoxW(self.win, \
msg, self.appName, [MB_OK, MB_YESNOCANCEL][ask])
def NewDocument(self):
self.ed.ClearAll()
self.ed.EmptyUndoBuffer()
self.ed.SetSavePoint()
def SaveIfUnsure(self):
if self.ed.Modify:
msg = "Save changes to \"" + self.fullPath + "\"?"
print(msg)
decision = self.DisplayMessage(msg, True)
if decision:
self.CmdSave()
return decision
return True
def New(self):
if self.SaveIfUnsure():
self.fullPath = ""
self.overrideMode = None
self.NewDocument()
self.SetTitle(1)
self.Invalidate()
def CheckMenus(self):
pass
def MoveSelection(self, caret, anchor=-1):
if anchor == -1:
anchor = caret
self.ed.SetSelectionStart(caret)
self.ed.SetSelectionEnd(anchor)
self.ed.ScrollCaret()
self.Invalidate()
def GrabFile(self, name):
self.fullPath = name
self.overrideMode = None
self.NewDocument()
fsr = open(name, "rb")
data = fsr.read()
fsr.close()
self.ed.AddText(len(data), data)
self.ed.EmptyUndoBuffer()
self.MoveSelection(0)
self.SetTitle(1)
def Save(self):
fos = open(self.fullPath, "wb")
blockSize = 1024
length = self.ed.Length
i = 0
while i < length:
grabSize = length - i
if grabSize > blockSize:
grabSize = blockSize
#~ print(i, grabSize, length)
data = self.ed.ByteRange(i, i + grabSize)
fos.write(data)
i += grabSize
fos.close()
self.ed.SetSavePoint()
self.SetTitle(0)
# Command handlers are called by menu actions
def CmdNew(self):
self.New()
def CmdOpen(self):
self.Open()
def CmdSave(self):
if (self.fullPath is None) or (len(self.fullPath) == 0):
self.SaveAs()
else:
self.Save()
def CmdSaveAs(self):
self.SaveAs()
def CmdTest(self):
runner = unittest.TextTestRunner()
if self.test:
tests = unittest.defaultTestLoader.loadTestsFromName(self.test)
else:
tests = unittest.defaultTestLoader.loadTestsFromName("simpleTests")
results = runner.run(tests)
#~ print(results)
if self.test:
user32.PostQuitMessage(0)
def CmdExercised(self):
print()
unused = sorted(self.ed.all.difference(self.ed.used))
print("Unused", len(unused))
print()
print("\n".join(unused))
print()
print("Used", len(self.ed.used))
print()
print("\n".join(sorted(self.ed.used)))
def Uncalled(self):
print("")
unused = sorted(self.ed.all.difference(self.ed.used))
uu = {}
for u in unused:
v = self.ed.getvalue(u)
if v > 2000:
uu[v] = u
#~ for x in sorted(uu.keys())[150:]:
return uu
def CmdExit(self):
self.Exit()
def CmdUndo(self):
self.ed.Undo()
def CmdRedo(self):
self.ed.Redo()
def CmdCut(self):
self.ed.Cut()
def CmdCopy(self):
self.ed.Copy()
def CmdPaste(self):
self.ed.Paste()
def CmdDelete(self):
self.ed.Clear()
xiteFrame = None
def main(test):
global xiteFrame
xiteFrame = XiteWin(test)
xiteFrame.AppLoop()
#~ xiteFrame.CmdExercised()
return xiteFrame.Uncalled()

View File

@ -0,0 +1,345 @@
<?xml version="1.0"?>
<!-- This file was automatically generated from C sources - DO NOT EDIT!
To affect the contents of this file, edit the original C definitions,
and/or use gtk-doc annotations. -->
<repository version="1.2"
xmlns="http://www.gtk.org/introspection/core/1.0"
xmlns:c="http://www.gtk.org/introspection/c/1.0"
xmlns:glib="http://www.gtk.org/introspection/glib/1.0">
<include name="Gtk" version="3.0"/>
<c:include name="Scintilla.h"/>
<c:include name="ScintillaWidget.h"/>
<namespace name="Scintilla"
version="0.1"
shared-library="libscintilla.so"
c:identifier-prefixes="Scintilla"
c:symbol-prefixes="scintilla">
<alias name="Sci_Position" c:type="Sci_Position">
<type name="gint" c:type="int"/>
</alias>
<alias name="Sci_PositionCR" c:type="Sci_PositionCR">
<type name="glong" c:type="long"/>
</alias>
<alias name="Sci_PositionU" c:type="Sci_PositionU">
<type name="guint" c:type="unsigned int"/>
</alias>
<alias name="Sci_SurfaceID" c:type="Sci_SurfaceID">
<type name="gpointer" c:type="gpointer"/>
</alias>
<alias name="sptr_t" c:type="sptr_t">
<type name="glong" c:type="long"/>
</alias>
<alias name="uptr_t" c:type="uptr_t">
<type name="gulong" c:type="unsigned long"/>
</alias>
<constant name="NOTIFY" value="sci-notify" c:type="SCINTILLA_NOTIFY">
<type name="utf8" c:type="gchar*"/>
</constant>
<class name="Object"
c:symbol-prefix="object"
c:type="ScintillaObject"
parent="Gtk.Container"
glib:type-name="ScintillaObject"
glib:get-type="scintilla_object_get_type"
glib:type-struct="ObjectClass">
<implements name="Atk.ImplementorIface"/>
<implements name="Gtk.Buildable"/>
<constructor name="new" c:identifier="scintilla_object_new">
<return-value transfer-ownership="none">
<type name="Gtk.Widget" c:type="GtkWidget*"/>
</return-value>
</constructor>
<virtual-method name="command">
<return-value transfer-ownership="none">
<type name="none" c:type="void"/>
</return-value>
<parameters>
<instance-parameter name="sci" transfer-ownership="none">
<type name="Object" c:type="ScintillaObject*"/>
</instance-parameter>
<parameter name="cmd" transfer-ownership="none">
<type name="gint" c:type="int"/>
</parameter>
<parameter name="window" transfer-ownership="none">
<type name="Gtk.Widget" c:type="GtkWidget*"/>
</parameter>
</parameters>
</virtual-method>
<virtual-method name="notify">
<return-value transfer-ownership="none">
<type name="none" c:type="void"/>
</return-value>
<parameters>
<instance-parameter name="sci" transfer-ownership="none">
<type name="Object" c:type="ScintillaObject*"/>
</instance-parameter>
<parameter name="id" transfer-ownership="none">
<type name="gint" c:type="int"/>
</parameter>
<parameter name="scn" transfer-ownership="none">
<type name="SCNotification" c:type="SCNotification*"/>
</parameter>
</parameters>
</virtual-method>
<method name="send_message" c:identifier="scintilla_object_send_message">
<return-value transfer-ownership="none">
<type name="gintptr" c:type="gintptr"/>
</return-value>
<parameters>
<instance-parameter name="sci" transfer-ownership="none">
<type name="Object" c:type="ScintillaObject*"/>
</instance-parameter>
<parameter name="iMessage" transfer-ownership="none">
<type name="guint" c:type="unsigned int"/>
</parameter>
<parameter name="wParam" transfer-ownership="none">
<type name="guintptr" c:type="guintptr"/>
</parameter>
<parameter name="lParam" transfer-ownership="none">
<type name="gintptr" c:type="gintptr"/>
</parameter>
</parameters>
</method>
<field name="cont">
<type name="Gtk.Container" c:type="GtkContainer"/>
</field>
<field name="pscin">
<type name="gpointer" c:type="void*"/>
</field>
<glib:signal name="command" when="last" action="1">
<return-value transfer-ownership="none">
<type name="none" c:type="void"/>
</return-value>
<parameters>
<parameter name="object" transfer-ownership="none">
<type name="gint" c:type="gint"/>
</parameter>
<parameter name="p0" transfer-ownership="none">
<type name="Gtk.Widget"/>
</parameter>
</parameters>
</glib:signal>
<glib:signal name="sci-notify" when="last" action="1">
<return-value transfer-ownership="none">
<type name="none" c:type="void"/>
</return-value>
<parameters>
<parameter name="object" transfer-ownership="none">
<type name="gint" c:type="gint"/>
</parameter>
<parameter name="p0" transfer-ownership="none">
<type name="SCNotification"/>
</parameter>
</parameters>
</glib:signal>
</class>
<record name="ObjectClass"
c:type="ScintillaObjectClass"
glib:is-gtype-struct-for="Object">
<field name="parent_class">
<type name="Gtk.ContainerClass" c:type="GtkContainerClass"/>
</field>
<field name="command">
<callback name="command">
<return-value transfer-ownership="none">
<type name="none" c:type="void"/>
</return-value>
<parameters>
<parameter name="sci" transfer-ownership="none">
<type name="Object" c:type="ScintillaObject*"/>
</parameter>
<parameter name="cmd" transfer-ownership="none">
<type name="gint" c:type="int"/>
</parameter>
<parameter name="window" transfer-ownership="none">
<type name="Gtk.Widget" c:type="GtkWidget*"/>
</parameter>
</parameters>
</callback>
</field>
<field name="notify">
<callback name="notify">
<return-value transfer-ownership="none">
<type name="none" c:type="void"/>
</return-value>
<parameters>
<parameter name="sci" transfer-ownership="none">
<type name="Object" c:type="ScintillaObject*"/>
</parameter>
<parameter name="id" transfer-ownership="none">
<type name="gint" c:type="int"/>
</parameter>
<parameter name="scn" transfer-ownership="none">
<type name="SCNotification" c:type="SCNotification*"/>
</parameter>
</parameters>
</callback>
</field>
</record>
<record name="SCNotification"
c:type="SCNotification"
glib:type-name="SCNotification"
glib:get-type="scnotification_get_type"
c:symbol-prefix="scnotification">
<field name="nmhdr" writable="1">
<type name="Sci_NotifyHeader" c:type="Sci_NotifyHeader"/>
</field>
<field name="position" writable="1">
<type name="Sci_Position" c:type="Sci_Position"/>
</field>
<field name="ch" writable="1">
<type name="gint" c:type="int"/>
</field>
<field name="modifiers" writable="1">
<type name="gint" c:type="int"/>
</field>
<field name="modificationType" writable="1">
<type name="gint" c:type="int"/>
</field>
<field name="text" writable="1">
<type name="utf8" c:type="const char*"/>
</field>
<field name="length" writable="1">
<type name="Sci_Position" c:type="Sci_Position"/>
</field>
<field name="linesAdded" writable="1">
<type name="Sci_Position" c:type="Sci_Position"/>
</field>
<field name="message" writable="1">
<type name="gint" c:type="int"/>
</field>
<field name="wParam" writable="1">
<type name="uptr_t" c:type="uptr_t"/>
</field>
<field name="lParam" writable="1">
<type name="sptr_t" c:type="sptr_t"/>
</field>
<field name="line" writable="1">
<type name="Sci_Position" c:type="Sci_Position"/>
</field>
<field name="foldLevelNow" writable="1">
<type name="gint" c:type="int"/>
</field>
<field name="foldLevelPrev" writable="1">
<type name="gint" c:type="int"/>
</field>
<field name="margin" writable="1">
<type name="gint" c:type="int"/>
</field>
<field name="listType" writable="1">
<type name="gint" c:type="int"/>
</field>
<field name="x" writable="1">
<type name="gint" c:type="int"/>
</field>
<field name="y" writable="1">
<type name="gint" c:type="int"/>
</field>
<field name="token" writable="1">
<type name="gint" c:type="int"/>
</field>
<field name="annotationLinesAdded" writable="1">
<type name="Sci_Position" c:type="Sci_Position"/>
</field>
<field name="updated" writable="1">
<type name="gint" c:type="int"/>
</field>
<field name="listCompletionMethod" writable="1">
<type name="gint" c:type="int"/>
</field>
</record>
<callback name="SciFnDirect">
<return-value transfer-ownership="none">
<type name="sptr_t" c:type="sptr_t"/>
</return-value>
<parameters>
<parameter name="ptr" transfer-ownership="none">
<type name="sptr_t" c:type="sptr_t"/>
</parameter>
<parameter name="iMessage" transfer-ownership="none">
<type name="guint" c:type="unsigned int"/>
</parameter>
<parameter name="wParam" transfer-ownership="none">
<type name="uptr_t" c:type="uptr_t"/>
</parameter>
<parameter name="lParam" transfer-ownership="none">
<type name="sptr_t" c:type="sptr_t"/>
</parameter>
</parameters>
</callback>
<record name="Sci_CharacterRange" c:type="Sci_CharacterRange">
<field name="cpMin" writable="1">
<type name="Sci_PositionCR" c:type="Sci_PositionCR"/>
</field>
<field name="cpMax" writable="1">
<type name="Sci_PositionCR" c:type="Sci_PositionCR"/>
</field>
</record>
<record name="Sci_NotifyHeader" c:type="Sci_NotifyHeader">
<field name="hwndFrom" writable="1">
<type name="gpointer" c:type="void*"/>
</field>
<field name="idFrom" writable="1">
<type name="uptr_t" c:type="uptr_t"/>
</field>
<field name="code" writable="1">
<type name="guint" c:type="unsigned"/>
</field>
</record>
<record name="Sci_RangeToFormat" c:type="Sci_RangeToFormat">
<field name="hdc" writable="1">
<type name="Sci_SurfaceID" c:type="Sci_SurfaceID"/>
</field>
<field name="hdcTarget" writable="1">
<type name="Sci_SurfaceID" c:type="Sci_SurfaceID"/>
</field>
<field name="rc" writable="1">
<type name="gpointer" c:type="Sci_Rectangle"/>
</field>
<field name="rcPage" writable="1">
<type name="gpointer" c:type="Sci_Rectangle"/>
</field>
<field name="chrg" writable="1">
<type name="gpointer" c:type="Sci_CharacterRange"/>
</field>
</record>
<record name="Sci_Rectangle" c:type="Sci_Rectangle">
<field name="left" writable="1">
<type name="gint" c:type="int"/>
</field>
<field name="top" writable="1">
<type name="gint" c:type="int"/>
</field>
<field name="right" writable="1">
<type name="gint" c:type="int"/>
</field>
<field name="bottom" writable="1">
<type name="gint" c:type="int"/>
</field>
</record>
<record name="Sci_TextRange" c:type="Sci_TextRange">
<field name="chrg" writable="1">
<type name="gpointer" c:type="Sci_CharacterRange"/>
</field>
<field name="lpstrText" writable="1">
<type name="utf8" c:type="char*"/>
</field>
</record>
<record name="Sci_TextToFind" c:type="Sci_TextToFind">
<field name="chrg" writable="1">
<type name="gpointer" c:type="Sci_CharacterRange"/>
</field>
<field name="lpstrText" writable="1">
<type name="utf8" c:type="const char*"/>
</field>
<field name="chrgText" writable="1">
<type name="gpointer" c:type="Sci_CharacterRange"/>
</field>
</record>
<function name="Scintilla_LinkLexers" c:identifier="Scintilla_LinkLexers">
<return-value transfer-ownership="none">
<type name="gint" c:type="int"/>
</return-value>
</function>
</namespace>
</repository>

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python
# Filters Scintilla.h to not contain generated stuff or deprecated defines
import sys
import fileinput
def main():
inhibit = 0
for line in fileinput.input():
if line.startswith("/* ++Autogenerated") or line.startswith("#ifdef INCLUDE_DEPRECATED_FEATURES"):
inhibit += 1
if inhibit == 0:
sys.stdout.write(line)
if line.startswith("/* --Autogenerated") or line.startswith("#endif"):
if (inhibit > 0):
inhibit -= 1
if __name__ == "__main__":
main()

View File

@ -0,0 +1,26 @@
#!/usr/bin/python
import gi
gi.require_version('Scintilla', '0.1')
# Scintilla is imported before because it loads Gtk with a specified version
# this avoids a warning when Gtk is imported without version such as below (where
# it is imported without because this script works with gtk2 and gtk3)
from gi.repository import Scintilla
from gi.repository import Gtk
def on_notify(sci, id, scn):
if (scn.nmhdr.code == 2001): # SCN_CHARADDED
print ("sci-notify: id: %d, char added: %d" % (id, scn.ch))
elif (scn.nmhdr.code == 2008): # SCN_MODIFIED
print ("sci-notify: id: %d, pos: %d, mod type: %d" % (id, scn.position, scn.modificationType))
else:
print ("sci-notify: id: %d, scn.nmhdr.code: %d" % (id, scn.nmhdr.code))
win = Gtk.Window()
win.connect("delete-event", Gtk.main_quit)
sci = Scintilla.Object()
sci.connect("sci-notify", on_notify)
win.add(sci)
win.show_all()
win.resize(400,300)
Gtk.main()

View File

@ -0,0 +1,53 @@
# Build gir and test
# For Ubuntu, these may be needed:
# apt-get install gobject-introspection
# apt-get install libgirepository1.0-dev
all: Scintilla-0.1.typelib
ifdef GTK3
GTKVERSION=3.0
else
GTKVERSION=2.0
endif
GI_SCANNER = g-ir-scanner
GI_COMPILER = g-ir-compiler
GTK_LIBS = $(shell pkg-config --libs gtk+-$(GTKVERSION))
GTK_CFLAGS = $(shell pkg-config --cflags gtk+-$(GTKVERSION))
PWD = $(shell pwd)
.PHONY: test clean FORCE
../../bin/scintilla.a: FORCE
$(MAKE) -C ../../gtk all
Scintilla-filtered.h: ../../include/Scintilla.h
python filter-scintilla-h.py $< > $@
libscintilla.so: ../../bin/scintilla.a
$(CXX) -shared -o $@ -Wl,--whole-archive $^ -Wl,--no-whole-archive $(GTK_LIBS)
Scintilla-0.1.gir: libscintilla.so Scintilla-filtered.h
LDFLAGS=-Wl,-rpath=$(shell pwd) \
$(GI_SCANNER) --no-libtool --warn-all -i Gtk-$(GTKVERSION) -DG_IR_SCANNING -DGTK \
--cflags-begin $(GTK_CFLAGS) -include gtk/gtk.h \
-include Scintilla-filtered.h -I../../include --cflags-end \
--accept-unprefixed \
--c-include Scintilla.h --c-include ScintillaWidget.h \
-n Scintilla --nsversion 0.1 --library scintilla -L$(PWD) \
../../include/Sci_Position.h ../../include/ScintillaWidget.h Scintilla-filtered.h \
-o $@
Scintilla-0.1.typelib: Scintilla-0.1.gir
$(GI_COMPILER) $^ -o $@
clean:
rm -f libscintilla.so Scintilla-0.1.gir Scintilla-0.1.typelib Scintilla-filtered.h
$(MAKE) -C ../../gtk clean
test: Scintilla-0.1.gir Scintilla-0.1.typelib
@echo Verifying Scintilla-0.1.gir file
@diff $<.good $< || (echo "GIR FILE MISMATCH!"; exit 1)
@echo Launching gi-test.py python program
GI_TYPELIB_PATH=$(PWD) LD_LIBRARY_PATH=$(PWD) \
python $(PWD)/gi-test.py

View File

@ -0,0 +1,149 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Requires Python 2.7 or later
from __future__ import with_statement
from __future__ import unicode_literals
import string, time, unittest
try:
start = time.perf_counter()
timer = time.perf_counter
except AttributeError:
timer = time.time
import XiteWin as Xite
class TestPerformance(unittest.TestCase):
def setUp(self):
self.xite = Xite.xiteFrame
self.ed = self.xite.ed
self.ed.ClearAll()
self.ed.EmptyUndoBuffer()
def testAddLine(self):
data = (string.ascii_letters + string.digits + "\n").encode('utf-8')
start = timer()
for i in range(2000):
self.ed.AddText(len(data), data)
self.assertEqual(self.ed.LineCount, i + 2)
end = timer()
duration = end - start
print("%6.3f testAddLine" % duration)
self.xite.DoEvents()
self.assertTrue(self.ed.Length > 0)
def testAddLineMiddle(self):
data = (string.ascii_letters + string.digits + "\n").encode('utf-8')
start = timer()
for i in range(2000):
self.ed.AddText(len(data), data)
self.assertEqual(self.ed.LineCount, i + 2)
end = timer()
duration = end - start
print("%6.3f testAddLineMiddle" % duration)
self.xite.DoEvents()
self.assertTrue(self.ed.Length > 0)
def testHuge(self):
data = (string.ascii_letters + string.digits + "\n").encode('utf-8')
data = data * 100000
start = timer()
self.ed.AddText(len(data), data)
end = timer()
duration = end - start
print("%6.3f testHuge" % duration)
self.xite.DoEvents()
self.assertTrue(self.ed.Length > 0)
def testHugeInserts(self):
data = (string.ascii_letters + string.digits + "\n").encode('utf-8')
data = data * 100000
insert = (string.digits + "\n").encode('utf-8')
self.ed.AddText(len(data), data)
start = timer()
for i in range(2000):
self.ed.InsertText(0, insert)
end = timer()
duration = end - start
print("%6.3f testHugeInserts" % duration)
self.xite.DoEvents()
self.assertTrue(self.ed.Length > 0)
def testHugeReplace(self):
oneLine = (string.ascii_letters + string.digits + "\n").encode('utf-8')
data = oneLine * 100000
insert = (string.digits + "\n").encode('utf-8')
self.ed.AddText(len(data), data)
start = timer()
for i in range(1000):
self.ed.TargetStart = i * len(insert)
self.ed.TargetEnd = self.ed.TargetStart + len(oneLine)
self.ed.ReplaceTarget(len(insert), insert)
end = timer()
duration = end - start
print("%6.3f testHugeReplace" % duration)
self.xite.DoEvents()
self.assertTrue(self.ed.Length > 0)
def testUTF8CaseSearches(self):
self.ed.SetCodePage(65001)
oneLine = "Fold Margin=折りたたみ表示用の余白(&F)\n".encode('utf-8')
manyLines = oneLine * 100000
manyLines = manyLines + "φ\n".encode('utf-8')
self.ed.AddText(len(manyLines), manyLines)
searchString = "φ".encode('utf-8')
start = timer()
for i in range(1000):
self.ed.TargetStart = 0
self.ed.TargetEnd = self.ed.Length-1
self.ed.SearchFlags = self.ed.SCFIND_MATCHCASE
pos = self.ed.SearchInTarget(len(searchString), searchString)
self.assertTrue(pos > 0)
end = timer()
duration = end - start
print("%6.3f testUTF8CaseSearches" % duration)
self.xite.DoEvents()
def testUTF8Searches(self):
self.ed.SetCodePage(65001)
oneLine = "Fold Margin=折りたたみ表示用の余白(&F)\n".encode('utf-8')
manyLines = oneLine * 100000
manyLines = manyLines + "φ\n".encode('utf-8')
self.ed.AddText(len(manyLines), manyLines)
searchString = "φ".encode('utf-8')
start = timer()
for i in range(20):
self.ed.TargetStart = 0
self.ed.TargetEnd = self.ed.Length-1
self.ed.SearchFlags = 0
pos = self.ed.SearchInTarget(len(searchString), searchString)
self.assertTrue(pos > 0)
end = timer()
duration = end - start
print("%6.3f testUTF8Searches" % duration)
self.xite.DoEvents()
def testUTF8AsciiSearches(self):
self.ed.SetCodePage(65001)
oneLine = "Fold Margin=NagasakiOsakaHiroshimaHanedaKyoto(&F)\n".encode('utf-8')
manyLines = oneLine * 100000
manyLines = manyLines + "φ\n".encode('utf-8')
self.ed.AddText(len(manyLines), manyLines)
searchString = "φ".encode('utf-8')
start = timer()
for i in range(20):
self.ed.TargetStart = 0
self.ed.TargetEnd = self.ed.Length-1
self.ed.SearchFlags = 0
pos = self.ed.SearchInTarget(len(searchString), searchString)
self.assertTrue(pos > 0)
end = timer()
duration = end - start
print("%6.3f testUTF8AsciiSearches" % duration)
self.xite.DoEvents()
if __name__ == '__main__':
Xite.main("performanceTests")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,15 @@
The test/unit directory contains unit tests for Scintilla data structures.
The tests can be run on Windows, macOS, or Linux using g++ and GNU make.
The Catch test framework is used.
https://github.com/philsquared/Catch
The file catch.hpp is under the Boost Software License which is contained in LICENSE_1_0.txt
To run the tests on macOS or Linux:
make test
To run the tests on Windows:
mingw32-make test
Visual C++ (2010+) and nmake can also be used on Windows:
nmake -f test.mak test

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="Scintilla::Internal::SplitVector&lt;*&gt;">
<DisplayString>{{size = {lengthBody}}}</DisplayString>
<Expand>
<Item Name="[size]">lengthBody</Item>
<Item Name="[part1Length]">part1Length</Item>
<Item Name="[gap]">gapLength</Item>
<IndexListItems>
<Size>lengthBody</Size>
<ValueNode>body[($i&lt;part1Length)?$i:$i+gapLength]</ValueNode>
</IndexListItems>
</Expand>
</Type>
<Type Name="Scintilla::Internal::XSparseVector&lt;*&gt;">
<DisplayString>{{size = {values->lengthBody}}}</DisplayString>
<Expand>
<IndexListItems>
<Size>values->lengthBody</Size>
<ValueNode>starts->body->body[($i&lt;starts->body->part1Length)?$i:$i+starts->body->gapLength]+($i&gt;starts->stepPartition?starts->stepLength:0)</ValueNode>
</IndexListItems>
<IndexListItems>
<Size>values->lengthBody</Size>
<ValueNode>values->body->body[($i&lt;values->body->part1Length)?$i:$i+values->body->gapLength]+($i&gt;values->stepPartition?values->stepLength:0)</ValueNode>
</IndexListItems>
</Expand>
</Type>
<Type Name="Scintilla::Internal::Partitioning&lt;*&gt;">
<DisplayString>{{size = {body->lengthBody}}}</DisplayString>
<Expand>
<IndexListItems>
<Size>body->lengthBody</Size>
<ValueNode>body->body[($i&lt;body->part1Length)?$i:$i+body->gapLength]+($i&gt;stepPartition?stepLength:0)</ValueNode>
</IndexListItems>
</Expand>
</Type>
<Type Name="std::unique_ptr&lt;*&gt;">
<SmartPointer Usage="Minimal">_Mypair._Myval2</SmartPointer>
<DisplayString Condition="_Mypair._Myval2 == 0">empty</DisplayString>
<DisplayString Condition="_Mypair._Myval2 != 0">unique_ptr {*_Mypair._Myval2}</DisplayString>
<Expand>
<ExpandedItem Condition="_Mypair._Myval2 != 0">_Mypair._Myval2</ExpandedItem>
<ExpandedItem Condition="_Mypair._Myval2 != 0">_Mypair</ExpandedItem>
</Expand>
</Type>
</AutoVisualizer>

View File

@ -0,0 +1,5 @@
command.go.*.cxx=./unitTest
if PLAT_WIN
make.command=mingw32-make
command.go.*.cxx=unitTest
command.go.needs.$(file.patterns.cplusplus)=$(make.command)

View File

@ -0,0 +1,47 @@
/** @file UnitTester.cxx
** UnitTester.cpp : Defines the entry point for the console application.
**/
// Catch uses std::uncaught_exception which is deprecated in C++17.
// This define silences a warning from Visual C++.
#define _SILENCE_CXX17_UNCAUGHT_EXCEPTION_DEPRECATION_WARNING
#include <cstdio>
#include <cstdarg>
#include <string_view>
#include <vector>
#include <optional>
#include <memory>
#include "Debugging.h"
#if defined(_WIN32)
#define CATCH_CONFIG_WINDOWS_CRTDBG
#endif
#define CATCH_CONFIG_RUNNER
#include "catch.hpp"
using namespace Scintilla::Internal;
// Needed for PLATFORM_ASSERT in code being tested
void Platform::Assert(const char *c, const char *file, int line) noexcept {
fprintf(stderr, "Assertion [%s] failed at %s %d\n", c, file, line);
abort();
}
void Platform::DebugPrintf(const char *format, ...) noexcept {
char buffer[2000];
va_list pArguments;
va_start(pArguments, format);
vsnprintf(buffer, std::size(buffer), format, pArguments);
va_end(pArguments);
fprintf(stderr, "%s", buffer);
}
int main(int argc, char* argv[]) {
const int result = Catch::Session().run(argc, argv);
return result;
}

View File

@ -0,0 +1,180 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{35688A27-D91B-453A-8A05-65A7F28DEFBF}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>UnitTester</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<EnableClangTidyCodeAnalysis>true</EnableClangTidyCodeAnalysis>
<CodeAnalysisRuleSet>..\..\..\..\..\Users\Neil\SensibleRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>CHECK_CORRECTNESS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\include\;..\..\src\;..\..\lexlib\</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>CHECK_CORRECTNESS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\include\;..\..\src\;..\..\lexlib\</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>CHECK_CORRECTNESS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\include\;..\..\src\;..\..\lexlib\</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>CHECK_CORRECTNESS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\include\;..\..\src\;..\..\lexlib\</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\src\CaseConvert.cxx" />
<ClCompile Include="..\..\src\CaseFolder.cxx" />
<ClCompile Include="..\..\src\CellBuffer.cxx" />
<ClCompile Include="..\..\src\ChangeHistory.cxx" />
<ClCompile Include="..\..\src\CharacterCategoryMap.cxx" />
<ClCompile Include="..\..\src\CharClassify.cxx" />
<ClCompile Include="..\..\src\ContractionState.cxx" />
<ClCompile Include="..\..\src\Decoration.cxx" />
<ClCompile Include="..\..\src\Document.cxx" />
<ClCompile Include="..\..\src\Geometry.cxx" />
<ClCompile Include="..\..\src\PerLine.cxx" />
<ClCompile Include="..\..\src\RESearch.cxx" />
<ClCompile Include="..\..\src\RunStyles.cxx" />
<ClCompile Include="..\..\src\UndoHistory.cxx" />
<ClCompile Include="..\..\src\UniConversion.cxx" />
<ClCompile Include="..\..\src\UniqueString.cxx" />
<ClCompile Include="test*.cxx" />
<ClCompile Include="UnitTester.cxx" />
</ItemGroup>
<ItemGroup>
<Natvis Include="Sci.natvis" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,89 @@
# Build all the unit tests using GNU make and either g++ or clang
# Should be run using mingw32-make on Windows, not nmake
# On Windows g++ is used, on macOS clang, and on Linux G++ is used by default
# but clang can be used by defining CLANG when invoking make
# clang works only with libc++, not libstdc++
# Tested with clang 9 and g++ 9
CXXSTD=c++17
ifndef windir
ifeq ($(shell uname),Darwin)
# On macOS (detected with Darwin uname) always use clang as g++ is old version
CLANG = 1
USELIBCPP = 1
endif
endif
CXXFLAGS += $(OPTIMIZATION)
CXXFLAGS += --std=$(CXXSTD)
ifdef CLANG
CXX = clang++
ifdef USELIBCPP
# macOS, use libc++ but don't have sanitizers
CXXFLAGS += --stdlib=libc++
else
ifndef windir
# Linux, have sanitizers
SANITIZE = -fsanitize=address,undefined
CXXFLAGS += $(SANITIZE)
endif
endif
else
CXX = g++
endif
ifdef windir
DEL = del /q
EXE = unitTest.exe
else
DEL = rm -f
EXE = unitTest
endif
vpath %.cxx ../../src
INCLUDEDIRS = -I ../../include -I ../../src
CPPFLAGS += $(INCLUDEDIRS)
CXXFLAGS += -Wall -Wextra
# Files in this directory containing tests
TESTSRC=$(wildcard test*.cxx)
TESTOBJ=$(TESTSRC:.cxx=.o)
# Files being tested from scintilla/src directory
TESTEDOBJ=\
CaseConvert.o \
CaseFolder.o \
CellBuffer.o \
ChangeHistory.o \
CharacterCategoryMap.o \
CharClassify.o \
ContractionState.o \
Decoration.o \
Document.o \
Geometry.o \
PerLine.o \
RESearch.o \
RunStyles.o \
UndoHistory.o \
UniConversion.o \
UniqueString.o
TESTS=$(EXE)
all: $(TESTS)
test: $(TESTS)
./$(EXE)
clean:
$(DEL) $(TESTS) *.o *.obj *.exe
%.o: %.cxx
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
$(EXE): $(TESTOBJ) $(TESTEDOBJ) unitTest.o
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LINKFLAGS) $^ -o $@

View File

@ -0,0 +1,43 @@
# Build all the unit tests with Microsoft Visual C++ using nmake
# Tested with Visual C++ 2019
DEL = del /q
EXE = unitTest.exe
INCLUDEDIRS = /I../../include /I../../src
CXXFLAGS = /MP /EHsc /std:c++17 $(OPTIMIZATION) /nologo /D_HAS_AUTO_PTR_ETC=1 /wd 4805 $(INCLUDEDIRS)
# Files in this directory containing tests
TESTSRC=test*.cxx
# Files being tested from scintilla/src directory
TESTEDSRC=\
../../src/CaseConvert.cxx \
../../src/CaseFolder.cxx \
../../src/CellBuffer.cxx \
../../src/ChangeHistory.cxx \
../../src/CharacterCategoryMap.cxx \
../../src/CharClassify.cxx \
../../src/ContractionState.cxx \
../../src/Decoration.cxx \
../../src/Document.cxx \
../../src/Geometry.cxx \
../../src/PerLine.cxx \
../../src/RESearch.cxx \
../../src/RunStyles.cxx \
../../src/UndoHistory.cxx \
../../src/UniConversion.cxx \
../../src/UniqueString.cxx
TESTS=$(EXE)
all: $(TESTS)
test: $(TESTS)
$(EXE)
clean:
$(DEL) $(TESTS) *.o *.obj *.exe
$(EXE): $(TESTSRC) $(TESTEDSRC) $(@B).obj
$(CXX) $(CXXFLAGS) /Fe$@ $**

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,124 @@
/** @file testCharClassify.cxx
** Unit Tests for Scintilla internal data structures
**/
#include <cstring>
#include <string_view>
#include <vector>
#include <optional>
#include <algorithm>
#include <memory>
#include <iostream>
#include "Debugging.h"
#include "CharClassify.h"
#include "catch.hpp"
using namespace Scintilla::Internal;
// Test CharClassify.
class CharClassifyTest {
protected:
CharClassifyTest() {
pcc = std::make_unique<CharClassify>();
for (int ch = 0; ch < 256; ch++) {
if (ch == '\r' || ch == '\n')
charClass[ch] = CharacterClass::newLine;
else if (ch < 0x20 || ch == ' ' || ch == '\x7f')
charClass[ch] = CharacterClass::space;
else if (ch >= 0x80 || isalnum(ch) || ch == '_')
charClass[ch] = CharacterClass::word;
else
charClass[ch] = CharacterClass::punctuation;
}
}
// Avoid warnings, deleted so never called.
CharClassifyTest(const CharClassifyTest &) = delete;
std::unique_ptr<CharClassify> pcc;
CharacterClass charClass[256] {};
static const char* GetClassName(CharacterClass charClass) noexcept {
switch(charClass) {
#define CASE(c) case CharacterClass::c: return #c
CASE(space);
CASE(newLine);
CASE(word);
CASE(punctuation);
#undef CASE
default:
return "<unknown>";
}
}
};
TEST_CASE_METHOD(CharClassifyTest, "Defaults") {
for (int i = 0; i < 256; i++) {
if (charClass[i] != pcc->GetClass(i))
std::cerr
<< "Character " << i
<< " should be class " << GetClassName(charClass[i])
<< ", but got " << GetClassName(pcc->GetClass(i)) << std::endl;
REQUIRE(charClass[i] == pcc->GetClass(i));
}
}
TEST_CASE_METHOD(CharClassifyTest, "Custom") {
unsigned char buf[2] = {0, 0};
for (int i = 0; i < 256; i++) {
const CharacterClass thisClass = static_cast<CharacterClass>(i % 4);
buf[0] = i;
pcc->SetCharClasses(buf, thisClass);
charClass[i] = thisClass;
}
for (int i = 0; i < 256; i++) {
if (charClass[i] != pcc->GetClass(i))
std::cerr
<< "Character " << i
<< " should be class " << GetClassName(charClass[i])
<< ", but got " << GetClassName(pcc->GetClass(i)) << std::endl;
REQUIRE(charClass[i] == pcc->GetClass(i));
}
}
TEST_CASE_METHOD(CharClassifyTest, "CharsOfClass") {
unsigned char buf[2] = {0, 0};
for (int i = 1; i < 256; i++) {
const CharacterClass thisClass = static_cast<CharacterClass>(i % 4);
buf[0] = i;
pcc->SetCharClasses(buf, thisClass);
charClass[i] = thisClass;
}
for (int classVal = 0; classVal < 4; ++classVal) {
const CharacterClass thisClass = static_cast<CharacterClass>(classVal % 4);
const int size = pcc->GetCharsOfClass(thisClass, nullptr);
std::vector<unsigned char> buffer(size+1);
const unsigned char *pBuffer = buffer.data();
pcc->GetCharsOfClass(thisClass, buffer.data());
for (int i = 1; i < 256; i++) {
if (charClass[i] == thisClass) {
if (!memchr(pBuffer, i, size))
std::cerr
<< "Character " << i
<< " should be class " << GetClassName(thisClass)
<< ", but was not in GetCharsOfClass;"
<< " it is reported to be "
<< GetClassName(pcc->GetClass(i)) << std::endl;
REQUIRE(memchr(pBuffer, i, size));
} else {
if (memchr(pBuffer, i, size))
std::cerr
<< "Character " << i
<< " should not be class " << GetClassName(thisClass)
<< ", but was in GetCharsOfClass"
<< " it is reported to be "
<< GetClassName(pcc->GetClass(i)) << std::endl;
REQUIRE_FALSE(memchr(pBuffer, i, size));
}
}
}
}

View File

@ -0,0 +1,73 @@
/** @file testCharacterCategoryMap.cxx
** Unit Tests for Scintilla internal data structures
**/
#include <cstddef>
#include <cstring>
#include <stdexcept>
#include <string_view>
#include <vector>
#include <optional>
#include <algorithm>
#include <memory>
#include "Debugging.h"
#include "CharacterCategoryMap.h"
#include "catch.hpp"
using namespace Scintilla;
using namespace Scintilla::Internal;
// Test CharacterCategoryMap.
TEST_CASE("CharacterCategoryMap") {
const CharacterCategoryMap ccm;
SECTION("LowerCaseLetter") {
const CharacterCategory cc = ccm.CategoryFor('a');
REQUIRE(cc == CharacterCategory::ccLl);
}
SECTION("All") {
REQUIRE(ccm.CategoryFor('A') == CharacterCategory::ccLu);
REQUIRE(ccm.CategoryFor('a') == CharacterCategory::ccLl);
REQUIRE(ccm.CategoryFor(0x01C5) == CharacterCategory::ccLt);
REQUIRE(ccm.CategoryFor(0x0E46) == CharacterCategory::ccLm);
REQUIRE(ccm.CategoryFor(0x4E00) == CharacterCategory::ccLo);
REQUIRE(ccm.CategoryFor(0x0300) == CharacterCategory::ccMn);
REQUIRE(ccm.CategoryFor(0x0903) == CharacterCategory::ccMc);
REQUIRE(ccm.CategoryFor(0x20E0) == CharacterCategory::ccMe);
REQUIRE(ccm.CategoryFor('7') == CharacterCategory::ccNd);
REQUIRE(ccm.CategoryFor(0x2160) == CharacterCategory::ccNl);
REQUIRE(ccm.CategoryFor(0x00BC) == CharacterCategory::ccNo);
REQUIRE(ccm.CategoryFor('_') == CharacterCategory::ccPc);
REQUIRE(ccm.CategoryFor('-') == CharacterCategory::ccPd);
REQUIRE(ccm.CategoryFor('(') == CharacterCategory::ccPs);
REQUIRE(ccm.CategoryFor('}') == CharacterCategory::ccPe);
REQUIRE(ccm.CategoryFor(0x00AB) == CharacterCategory::ccPi);
REQUIRE(ccm.CategoryFor(0x00BB) == CharacterCategory::ccPf);
REQUIRE(ccm.CategoryFor('"') == CharacterCategory::ccPo);
REQUIRE(ccm.CategoryFor('+') == CharacterCategory::ccSm);
REQUIRE(ccm.CategoryFor('$') == CharacterCategory::ccSc);
REQUIRE(ccm.CategoryFor(0x02C2) == CharacterCategory::ccSk);
REQUIRE(ccm.CategoryFor(0x00A6) == CharacterCategory::ccSo);
REQUIRE(ccm.CategoryFor(' ') == CharacterCategory::ccZs);
REQUIRE(ccm.CategoryFor(0x2028) == CharacterCategory::ccZl);
REQUIRE(ccm.CategoryFor(0x2029) == CharacterCategory::ccZp);
REQUIRE(ccm.CategoryFor('\n') == CharacterCategory::ccCc);
REQUIRE(ccm.CategoryFor(0x00AD) == CharacterCategory::ccCf);
REQUIRE(ccm.CategoryFor(0xD800) == CharacterCategory::ccCs);
REQUIRE(ccm.CategoryFor(0xE000) == CharacterCategory::ccCo);
REQUIRE(ccm.CategoryFor(0xFFFE) == CharacterCategory::ccCn);
}
}

View File

@ -0,0 +1,205 @@
/** @file testContractionState.cxx
** Unit Tests for Scintilla internal data structures
**/
#include <cstddef>
#include <cstring>
#include <stdexcept>
#include <string_view>
#include <vector>
#include <optional>
#include <algorithm>
#include <memory>
#include "Debugging.h"
#include "Position.h"
#include "UniqueString.h"
#include "SplitVector.h"
#include "Partitioning.h"
#include "RunStyles.h"
#include "ContractionState.h"
#include "catch.hpp"
using namespace Scintilla::Internal;
// Test ContractionState.
TEST_CASE("ContractionState") {
std::unique_ptr<IContractionState> pcs = ContractionStateCreate(false);
SECTION("IsEmptyInitially") {
REQUIRE(1 == pcs->LinesInDoc());
REQUIRE(1 == pcs->LinesDisplayed());
REQUIRE(0 == pcs->DisplayFromDoc(0));
REQUIRE(0 == pcs->DocFromDisplay(0));
}
SECTION("OneLine") {
pcs->InsertLines(0, 1);
REQUIRE(2 == pcs->LinesInDoc());
REQUIRE(2 == pcs->LinesDisplayed());
REQUIRE(0 == pcs->DisplayFromDoc(0));
REQUIRE(0 == pcs->DocFromDisplay(0));
REQUIRE(1 == pcs->DisplayFromDoc(1));
REQUIRE(1 == pcs->DocFromDisplay(1));
}
SECTION("InsertionThenDeletions") {
pcs->InsertLines(0,4);
pcs->DeleteLines(1, 1);
REQUIRE(4 == pcs->LinesInDoc());
REQUIRE(4 == pcs->LinesDisplayed());
for (int l=0;l<4;l++) {
REQUIRE(l == pcs->DisplayFromDoc(l));
REQUIRE(l == pcs->DocFromDisplay(l));
}
pcs->DeleteLines(0,2);
REQUIRE(2 == pcs->LinesInDoc());
REQUIRE(2 == pcs->LinesDisplayed());
for (int l=0;l<2;l++) {
REQUIRE(l == pcs->DisplayFromDoc(l));
REQUIRE(l == pcs->DocFromDisplay(l));
}
}
SECTION("ShowHide") {
pcs->InsertLines(0,4);
REQUIRE(true == pcs->GetVisible(0));
REQUIRE(true == pcs->GetVisible(1));
REQUIRE(true == pcs->GetVisible(2));
REQUIRE(5 == pcs->LinesDisplayed());
pcs->SetVisible(1, 1, false);
REQUIRE(true == pcs->GetVisible(0));
REQUIRE(false == pcs->GetVisible(1));
REQUIRE(true == pcs->GetVisible(2));
REQUIRE(4 == pcs->LinesDisplayed());
REQUIRE(true == pcs->HiddenLines());
pcs->SetVisible(1, 2, true);
for (int l=0;l<4;l++) {
REQUIRE(true == pcs->GetVisible(0));
}
pcs->SetVisible(1, 1, false);
REQUIRE(false == pcs->GetVisible(1));
pcs->ShowAll();
for (int l=0;l<4;l++) {
REQUIRE(true == pcs->GetVisible(0));
}
REQUIRE(false == pcs->HiddenLines());
}
SECTION("Hidden") {
pcs->InsertLines(0,1);
for (int l=0;l<2;l++) {
REQUIRE(true == pcs->GetVisible(0));
}
REQUIRE(false == pcs->HiddenLines());
pcs->SetVisible(1, 1, false);
REQUIRE(true == pcs->GetVisible(0));
REQUIRE(false == pcs->GetVisible(1));
REQUIRE(true == pcs->HiddenLines());
REQUIRE(1 == pcs->LinesDisplayed());
pcs->SetVisible(1, 1, true);
for (int l=0;l<2;l++) {
REQUIRE(true == pcs->GetVisible(0));
}
REQUIRE(false == pcs->HiddenLines());
}
SECTION("Hide All") {
pcs->InsertLines(0,1);
for (int l=0;l<2;l++) {
REQUIRE(true == pcs->GetVisible(0));
}
REQUIRE(false == pcs->HiddenLines());
pcs->SetVisible(0, 1, false);
REQUIRE(false == pcs->GetVisible(0));
REQUIRE(false == pcs->GetVisible(1));
REQUIRE(true == pcs->HiddenLines());
REQUIRE(0 == pcs->LinesDisplayed());
}
SECTION("Contracting") {
pcs->InsertLines(0,4);
for (int l=0;l<4;l++) {
REQUIRE(true == pcs->GetExpanded(l));
}
pcs->SetExpanded(2, false);
REQUIRE(true == pcs->GetExpanded(1));
REQUIRE(false == pcs->GetExpanded(2));
REQUIRE(true == pcs->GetExpanded(3));
REQUIRE(2 == pcs->ContractedNext(0));
REQUIRE(2 == pcs->ContractedNext(1));
REQUIRE(2 == pcs->ContractedNext(2));
REQUIRE(-1 == pcs->ContractedNext(3));
pcs->SetExpanded(2, true);
REQUIRE(true == pcs->GetExpanded(1));
REQUIRE(true == pcs->GetExpanded(2));
REQUIRE(true == pcs->GetExpanded(3));
}
SECTION("ExpandAll") {
pcs->InsertLines(0,4);
for (int l=0;l<4;l++) {
REQUIRE(true == pcs->GetExpanded(l));
}
pcs->SetExpanded(2, false);
REQUIRE(true == pcs->GetExpanded(1));
REQUIRE(false == pcs->GetExpanded(2));
REQUIRE(true == pcs->GetExpanded(3));
pcs->SetExpanded(1, false);
REQUIRE(false == pcs->GetExpanded(1));
REQUIRE(false == pcs->GetExpanded(2));
REQUIRE(true == pcs->GetExpanded(3));
REQUIRE(true == pcs->ExpandAll());
REQUIRE(true == pcs->GetExpanded(1));
REQUIRE(true == pcs->GetExpanded(2));
REQUIRE(true == pcs->GetExpanded(3));
}
SECTION("ChangeHeight") {
pcs->InsertLines(0,4);
for (int l=0;l<4;l++) {
REQUIRE(1 == pcs->GetHeight(l));
}
pcs->SetHeight(1, 2);
REQUIRE(1 == pcs->GetHeight(0));
REQUIRE(2 == pcs->GetHeight(1));
REQUIRE(1 == pcs->GetHeight(2));
}
SECTION("SetFoldDisplayText") {
pcs->InsertLines(0, 4);
REQUIRE(5 == pcs->LinesInDoc());
pcs->SetFoldDisplayText(1, "abc");
REQUIRE(strcmp(pcs->GetFoldDisplayText(1), "abc") == 0);
pcs->SetFoldDisplayText(1, "def");
REQUIRE(strcmp(pcs->GetFoldDisplayText(1), "def") == 0);
pcs->SetFoldDisplayText(1, nullptr);
REQUIRE(static_cast<const char *>(nullptr) == pcs->GetFoldDisplayText(1));
// At end
pcs->SetFoldDisplayText(5, "xyz");
REQUIRE(strcmp(pcs->GetFoldDisplayText(5), "xyz") == 0);
pcs->DeleteLines(4, 1);
REQUIRE(strcmp(pcs->GetFoldDisplayText(4), "xyz") == 0);
}
}

View File

@ -0,0 +1,98 @@
/** @file testDecoration.cxx
** Unit Tests for Scintilla internal data structures
**/
#include <cstddef>
#include <cstring>
#include <stdexcept>
#include <string_view>
#include <vector>
#include <optional>
#include <algorithm>
#include <memory>
#include "Debugging.h"
#include "Position.h"
#include "SplitVector.h"
#include "Partitioning.h"
#include "RunStyles.h"
#include "Decoration.h"
#include "catch.hpp"
constexpr int indicator=4;
using namespace Scintilla::Internal;
// Test Decoration.
TEST_CASE("Decoration") {
std::unique_ptr<IDecoration> deco = DecorationCreate(false, indicator);
SECTION("HasCorrectIndicator") {
REQUIRE(indicator == deco->Indicator());
}
SECTION("IsEmptyInitially") {
REQUIRE(0 == deco->Length());
REQUIRE(1 == deco->Runs());
REQUIRE(deco->Empty());
}
SECTION("SimpleSpace") {
deco->InsertSpace(0, 1);
REQUIRE(deco->Empty());
}
SECTION("SimpleRun") {
deco->InsertSpace(0, 1);
deco->SetValueAt(0, 2);
REQUIRE(!deco->Empty());
}
}
// Test DecorationList.
TEST_CASE("DecorationList") {
std::unique_ptr<IDecorationList> decol = DecorationListCreate(false);
SECTION("HasCorrectIndicator") {
decol->SetCurrentIndicator(indicator);
REQUIRE(indicator == decol->GetCurrentIndicator());
}
SECTION("HasCorrectCurrentValue") {
constexpr int value = 55;
decol->SetCurrentValue(value);
REQUIRE(value == decol->GetCurrentValue());
}
SECTION("ExpandSetValues") {
decol->SetCurrentIndicator(indicator);
decol->InsertSpace(0, 9);
constexpr int value = 59;
constexpr Sci::Position position = 4;
constexpr Sci::Position fillLength = 3;
auto fr = decol->FillRange(position, value, fillLength);
REQUIRE(fr.changed);
REQUIRE(fr.position == 4);
REQUIRE(fr.fillLength == 3);
REQUIRE(decol->ValueAt(indicator, 5) == value);
REQUIRE(decol->AllOnFor(5) == (1 << indicator));
REQUIRE(decol->Start(indicator, 5) == 4);
REQUIRE(decol->End(indicator, 5) == 7);
constexpr int indicatorB=6;
decol->SetCurrentIndicator(indicatorB);
fr = decol->FillRange(position, value, fillLength);
REQUIRE(fr.changed);
REQUIRE(decol->AllOnFor(5) == ((1 << indicator) | (1 << indicatorB)));
decol->DeleteRange(5, 1);
REQUIRE(decol->Start(indicatorB, 5) == 4);
REQUIRE(decol->End(indicatorB, 5) == 6);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,246 @@
/** @file testGeometry.cxx
** Unit Tests for Scintilla internal data structures
**/
#include <cstdint>
#include "Geometry.h"
#include "catch.hpp"
using namespace Scintilla;
using namespace Scintilla::Internal;
// Test Geometry.
TEST_CASE("Point") {
SECTION("Point") {
constexpr Point pt(1.0, 2.0);
REQUIRE(pt.x == 1.0);
REQUIRE(pt.y == 2.0);
constexpr Point pti = Point::FromInts(1, 2);
REQUIRE(pt == pti);
constexpr Point pt2 = pt + pt;
REQUIRE(pt != pt2);
constexpr Point ptBack = pt2 - pt;
REQUIRE(pt == ptBack);
}
}
TEST_CASE("Interval") {
SECTION("Interval") {
constexpr Interval interval { 1.0, 2.0 };
REQUIRE(interval.left == 1.0);
REQUIRE(interval.right == 2.0);
REQUIRE(interval.Width() == 1.0);
REQUIRE(!interval.Empty());
constexpr Interval empty { 4.0, 4.0 };
REQUIRE(empty.Empty());
REQUIRE(empty.Width() == 0.0);
REQUIRE(!(interval == empty));
REQUIRE(!(interval.Intersects(empty)));
constexpr Interval inside { 1.7, 1.8 };
REQUIRE(interval.Intersects(inside));
constexpr Interval straddles { 1.7, 2.8 };
REQUIRE(interval.Intersects(straddles));
}
SECTION("Interval") {
constexpr Interval interval1{ 1.0, 3.0 };
constexpr Interval interval2{ 2.0, 4.0 };
REQUIRE(Intersection(interval1, interval2) == Interval{ 2.0, 3.0 });
}
}
TEST_CASE("PRectangle") {
SECTION("PRectangle") {
constexpr PRectangle rc(1.0, 2.0, 3.0, 4.0);
REQUIRE(rc.left == 1.0);
REQUIRE(rc.top == 2.0);
REQUIRE(rc.right == 3.0);
REQUIRE(rc.bottom == 4.0);
REQUIRE(rc.Width() == 2.0);
REQUIRE(rc.Height() == 2.0);
REQUIRE(!rc.Empty());
constexpr PRectangle rci = PRectangle::FromInts(1, 2, 3, 4);
REQUIRE(rc == rci);
constexpr PRectangle rcEmpty;
REQUIRE(rcEmpty.Empty());
}
SECTION("Contains") {
constexpr PRectangle rc(1.0, 2.0, 3.0, 4.0);
constexpr Point pt(1.5, 2.5);
REQUIRE(rc.Contains(pt));
REQUIRE(rc.ContainsWholePixel(pt));
constexpr Point ptNearEdge(2.9, 2.5);
REQUIRE(rc.Contains(ptNearEdge));
REQUIRE(!rc.ContainsWholePixel(ptNearEdge));
constexpr Point ptOutside(1.5, 20.5);
REQUIRE(!rc.Contains(ptOutside));
REQUIRE(!rc.ContainsWholePixel(ptOutside));
constexpr PRectangle rcInside(1.5, 2.0, 2.5, 4.0);
REQUIRE(rc.Contains(rcInside));
REQUIRE(rc.Intersects(rcInside));
constexpr PRectangle rcIntersects(1.5, 2.0, 3.5, 4.0);
REQUIRE(!rc.Contains(rcIntersects));
REQUIRE(rc.Intersects(rcIntersects));
constexpr PRectangle rcSeparate(11.0, 12.0, 13.0, 14.0);
REQUIRE(!rc.Contains(rcSeparate));
REQUIRE(!rc.Intersects(rcSeparate));
constexpr Point ptCentre = rc.Centre();
REQUIRE(ptCentre == Point(2.0, 3.0));
}
SECTION("Move") {
PRectangle rc(1.0, 2.0, 3.0, 4.0);
rc.Move(1.0, 10.0);
REQUIRE(rc == PRectangle(2.0, 12.0, 4.0, 14.0));
}
SECTION("Inset") {
constexpr PRectangle rc(1.0, 2.0, 3.0, 4.0);
constexpr PRectangle rcInset = rc.Inset(0.5);
REQUIRE(rcInset == PRectangle(1.5, 2.5, 2.5, 3.5));
constexpr PRectangle rcInsetPt = rc.Inset(Point(0.25, 0.5));
REQUIRE(rcInsetPt == PRectangle(1.25, 2.5, 2.75, 3.5));
}
SECTION("Clamp") {
constexpr PRectangle rc(1.0, 2.0, 3.0, 4.0);
const PRectangle cutBottom = Clamp(rc, Edge::bottom, 3.5);
REQUIRE(cutBottom == PRectangle(1.0, 2.0, 3.0, 3.5));
const PRectangle justBottom = Side(rc, Edge::bottom, 0.5);
REQUIRE(justBottom == PRectangle(1.0, 3.5, 3.0, 4.0));
constexpr PRectangle rcInset = rc.Inset(0.5);
REQUIRE(rcInset == PRectangle(1.5, 2.5, 2.5, 3.5));
constexpr PRectangle rcInsetPt = rc.Inset(Point(0.25, 0.5));
REQUIRE(rcInsetPt == PRectangle(1.25, 2.5, 2.75, 3.5));
const Interval bounds = HorizontalBounds(rcInsetPt);
REQUIRE(bounds == Interval{ 1.25, 2.75 });
const PRectangle applyBounds = Intersection(rc, bounds);
REQUIRE(applyBounds == PRectangle(1.25, 2.0, 2.75, 4.0));
}
SECTION("PixelAlign") {
// Whole pixels
REQUIRE(PixelAlign(1.0, 1) == 1.0);
REQUIRE(PixelAlignFloor(1.0, 1) == 1.0);
REQUIRE(PixelAlignCeil(1.0, 1) == 1.0);
REQUIRE(PixelAlign(1.25, 1) == 1.0);
REQUIRE(PixelAlignFloor(1.25, 1) == 1.0);
REQUIRE(PixelAlignCeil(1.25, 1) == 2.0);
REQUIRE(PixelAlign(1.5, 1) == 2.0);
REQUIRE(PixelAlignFloor(1.5, 1) == 1.0);
REQUIRE(PixelAlignCeil(1.5, 1) == 2.0);
REQUIRE(PixelAlign(1.75, 1) == 2.0);
REQUIRE(PixelAlignFloor(1.75, 1) == 1.0);
REQUIRE(PixelAlignCeil(1.75, 1) == 2.0);
REQUIRE(PixelAlign(Point(1.75, 1.25), 1) == Point(2.0, 1.0));
REQUIRE(PixelAlign(Point(1.5, 1.0), 1) == Point(2.0, 1.0));
// Half pixels
REQUIRE(PixelAlign(1.0, 2) == 1.0);
REQUIRE(PixelAlignFloor(1.0, 2) == 1.0);
REQUIRE(PixelAlignCeil(1.0, 2) == 1.0);
REQUIRE(PixelAlign(1.25, 2) == 1.5);
REQUIRE(PixelAlignFloor(1.25, 2) == 1.0);
REQUIRE(PixelAlignCeil(1.25, 2) == 1.5);
REQUIRE(PixelAlign(1.5, 2) == 1.5);
REQUIRE(PixelAlignFloor(1.5, 2) == 1.5);
REQUIRE(PixelAlignCeil(1.5, 2) == 1.5);
REQUIRE(PixelAlign(1.75, 2) == 2.0);
REQUIRE(PixelAlignFloor(1.75, 2) == 1.5);
REQUIRE(PixelAlignCeil(1.75, 2) == 2.0);
REQUIRE(PixelAlign(Point(1.75, 1.25), 2) == Point(2.0, 1.5));
REQUIRE(PixelAlign(Point(1.5, 1.0), 2) == Point(1.5, 1.0));
// x->round, y->floored
REQUIRE(PixelAlign(PRectangle(1.0, 1.25, 1.5, 1.75), 1) == PRectangle(1.0, 1.0, 2.0, 1.0));
REQUIRE(PixelAlign(PRectangle(1.0, 1.25, 1.5, 1.75), 2) == PRectangle(1.0, 1.0, 1.5, 1.5));
// x->outside(floor left, ceil right), y->floored
REQUIRE(PixelAlignOutside(PRectangle(1.1, 1.25, 1.6, 1.75), 1) == PRectangle(1.0, 1.0, 2.0, 1.0));
REQUIRE(PixelAlignOutside(PRectangle(1.1, 1.25, 1.6, 1.75), 2) == PRectangle(1.0, 1.0, 2.0, 1.5));
}
}
TEST_CASE("ColourRGBA") {
SECTION("ColourRGBA") {
constexpr ColourRGBA colour(0x10203040);
constexpr ColourRGBA colourFromRGB(0x40, 0x30, 0x20, 0x10);
REQUIRE(colour == colourFromRGB);
REQUIRE(colour.GetRed() == 0x40);
REQUIRE(colour.GetGreen() == 0x30);
REQUIRE(colour.GetBlue() == 0x20);
REQUIRE(colour.GetAlpha() == 0x10);
REQUIRE(!colour.IsOpaque());
REQUIRE(colour.AsInteger() == 0x10203040);
REQUIRE(ColourRGBA(colour, 0x80) == ColourRGBA(0x40, 0x30, 0x20, 0x80));
REQUIRE(ColourRGBA::FromRGB(0x203040) == ColourRGBA(0x40, 0x30, 0x20, 0xff));
REQUIRE(ColourRGBA::FromIpRGB(0x203040) == ColourRGBA(0x40, 0x30, 0x20, 0xff));
constexpr ColourRGBA colour01(0x00ff00ff);
REQUIRE(colour01.GetRedComponent() == 1.0);
REQUIRE(colour01.GetGreenComponent() == 0.0);
REQUIRE(colour01.GetBlueComponent() == 1.0);
REQUIRE(colour01.GetAlphaComponent() == 0.0);
// Opaque colours
constexpr ColourRGBA colourRGB(0xff203040);
REQUIRE(colourRGB.IsOpaque());
constexpr ColourRGBA colourOpaque(0x40, 0x30, 0x20, 0xff);
REQUIRE(colourRGB == colourOpaque);
REQUIRE(colourRGB.OpaqueRGB() == 0x203040);
REQUIRE(colourRGB.WithoutAlpha() == ColourRGBA(0x40, 0x30, 0x20, 0));
}
SECTION("Mixing") {
constexpr ColourRGBA colourMin(0x10, 0x20, 0x30, 0x40);
constexpr ColourRGBA colourMax(0x30, 0x60, 0x90, 0xC0);
constexpr ColourRGBA colourMid(0x20, 0x40, 0x60, 0x80);
REQUIRE(colourMin.MixedWith(colourMax) == colourMid);
REQUIRE(colourMin.MixedWith(colourMax, 0.5) == colourMid);
// 3/4 between min and max
constexpr ColourRGBA colour75(0x28, 0x50, 0x78, 0xA0);
REQUIRE(colourMin.MixedWith(colourMax, 0.75) == colour75);
}
}

View File

@ -0,0 +1,224 @@
/** @file testPartitioning.cxx
** Unit Tests for Scintilla internal data structures
**/
#include <cstddef>
#include <cstring>
#include <stdexcept>
#include <string_view>
#include <vector>
#include <optional>
#include <algorithm>
#include <memory>
#include "Debugging.h"
#include "Position.h"
#include "SplitVector.h"
#include "Partitioning.h"
#include "catch.hpp"
using namespace Scintilla::Internal;
// Test Partitioning.
TEST_CASE("CompileCopying Partitioning") {
// These are compile-time tests to check that basic copy and move
// operations are defined correctly.
SECTION("CopyingMoving") {
Partitioning<int> s;
Partitioning<int> s2;
// Copy constructor
const Partitioning<int> sa(s);
// Copy assignment
Partitioning<int> sb;
sb = s;
// Move constructor
const Partitioning<int> sc(std::move(s));
// Move assignment
Partitioning<int> sd;
sd = (std::move(s2));
}
}
TEST_CASE("Partitioning") {
Partitioning<Sci::Position> part;
SECTION("IsEmptyInitially") {
REQUIRE(1 == part.Partitions());
REQUIRE(0 == part.PositionFromPartition(part.Partitions()));
REQUIRE(0 == part.PartitionFromPosition(0));
}
SECTION("SimpleInsert") {
part.InsertText(0, 1);
REQUIRE(1 == part.Partitions());
REQUIRE(1 == part.PositionFromPartition(part.Partitions()));
}
SECTION("TwoPartitions") {
part.InsertText(0, 2);
part.InsertPartition(1, 1);
REQUIRE(2 == part.Partitions());
REQUIRE(0 == part.PositionFromPartition(0));
REQUIRE(1 == part.PositionFromPartition(1));
REQUIRE(2 == part.PositionFromPartition(2));
}
SECTION("MoveStart") {
part.InsertText(0, 3);
part.InsertPartition(1, 2);
part.SetPartitionStartPosition(1,1);
REQUIRE(2 == part.Partitions());
REQUIRE(0 == part.PositionFromPartition(0));
REQUIRE(1 == part.PositionFromPartition(1));
REQUIRE(3 == part.PositionFromPartition(2));
part.Check();
}
SECTION("InsertAgain") {
part.InsertText(0, 3);
part.InsertPartition(1, 2);
part.InsertText(0,3);
part.InsertText(1,2);
REQUIRE(2 == part.Partitions());
REQUIRE(0 == part.PositionFromPartition(0));
REQUIRE(5 == part.PositionFromPartition(1));
REQUIRE(8 == part.PositionFromPartition(2));
part.Check();
}
SECTION("InsertMultiple") {
part.InsertText(0, 10);
const Sci::Position positions[] { 2, 5, 7 };
part.InsertPartitions(1, positions, std::size(positions));
REQUIRE(4 == part.Partitions());
REQUIRE(0 == part.PositionFromPartition(0));
REQUIRE(2 == part.PositionFromPartition(1));
REQUIRE(5 == part.PositionFromPartition(2));
REQUIRE(7 == part.PositionFromPartition(3));
REQUIRE(10 == part.PositionFromPartition(4));
part.Check();
}
SECTION("InsertMultipleWithCast") {
part.InsertText(0, 9);
REQUIRE(1 == part.Partitions());
const ptrdiff_t positionsp[]{ 2, 4, 6, 8 };
part.InsertPartitionsWithCast(1, positionsp, std::size(positionsp));
REQUIRE(5 == part.Partitions());
REQUIRE(0 == part.PositionFromPartition(0));
REQUIRE(2 == part.PositionFromPartition(1));
REQUIRE(4 == part.PositionFromPartition(2));
REQUIRE(6 == part.PositionFromPartition(3));
REQUIRE(8 == part.PositionFromPartition(4));
REQUIRE(9 == part.PositionFromPartition(5));
part.Check();
}
SECTION("InsertReversed") {
part.InsertText(0, 3);
part.InsertPartition(1, 2);
part.InsertText(1,2);
part.InsertText(0,3);
REQUIRE(2 == part.Partitions());
REQUIRE(0 == part.PositionFromPartition(0));
REQUIRE(5 == part.PositionFromPartition(1));
REQUIRE(8 == part.PositionFromPartition(2));
part.Check();
}
SECTION("InverseSearch") {
part.InsertText(0, 3);
part.InsertPartition(1, 2);
part.SetPartitionStartPosition(1,1);
REQUIRE(2 == part.Partitions());
REQUIRE(0 == part.PositionFromPartition(0));
REQUIRE(1 == part.PositionFromPartition(1));
REQUIRE(3 == part.PositionFromPartition(2));
REQUIRE(0 == part.PartitionFromPosition(0));
REQUIRE(1 == part.PartitionFromPosition(1));
REQUIRE(1 == part.PartitionFromPosition(2));
REQUIRE(1 == part.PartitionFromPosition(3));
part.Check();
}
SECTION("DeletePartition") {
part.InsertText(0, 2);
part.InsertPartition(1, 1);
part.RemovePartition(1);
REQUIRE(1 == part.Partitions());
REQUIRE(0 == part.PositionFromPartition(0));
REQUIRE(2 == part.PositionFromPartition(1));
part.Check();
}
SECTION("DeleteAll") {
part.InsertText(0, 3);
part.InsertPartition(1, 2);
part.SetPartitionStartPosition(1,1);
part.DeleteAll();
// Back to initial state
REQUIRE(1 == part.Partitions());
REQUIRE(0 == part.PositionFromPartition(part.Partitions()));
}
SECTION("TestBackwards") {
part.InsertText(0, 10);
part.InsertPartition(1, 3);
part.InsertPartition(2, 6);
part.InsertPartition(3, 9);
part.InsertText(2,4);
part.InsertText(1,2);
part.InsertText(0,3);
REQUIRE(4 == part.Partitions());
REQUIRE(0 == part.PositionFromPartition(0));
REQUIRE(6 == part.PositionFromPartition(1));
REQUIRE(11 == part.PositionFromPartition(2));
REQUIRE(18 == part.PositionFromPartition(3));
REQUIRE(19 == part.PositionFromPartition(4));
part.Check();
}
SECTION("TestMany") {
// Provoke backstep call
part.InsertText(0, 42);
for (int i=0; i<20; i++) {
part.InsertPartition(i+1, (i+1) * 2);
}
for (int i=20; i>0; i--) {
part.InsertText(i,2);
}
REQUIRE(21 == part.Partitions());
for (int i=1; i<20; i++) {
REQUIRE((i*4 - 2) == part.PositionFromPartition(i));
REQUIRE(i == part.PartitionFromPosition(i*4 - 2));
}
part.InsertText(19,2);
REQUIRE(3 == part.PartitionFromPosition(10));
part.InsertText(0,2);
part.InsertText(0,-2);
part.RemovePartition(1);
REQUIRE(0 == part.PositionFromPartition(0));
REQUIRE(6 == part.PositionFromPartition(1));
REQUIRE(10 == part.PositionFromPartition(2));
part.RemovePartition(10);
REQUIRE(46 == part.PositionFromPartition(10));
REQUIRE(10 == part.PartitionFromPosition(46));
REQUIRE(50 == part.PositionFromPartition(11));
REQUIRE(11 == part.PartitionFromPosition(50));
part.Check();
}
}

View File

@ -0,0 +1,393 @@
/** @file testPerLine.cxx
** Unit Tests for Scintilla internal data structures
**/
#include <cstddef>
#include <cstring>
#include <stdexcept>
#include <string_view>
#include <vector>
#include <forward_list>
#include <optional>
#include <algorithm>
#include <memory>
#include "ScintillaTypes.h"
#include "Debugging.h"
#include "Position.h"
#include "SplitVector.h"
#include "Partitioning.h"
#include "RunStyles.h"
#include "CellBuffer.h"
#include "PerLine.h"
#include "catch.hpp"
using namespace Scintilla::Internal;
constexpr int FoldBase = static_cast<int>(Scintilla::FoldLevel::Base);
// Test MarkerHandleSet.
TEST_CASE("CompileCopying MarkerHandleSet") {
// These are compile-time tests to check that basic copy and move
// operations are defined correctly.
SECTION("CopyingMoving") {
MarkerHandleSet s;
MarkerHandleSet s2;
// Copy constructor
const MarkerHandleSet sa(s);
// Copy assignment
MarkerHandleSet sb;
sb = s;
// Move constructor
const MarkerHandleSet sc(std::move(s));
// Move assignment
MarkerHandleSet sd;
sd = (std::move(s2));
}
}
TEST_CASE("MarkerHandleSet") {
MarkerHandleSet mhs;
SECTION("Initial") {
// Initial State
REQUIRE(mhs.Empty());
REQUIRE(0 == mhs.MarkValue());
REQUIRE(!mhs.Contains(1));
}
SECTION("InsertDelete") {
// Test knows that InsertHandle inserts at front (0)
// Insert 1 with handle 100
REQUIRE(mhs.InsertHandle(100,1));
REQUIRE(!mhs.Empty());
REQUIRE(2 == mhs.MarkValue());
REQUIRE(mhs.Contains(100));
// Insert 2 with handle 200
REQUIRE(mhs.InsertHandle(200,2));
REQUIRE(!mhs.Empty());
REQUIRE(mhs.Contains(100));
REQUIRE(mhs.Contains(200));
REQUIRE(6 == mhs.MarkValue());
const MarkerHandleNumber *mhn0 = mhs.GetMarkerHandleNumber(0);
REQUIRE(200 == mhn0->handle);
REQUIRE(2 == mhn0->number);
const MarkerHandleNumber *mhn1 = mhs.GetMarkerHandleNumber(1);
REQUIRE(100 == mhn1->handle);
REQUIRE(1 == mhn1->number);
const MarkerHandleNumber *mhn2 = mhs.GetMarkerHandleNumber(2);
REQUIRE(nullptr == mhn2);
// Remove first insertion
mhs.RemoveHandle(100);
REQUIRE(!mhs.Empty());
REQUIRE(mhs.Contains(200));
REQUIRE(4 == mhs.MarkValue());
// Remove remaining element
REQUIRE(mhs.RemoveNumber(2, true));
REQUIRE(mhs.Empty());
REQUIRE(!mhs.Contains(200));
REQUIRE(0 == mhs.MarkValue());
}
SECTION("Combine") {
mhs.InsertHandle(100, 1);
MarkerHandleSet mhsOther;
mhsOther.InsertHandle(200, 2);
mhs.CombineWith(&mhsOther);
REQUIRE(mhsOther.Empty());
mhs.RemoveHandle(100);
mhs.RemoveHandle(200);
REQUIRE(mhs.Empty());
}
}
TEST_CASE("LineMarkers") {
LineMarkers lm;
SECTION("Initial") {
// Initial State
REQUIRE(0 == lm.MarkValue(0));
}
SECTION("AddDelete") {
// Test knows the way handles are allocated, starting from 1
lm.InsertLines(0, 5);
const int handle1 = lm.AddMark(0, 1, 5);
REQUIRE(1 == handle1);
REQUIRE(2 == lm.MarkValue(0));
REQUIRE(1 == lm.HandleFromLine(0, 0));
REQUIRE(1 == lm.NumberFromLine(0, 0));
REQUIRE(-1 == lm.HandleFromLine(0, 1));
REQUIRE(-1 == lm.NumberFromLine(0, 1));
REQUIRE(0 == lm.LineFromHandle(handle1));
REQUIRE(lm.DeleteMark(0, 1, true));
REQUIRE(0 == lm.MarkValue(0));
const int handle2 = lm.AddMark(0, 2, 5);
REQUIRE(2 == handle2);
REQUIRE(4 == lm.MarkValue(0));
lm.DeleteMarkFromHandle(handle2);
REQUIRE(0 == lm.MarkValue(0));
}
SECTION("MarkerNext") {
lm.AddMark(1, 1, 5);
lm.AddMark(2, 2, 5);
const Sci::Line line1 = lm.MarkerNext(0, 6);
REQUIRE(1 == line1);
const Sci::Line line2 = lm.MarkerNext(line1+1, 6);
REQUIRE(2 == line2);
const Sci::Line line3 = lm.MarkerNext(line2+1, 6);
REQUIRE(-1 == line3);
}
SECTION("MergeMarkers") {
lm.AddMark(1, 1, 5);
lm.AddMark(2, 2, 5);
lm.MergeMarkers(1);
REQUIRE(6 == lm.MarkValue(1));
REQUIRE(0 == lm.MarkValue(2));
}
SECTION("InsertRemoveLine") {
const int handle1 = lm.AddMark(1, 1, 5);
const int handle2 = lm.AddMark(2, 2, 5);
lm.InsertLine(2);
REQUIRE(0 == lm.MarkValue(0));
REQUIRE(2 == lm.MarkValue(1));
REQUIRE(0 == lm.MarkValue(2));
REQUIRE(4 == lm.MarkValue(3));
REQUIRE(0 == lm.MarkValue(4));
lm.RemoveLine(2);
REQUIRE(0 == lm.MarkValue(0));
REQUIRE(2 == lm.MarkValue(1));
REQUIRE(4 == lm.MarkValue(2));
REQUIRE(0 == lm.MarkValue(3));
lm.InsertLines(2, 2);
REQUIRE(0 == lm.MarkValue(0));
REQUIRE(2 == lm.MarkValue(1));
REQUIRE(0 == lm.MarkValue(2));
REQUIRE(0 == lm.MarkValue(3));
REQUIRE(4 == lm.MarkValue(4));
REQUIRE(0 == lm.MarkValue(5));
REQUIRE(1 == lm.LineFromHandle(handle1));
REQUIRE(4 == lm.LineFromHandle(handle2));
}
}
TEST_CASE("LineLevels") {
LineLevels ll;
SECTION("Initial") {
// Initial State
REQUIRE(FoldBase == ll.GetLevel(0));
}
SECTION("SetLevel") {
REQUIRE(FoldBase == ll.SetLevel(1, 200, 5));
REQUIRE(FoldBase == ll.GetLevel(0));
REQUIRE(200 == ll.GetLevel(1));
REQUIRE(FoldBase == ll.GetLevel(2));
ll.ClearLevels();
REQUIRE(FoldBase == ll.GetLevel(1));
ll.ExpandLevels(5);
REQUIRE(FoldBase == ll.GetLevel(7));
}
SECTION("InsertRemoveLine") {
ll.SetLevel(1, 1, 5);
ll.SetLevel(2, 2, 5);
ll.InsertLine(2);
REQUIRE(FoldBase == ll.GetLevel(0));
REQUIRE(1 == ll.GetLevel(1));
REQUIRE(2 == ll.GetLevel(2));
REQUIRE(2 == ll.GetLevel(3));
REQUIRE(FoldBase == ll.GetLevel(4));
ll.RemoveLine(2);
REQUIRE(FoldBase == ll.GetLevel(0));
REQUIRE(1 == ll.GetLevel(1));
REQUIRE(2 == ll.GetLevel(2));
REQUIRE(FoldBase == ll.GetLevel(3));
ll.InsertLines(2, 2);
REQUIRE(FoldBase == ll.GetLevel(0));
REQUIRE(1 == ll.GetLevel(1));
REQUIRE(2 == ll.GetLevel(2));
REQUIRE(2 == ll.GetLevel(3));
REQUIRE(2 == ll.GetLevel(4));
REQUIRE(FoldBase == ll.GetLevel(5));
}
}
TEST_CASE("LineState") {
LineState ls;
SECTION("Initial") {
// Initial State
REQUIRE(0 == ls.GetMaxLineState());
REQUIRE(0 == ls.GetLineState(0));
REQUIRE(1 == ls.GetMaxLineState());
ls.Init();
REQUIRE(0 == ls.GetMaxLineState());
REQUIRE(0 == ls.GetLineState(0));
}
SECTION("SetLineState") {
REQUIRE(0 == ls.SetLineState(1, 200, 2));
REQUIRE(0 == ls.GetLineState(0));
REQUIRE(200 == ls.GetLineState(1));
REQUIRE(0 == ls.GetLineState(2));
REQUIRE(0 == ls.SetLineState(2, 400, 3));
REQUIRE(0 == ls.GetLineState(0));
REQUIRE(200 == ls.GetLineState(1));
REQUIRE(400 == ls.GetLineState(2));
REQUIRE(0 == ls.GetLineState(3));
// GetLineState(3) expands to 4 lines
REQUIRE(4 == ls.GetMaxLineState());
ls.Init();
REQUIRE(0 == ls.GetLineState(0));
REQUIRE(1 == ls.GetMaxLineState());
}
SECTION("InsertRemoveLine") {
REQUIRE(0 == ls.GetMaxLineState());
ls.SetLineState(1, 1, 3);
ls.SetLineState(2, 2, 3);
REQUIRE(4 == ls.GetMaxLineState());
ls.InsertLine(2);
REQUIRE(5 == ls.GetMaxLineState());
REQUIRE(0 == ls.GetLineState(0));
REQUIRE(1 == ls.GetLineState(1));
REQUIRE(2 == ls.GetLineState(2));
REQUIRE(2 == ls.GetLineState(3));
REQUIRE(0 == ls.GetLineState(4));
REQUIRE(5 == ls.GetMaxLineState());
ls.RemoveLine(2);
REQUIRE(4 == ls.GetMaxLineState());
REQUIRE(0 == ls.GetLineState(0));
REQUIRE(1 == ls.GetLineState(1));
REQUIRE(2 == ls.GetLineState(2));
REQUIRE(0 == ls.GetLineState(3));
ls.InsertLines(2, 2);
REQUIRE(6 == ls.GetMaxLineState());
REQUIRE(0 == ls.GetLineState(0));
REQUIRE(1 == ls.GetLineState(1));
REQUIRE(2 == ls.GetLineState(2));
REQUIRE(2 == ls.GetLineState(3));
REQUIRE(2 == ls.GetLineState(4));
REQUIRE(0 == ls.GetLineState(5));
}
}
TEST_CASE("LineAnnotation") {
LineAnnotation la;
SECTION("Initial") {
// Initial State
REQUIRE(0 == la.Length(0));
REQUIRE(0 == la.Lines(0));
REQUIRE(0 == la.Style(0));
REQUIRE(false == la.MultipleStyles(0));
}
SECTION("SetText") {
la.SetText(0, "Text");
REQUIRE(4 == la.Length(0));
REQUIRE(1 == la.Lines(0));
REQUIRE(memcmp(la.Text(0), "Text", 4) == 0);
REQUIRE(nullptr == la.Styles(0));
REQUIRE(0 == la.Style(0));
la.SetStyle(0, 9);
REQUIRE(9 == la.Style(0));
la.SetText(0, "Ant\nBird\nCat");
REQUIRE(3 == la.Lines(0));
la.ClearAll();
REQUIRE(nullptr == la.Text(0));
}
SECTION("SetStyles") {
la.SetText(0, "Text");
const unsigned char styles[] { 1,2,3,4 };
la.SetStyles(0, styles);
REQUIRE(memcmp(la.Text(0), "Text", 4) == 0);
REQUIRE(memcmp(la.Styles(0), styles, 4) == 0);
REQUIRE(la.MultipleStyles(0));
}
SECTION("InsertRemoveLine") {
la.SetText(0, "Ant");
la.SetText(1, "Bird");
REQUIRE(3 == la.Length(0));
REQUIRE(4 == la.Length(1));
REQUIRE(1 == la.Lines(0));
la.InsertLine(1);
REQUIRE(3 == la.Length(0));
REQUIRE(0 == la.Length(1));
REQUIRE(4 == la.Length(2));
la.RemoveLine(2);
REQUIRE(3 == la.Length(0));
REQUIRE(4 == la.Length(1));
la.InsertLines(1, 2);
REQUIRE(3 == la.Length(0));
REQUIRE(0 == la.Length(1));
REQUIRE(0 == la.Length(2));
REQUIRE(4 == la.Length(3));
}
}
TEST_CASE("LineTabstops") {
LineTabstops lt;
SECTION("Initial") {
// Initial State
REQUIRE(0 == lt.GetNextTabstop(0, 0));
}
SECTION("AddClearTabstops") {
lt.AddTabstop(0, 100);
REQUIRE(100 == lt.GetNextTabstop(0, 0));
REQUIRE(0 == lt.GetNextTabstop(0, 100));
lt.ClearTabstops(0);
REQUIRE(0 == lt.GetNextTabstop(0, 0));
}
SECTION("InsertRemoveLine") {
lt.AddTabstop(0, 100);
lt.AddTabstop(1, 200);
lt.InsertLine(1);
REQUIRE(100 == lt.GetNextTabstop(0, 0));
REQUIRE(0 == lt.GetNextTabstop(1, 0));
REQUIRE(200 == lt.GetNextTabstop(2, 0));
lt.RemoveLine(1);
REQUIRE(100 == lt.GetNextTabstop(0, 0));
REQUIRE(200 == lt.GetNextTabstop(1, 0));
lt.InsertLines(1, 2);
REQUIRE(100 == lt.GetNextTabstop(0, 0));
REQUIRE(0 == lt.GetNextTabstop(1, 0));
REQUIRE(0 == lt.GetNextTabstop(2, 0));
REQUIRE(200 == lt.GetNextTabstop(3, 0));
lt.Init();
REQUIRE(0 == lt.GetNextTabstop(0, 0));
}
}

View File

@ -0,0 +1,97 @@
/** @file testRESearch.cxx
** Unit Tests for Scintilla internal data structures
**/
#include <cstddef>
#include <cstring>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
#include <array>
#include <optional>
#include <algorithm>
#include <memory>
#include "ScintillaTypes.h"
#include "Debugging.h"
#include "Position.h"
#include "SplitVector.h"
#include "Partitioning.h"
#include "RunStyles.h"
#include "CellBuffer.h"
#include "CharClassify.h"
#include "RESearch.h"
#include "catch.hpp"
using namespace Scintilla;
using namespace Scintilla::Internal;
class StringCI : public CharacterIndexer {
std::string s;
public:
explicit StringCI(std::string_view sv_) : s(sv_) {
}
virtual ~StringCI() = default;
[[nodiscard]] Sci::Position Length() const noexcept {
return s.length();
}
char CharAt(Sci::Position index) const override {
return s.at(index);
}
Sci::Position MovePositionOutsideChar(Sci::Position pos, [[maybe_unused]] Sci::Position moveDir) const noexcept override {
return pos;
}
[[nodiscard]] std::string GetCharRange(Sci::Position position, Sci::Position lengthRetrieve) const {
return s.substr(position, lengthRetrieve);
}
};
// Test RESearch.
TEST_CASE("RESearch") {
CharClassify cc;
constexpr std::string_view sTextSpace = "Scintilla ";
constexpr std::string_view pattern = "[a-z]+";
SECTION("Compile") {
RESearch re(&cc);
const char *msg = re.Compile(pattern.data(), pattern.length(), true, false);
REQUIRE(nullptr == msg);
}
SECTION("Bug2413") {
// Check for https://sourceforge.net/p/scintilla/bugs/2413/
RESearch re(&cc);
constexpr std::string_view BOW = "\\<";
constexpr std::string_view EOW = "\\>";
const char *msg = re.Compile(BOW.data(), BOW.length(), true, false);
REQUIRE(nullptr == msg);
msg = re.Compile(EOW.data(), EOW.length(), true, false);
REQUIRE(nullptr == msg);
}
SECTION("Execute") {
RESearch re(&cc);
re.Compile(pattern.data(), pattern.length(), true, false);
const StringCI sci(sTextSpace);
const int x = re.Execute(sci, 0, sci.Length());
REQUIRE(x == 1);
REQUIRE(re.bopat[0] == 1);
REQUIRE(re.eopat[0] == sci.Length() - 1);
}
SECTION("Grab") {
RESearch re(&cc);
re.Compile(pattern.data(), pattern.length(), true, false);
const StringCI sci(sTextSpace);
re.Execute(sci, 0, sci.Length());
const std::string pat = sci.GetCharRange(re.bopat[0], re.eopat[0] - re.bopat[0]);
REQUIRE(pat == "cintilla");
}
}

View File

@ -0,0 +1,381 @@
/** @file testRunStyles.cxx
** Unit Tests for Scintilla internal data structures
**/
#include <cstddef>
#include <cstring>
#include <stdexcept>
#include <string_view>
#include <vector>
#include <optional>
#include <algorithm>
#include <memory>
#include "Debugging.h"
#include "Position.h"
#include "SplitVector.h"
#include "Partitioning.h"
#include "RunStyles.h"
#include "catch.hpp"
using namespace Scintilla::Internal;
// Test RunStyles.
using UniqueInt = std::unique_ptr<int>;
TEST_CASE("CompileCopying RunStyles") {
// These are compile-time tests to check that basic copy and move
// operations are defined correctly.
SECTION("CopyingMoving") {
RunStyles<int, int> s;
RunStyles<int, int> s2;
// Copy constructor
const RunStyles<int, int> sa(s);
// Copy assignment fails
RunStyles<int, int> sb;
sb = s;
// Move constructor
const RunStyles<int, int> sc(std::move(s));
// Move assignment
RunStyles<int, int> sd;
sd = (std::move(s2));
}
#if defined(SHOW_COPY_BUILD_FAILURES)
// It should be reasonable to instantiate RunStyles where STYLE is move-only but fails
SECTION("MoveOnly") {
RunStyles<int, UniqueInt> s;
// Copy is not defined for std::unique_ptr
// Copy constructor fails
RunStyles<int, UniqueInt> sa(s);
// Copy assignment fails
RunStyles<int, UniqueInt> sb;
sb = s;
// Move constructor fails
RunStyles<int, UniqueInt> sc(std::move(s));
// Move assignment fails
RunStyles<int, UniqueInt> sd;
sd = (std::move(s));
}
#endif
}
namespace Scintilla::Internal { // Xcode clang 9.0 doesn't like this when in the unnamed namespace
bool operator==(const FillResult<int> &fra, const FillResult<int> &frb) noexcept {
return fra.changed == frb.changed &&
fra.position == frb.position &&
fra.fillLength == frb.fillLength;
}
}
TEST_CASE("RunStyles") {
RunStyles<int, int> rs;
SECTION("IsEmptyInitially") {
REQUIRE(0 == rs.Length());
REQUIRE(1 == rs.Runs());
}
SECTION("SimpleInsert") {
rs.InsertSpace(0, 1);
REQUIRE(1 == rs.Length());
REQUIRE(1 == rs.Runs());
REQUIRE(0 == rs.ValueAt(0));
REQUIRE(1 == rs.FindNextChange(0, rs.Length()));
REQUIRE(2 == rs.FindNextChange(1, rs.Length()));
}
SECTION("TwoRuns") {
rs.InsertSpace(0, 2);
REQUIRE(2 == rs.Length());
REQUIRE(1 == rs.Runs());
rs.SetValueAt(0, 2);
REQUIRE(2 == rs.Runs());
REQUIRE(2 == rs.ValueAt(0));
REQUIRE(0 == rs.ValueAt(1));
REQUIRE(1 == rs.FindNextChange(0, rs.Length()));
REQUIRE(2 == rs.FindNextChange(1, rs.Length()));
REQUIRE(3 == rs.FindNextChange(2, rs.Length()));
}
SECTION("LongerRuns") {
rs.InsertSpace(0, 5);
rs.SetValueAt(0, 3);
rs.SetValueAt(1, 3);
REQUIRE(3 == rs.ValueAt(0));
REQUIRE(3 == rs.ValueAt(1));
REQUIRE(0 == rs.ValueAt(2));
REQUIRE(2 == rs.Runs());
REQUIRE(0 == rs.StartRun(0));
REQUIRE(2 == rs.EndRun(0));
REQUIRE(0 == rs.StartRun(1));
REQUIRE(2 == rs.EndRun(1));
REQUIRE(2 == rs.StartRun(2));
REQUIRE(5 == rs.EndRun(2));
REQUIRE(2 == rs.StartRun(3));
REQUIRE(5 == rs.EndRun(3));
REQUIRE(2 == rs.StartRun(4));
REQUIRE(5 == rs.EndRun(4));
// At end
REQUIRE(2 == rs.StartRun(5));
REQUIRE(5 == rs.EndRun(5));
// After end is same as end
REQUIRE(2 == rs.StartRun(6));
REQUIRE(5 == rs.EndRun(6));
REQUIRE(2 == rs.FindNextChange(0, rs.Length()));
REQUIRE(5 == rs.FindNextChange(2, rs.Length()));
REQUIRE(6 == rs.FindNextChange(5, rs.Length()));
}
SECTION("FillRange") {
rs.InsertSpace(0, 5);
const int startFill = 1;
const int lengthFill = 3;
const auto fr = rs.FillRange(startFill, 99, lengthFill);
REQUIRE(FillResult<int>{true, 1, 3} == fr);
REQUIRE(0 == rs.ValueAt(0));
REQUIRE(99 == rs.ValueAt(1));
REQUIRE(99 == rs.ValueAt(2));
REQUIRE(99 == rs.ValueAt(3));
REQUIRE(0 == rs.ValueAt(4));
REQUIRE(0 == rs.StartRun(0));
REQUIRE(1 == rs.EndRun(0));
REQUIRE(1 == rs.StartRun(1));
REQUIRE(4 == rs.EndRun(1));
}
SECTION("FillRangeAlreadyFilled") {
rs.InsertSpace(0, 5);
const int startFill = 1;
const int lengthFill = 3;
const auto fr = rs.FillRange(startFill, 99, lengthFill);
REQUIRE(FillResult<int>{true, 1, 3} == fr);
const int startFill2 = 2;
const int lengthFill2 = 1;
// Compiler warnings if 'false' used instead of '0' as expected value:
const auto fr2 = rs.FillRange(startFill2, 99, lengthFill2);
REQUIRE(FillResult<int>{false, 2, 1} == fr2);
REQUIRE(0 == rs.ValueAt(0));
REQUIRE(99 == rs.ValueAt(1));
REQUIRE(99 == rs.ValueAt(2));
REQUIRE(99 == rs.ValueAt(3));
REQUIRE(0 == rs.ValueAt(4));
REQUIRE(3 == rs.Runs());
}
SECTION("FillRangeAlreadyPartFilled") {
rs.InsertSpace(0, 5);
const int startFill = 1;
const int lengthFill = 2;
const auto fr = rs.FillRange(startFill, 99, lengthFill);
REQUIRE(FillResult<int>{true, 1, 2} == fr);
const int startFill2 = 2;
const int lengthFill2 = 2;
const auto fr2 = rs.FillRange(startFill2, 99, lengthFill2);
REQUIRE(FillResult<int>{true, 3, 1} == fr2);
REQUIRE(3 == rs.Runs());
}
SECTION("DeleteRange") {
rs.InsertSpace(0, 5);
rs.SetValueAt(0, 3);
REQUIRE(2 == rs.Runs());
rs.SetValueAt(1, 3);
REQUIRE(2 == rs.Runs());
rs.DeleteRange(1, 1);
REQUIRE(4 == rs.Length());
REQUIRE(2 == rs.Runs());
REQUIRE(3 == rs.ValueAt(0));
REQUIRE(0 == rs.ValueAt(1));
REQUIRE(0 == rs.StartRun(0));
REQUIRE(1 == rs.EndRun(0));
REQUIRE(1 == rs.StartRun(1));
REQUIRE(4 == rs.EndRun(1));
REQUIRE(1 == rs.StartRun(2));
REQUIRE(4 == rs.EndRun(2));
}
SECTION("Find") {
rs.InsertSpace(0, 5);
const int startFill = 1;
const int lengthFill = 3;
const auto fr = rs.FillRange(startFill, 99, lengthFill);
REQUIRE(FillResult<int>{true, 1, 3} == fr);
REQUIRE(0 == rs.Find(0,0));
REQUIRE(1 == rs.Find(99,0));
REQUIRE(-1 == rs.Find(3,0));
REQUIRE(4 == rs.Find(0,1));
REQUIRE(1 == rs.Find(99,1));
REQUIRE(-1 == rs.Find(3,1));
REQUIRE(4 == rs.Find(0,2));
REQUIRE(2 == rs.Find(99,2));
REQUIRE(-1 == rs.Find(3, 2));
REQUIRE(4 == rs.Find(0,4));
REQUIRE(-1 == rs.Find(99,4));
REQUIRE(-1 == rs.Find(3,4));
REQUIRE(-1 == rs.Find(0,5));
REQUIRE(-1 == rs.Find(99,5));
REQUIRE(-1 == rs.Find(3,5));
REQUIRE(-1 == rs.Find(0,6));
REQUIRE(-1 == rs.Find(99,6));
REQUIRE(-1 == rs.Find(3,6));
}
SECTION("AllSame") {
REQUIRE(true == rs.AllSame());
rs.InsertSpace(0, 5);
REQUIRE(true == rs.AllSame());
REQUIRE(false == rs.AllSameAs(88));
REQUIRE(true == rs.AllSameAs(0));
const int startFill = 1;
const int lengthFill = 3;
const auto fr = rs.FillRange(startFill, 99, lengthFill);
REQUIRE(true == fr.changed);
REQUIRE(false == rs.AllSame());
REQUIRE(false == rs.AllSameAs(88));
REQUIRE(false == rs.AllSameAs(0));
const auto fr2 = rs.FillRange(startFill, 0, lengthFill);
REQUIRE(true == fr2.changed);
REQUIRE(true == rs.AllSame());
REQUIRE(false == rs.AllSameAs(88));
REQUIRE(true == rs.AllSameAs(0));
}
SECTION("FindWithReversion") {
rs.InsertSpace(0, 5);
REQUIRE(1 == rs.Runs());
int startFill = 1;
int lengthFill = 1;
const auto fr = rs.FillRange(startFill, 99, lengthFill);
REQUIRE(FillResult<int>{true, 1, 1} == fr);
REQUIRE(3 == rs.Runs());
startFill = 2;
lengthFill = 1;
const auto fr2 = rs.FillRange(startFill, 99, lengthFill);
REQUIRE(FillResult<int>{true, 2, 1} == fr2);
REQUIRE(3 == rs.Runs());
startFill = 1;
lengthFill = 1;
const auto fr3 = rs.FillRange(startFill, 0, lengthFill);
REQUIRE(FillResult<int>{true, 1, 1} == fr3);
REQUIRE(3 == rs.Runs());
startFill = 2;
lengthFill = 1;
const auto fr4 = rs.FillRange(startFill, 0, lengthFill);
REQUIRE(FillResult<int>{true, 2, 1} == fr4);
REQUIRE(1 == rs.Runs());
REQUIRE(-1 == rs.Find(0,6));
}
SECTION("FinalRunInversion") {
REQUIRE(1 == rs.Runs());
rs.InsertSpace(0, 1);
REQUIRE(1 == rs.Runs());
rs.SetValueAt(0, 1);
REQUIRE(1 == rs.Runs());
rs.InsertSpace(1, 1);
REQUIRE(1 == rs.Runs());
rs.SetValueAt(1, 1);
REQUIRE(1 == rs.Runs());
rs.SetValueAt(1, 0);
REQUIRE(2 == rs.Runs());
rs.SetValueAt(1, 1);
REQUIRE(1 == rs.Runs());
}
SECTION("DeleteAll") {
rs.InsertSpace(0, 5);
rs.SetValueAt(0, 3);
rs.SetValueAt(1, 3);
rs.DeleteAll();
REQUIRE(0 == rs.Length());
REQUIRE(0 == rs.ValueAt(0));
REQUIRE(1 == rs.Runs());
}
SECTION("DeleteSecond") {
rs.InsertSpace(0, 3);
const int startFill = 1;
const int lengthFill = 1;
const auto fr = rs.FillRange(startFill, 99, lengthFill);
REQUIRE(true == fr.changed);
REQUIRE(3 == rs.Length());
REQUIRE(3 == rs.Runs());
rs.DeleteRange(1, 1);
REQUIRE(2 == rs.Length());
REQUIRE(1 == rs.Runs());
}
SECTION("DeleteEndRun") {
rs.InsertSpace(0, 2);
const int startFill = 1;
const int lengthFill = 1;
const auto fr = rs.FillRange(startFill, 99, lengthFill);
REQUIRE(true == fr.changed);
REQUIRE(2 == rs.Length());
REQUIRE(2 == rs.Runs());
REQUIRE(0 == rs.StartRun(0));
REQUIRE(1 == rs.EndRun(0));
REQUIRE(1 == rs.StartRun(1));
REQUIRE(2 == rs.EndRun(1));
rs.DeleteRange(1, 1);
REQUIRE(1 == rs.Length());
REQUIRE(1 == rs.Runs());
REQUIRE(0 == rs.StartRun(0));
REQUIRE(1 == rs.EndRun(0));
REQUIRE(0 == rs.StartRun(1));
REQUIRE(1 == rs.EndRun(1));
rs.Check();
}
SECTION("OutsideBounds") {
rs.InsertSpace(0, 1);
const int startFill = 1;
const int lengthFill = 1;
rs.FillRange(startFill, 99, lengthFill);
REQUIRE(1 == rs.Length());
REQUIRE(1 == rs.Runs());
REQUIRE(0 == rs.StartRun(0));
REQUIRE(1 == rs.EndRun(0));
}
}

View File

@ -0,0 +1,493 @@
/** @file testSparseVector.cxx
** Unit Tests for Scintilla internal data structures
**/
#include <cstddef>
#include <cassert>
#include <cstring>
#include <stdexcept>
#include <string_view>
#include <vector>
#include <optional>
#include <algorithm>
#include <memory>
#include "Debugging.h"
#include "Position.h"
#include "UniqueString.h"
#include "SplitVector.h"
#include "Partitioning.h"
#include "SparseVector.h"
#include "catch.hpp"
using namespace Scintilla::Internal;
// Test SparseVector.
using UniqueInt = std::unique_ptr<int>;
TEST_CASE("CompileCopying SparseVector") {
// These are compile-time tests to check that basic copy and move
// operations are defined correctly.
SECTION("CopyingMoving") {
SparseVector<int> s;
SparseVector<int> s2;
// Copy constructor
const SparseVector<int> sa(s);
// Copy assignment
SparseVector<int> sb;
sb = s;
// Move constructor
const SparseVector<int> sc(std::move(s));
// Move assignment
SparseVector<int> sd;
sd = (std::move(s2));
}
SECTION("MoveOnly") {
SparseVector<UniqueInt> s;
#if defined(SHOW_COPY_BUILD_FAILURES)
// Copy is not defined for std::unique_ptr
// Copy constructor fails
SparseVector<UniqueInt> sa(s);
// Copy assignment fails
SparseVector<UniqueInt> sb;
sb = s;
#endif
// Move constructor
const SparseVector<UniqueInt> sc(std::move(s));
// Move assignment
SparseVector<UniqueInt> s2;
SparseVector<UniqueInt> sd;
sd = (std::move(s2));
}
}
namespace {
// Helper to produce a string representation of a SparseVector<const char *>
// to simplify checks.
std::string Representation(const SparseVector<UniqueString> &st) {
std::string ret;
for (int i = 0;i <= st.Length();i++) {
const char *value = st.ValueAt(i).get();
if (value && *value)
ret += value;
else
ret += "-";
}
return ret;
}
}
TEST_CASE("SparseVector") {
SparseVector<UniqueString> st;
SECTION("IsEmptyInitially") {
REQUIRE(1 == st.Elements());
REQUIRE(0 == st.Length());
REQUIRE("-" == Representation(st));
st.Check();
}
SECTION("InsertSpace") {
st.InsertSpace(0, 5);
REQUIRE(1 == st.Elements());
REQUIRE(static_cast<const char *>(nullptr) == st.ValueAt(0).get());
REQUIRE(static_cast<const char *>(nullptr) == st.ValueAt(1).get());
REQUIRE(static_cast<const char *>(nullptr) == st.ValueAt(4).get());
st.Check();
}
SECTION("InsertValue") {
st.InsertSpace(0, 5);
st.SetValueAt(3, UniqueStringCopy("3"));
REQUIRE(2 == st.Elements());
REQUIRE("---3--" == Representation(st));
st.Check();
}
SECTION("InsertAndChangeAndDeleteValue") {
st.InsertSpace(0, 5);
REQUIRE(5 == st.Length());
st.SetValueAt(3, UniqueStringCopy("3"));
REQUIRE(2 == st.Elements());
st.SetValueAt(3, UniqueStringCopy("4"));
REQUIRE(2 == st.Elements());
st.DeletePosition(3);
REQUIRE(1 == st.Elements());
REQUIRE(4 == st.Length());
REQUIRE("-----" == Representation(st));
st.Check();
}
SECTION("InsertAndDeleteAtStart") {
REQUIRE(1 == st.Elements());
st.InsertSpace(0, 5);
st.SetValueAt(0, UniqueStringCopy("3"));
REQUIRE(1 == st.Elements());
REQUIRE("3-----" == Representation(st));
st.DeletePosition(0);
REQUIRE(1 == st.Elements());
REQUIRE("-----" == Representation(st));
st.SetValueAt(0, UniqueStringCopy("4"));
REQUIRE(1 == st.Elements());
REQUIRE("4----" == Representation(st));
st.DeletePosition(0);
REQUIRE(1 == st.Elements());
REQUIRE("----" == Representation(st));
st.SetValueAt(0, UniqueStringCopy("4"));
REQUIRE(1 == st.Elements());
REQUIRE("4---" == Representation(st));
st.DeletePosition(0);
REQUIRE(1 == st.Elements());
REQUIRE("---" == Representation(st));
st.Check();
}
SECTION("InsertStringAtStartThenInsertSpaceAtStart") {
REQUIRE(1 == st.Elements());
st.InsertSpace(0, 5);
st.SetValueAt(0, UniqueStringCopy("3"));
REQUIRE(1 == st.Elements());
REQUIRE("3-----" == Representation(st));
st.InsertSpace(0, 1);
REQUIRE(2 == st.Elements());
REQUIRE("-3-----" == Representation(st));
st.Check();
}
SECTION("InsertSpaceAfterStart") {
REQUIRE(1 == st.Elements());
st.InsertSpace(0, 5);
st.SetValueAt(1, UniqueStringCopy("1"));
REQUIRE(2 == st.Elements());
REQUIRE("-1----" == Representation(st));
st.InsertSpace(1, 1);
REQUIRE(2 == st.Elements());
REQUIRE("--1----" == Representation(st));
st.Check();
}
SECTION("InsertStringAt1ThenInsertLettersAt1") {
REQUIRE(1 == st.Elements());
st.InsertSpace(0, 5);
st.SetValueAt(1, UniqueStringCopy("9"));
REQUIRE(2 == st.Elements());
REQUIRE("-9----" == Representation(st));
st.InsertSpace(0, 1);
REQUIRE(2 == st.Elements());
REQUIRE("--9----" == Representation(st));
// Initial st has allocation of 9 values so this should cause reallocation
const std::string letters("ABCDEFGHIJKLMNOP"); // 16 letters
for (const char letter : letters) {
const char sLetter[] = { letter, 0 };
st.InsertSpace(0, 1);
st.SetValueAt(1, UniqueStringCopy(sLetter));
}
REQUIRE("-PONMLKJIHGFEDCBA-9----" == Representation(st));
st.Check();
}
SECTION("InsertAndDeleteAtEnd") {
REQUIRE(1 == st.Elements());
st.InsertSpace(0, 5);
st.SetValueAt(4, UniqueStringCopy("5"));
REQUIRE(2 == st.Elements());
REQUIRE("----5-" == Representation(st));
st.SetValueAt(5, UniqueStringCopy("6"));
REQUIRE(2 == st.Elements());
REQUIRE("----56" == Representation(st));
st.DeletePosition(4);
REQUIRE(1 == st.Elements());
REQUIRE("----6" == Representation(st));
st.SetValueAt(4, UniqueStringCopy("7"));
REQUIRE(1 == st.Elements());
REQUIRE("----7" == Representation(st));
st.Check();
}
SECTION("SetNULL") {
REQUIRE(1 == st.Elements());
st.InsertSpace(0, 5);
st.SetValueAt(4, UniqueStringCopy("5"));
REQUIRE(2 == st.Elements());
REQUIRE("----5-" == Representation(st));
st.SetValueAt(4, nullptr);
REQUIRE(1 == st.Elements());
REQUIRE("------" == Representation(st));
st.Check();
st.SetValueAt(5, nullptr);
REQUIRE(1 == st.Elements());
REQUIRE("------" == Representation(st));
st.Check();
}
SECTION("CheckDeletionLeavesOrdered") {
REQUIRE(1 == st.Elements());
st.InsertSpace(0, 1);
st.SetValueAt(0, UniqueStringCopy("1"));
REQUIRE("1-" == Representation(st));
REQUIRE(1 == st.Elements());
st.InsertSpace(1, 1);
st.SetValueAt(1, UniqueStringCopy("2"));
REQUIRE("12-" == Representation(st));
st.DeletePosition(0);
REQUIRE("2-" == Representation(st));
REQUIRE(1 == st.Elements());
st.DeletePosition(0);
REQUIRE("-" == Representation(st));
}
SECTION("DeleteAll") {
REQUIRE(1 == st.Elements());
st.InsertSpace(0, 10);
st.SetValueAt(9, UniqueStringCopy("9"));
st.SetValueAt(7, UniqueStringCopy("7"));
st.SetValueAt(4, UniqueStringCopy("4"));
st.SetValueAt(3, UniqueStringCopy("3"));
REQUIRE(5 == st.Elements());
REQUIRE("---34--7-9-" == Representation(st));
st.DeleteAll();
REQUIRE(1 == st.Elements());
REQUIRE("-" == Representation(st));
st.Check();
}
SECTION("DeleteStarting") {
REQUIRE(1 == st.Elements());
st.InsertSpace(0, 2);
st.SetValueAt(0, UniqueStringCopy("1"));
st.SetValueAt(1, UniqueStringCopy("2"));
REQUIRE("12-" == Representation(st));
st.DeletePosition(0);
REQUIRE("2-" == Representation(st));
st.DeletePosition(0);
REQUIRE("-" == Representation(st));
}
SECTION("DeleteRange") {
REQUIRE(1 == st.Elements());
st.InsertSpace(0, 10);
st.SetValueAt(9, UniqueStringCopy("9"));
st.SetValueAt(7, UniqueStringCopy("7"));
st.SetValueAt(4, UniqueStringCopy("4"));
st.SetValueAt(3, UniqueStringCopy("3"));
REQUIRE(5 == st.Elements());
REQUIRE(10 == st.Length());
REQUIRE("---34--7-9-" == Representation(st));
// Delete in space
st.DeleteRange(1, 1);
REQUIRE(5 == st.Elements());
REQUIRE(9 == st.Length());
REQUIRE("--34--7-9-" == Representation(st));
// Delete 2 values
st.DeleteRange(3, 4);
REQUIRE(3 == st.Elements());
REQUIRE(5 == st.Length());
REQUIRE("--3-9-" == Representation(st));
// Deletion at start
st.DeleteRange(0, 1);
REQUIRE(3 == st.Elements());
REQUIRE(4 == st.Length());
REQUIRE("-3-9-" == Representation(st));
}
SECTION("DeleteRangeAtEnds") {
// There are always elements at start and end although they can be nulled
REQUIRE(1 == st.Elements());
st.InsertSpace(0, 4);
REQUIRE(4 == st.Length());
st.SetValueAt(1, UniqueStringCopy("3"));
st.SetValueAt(4, UniqueStringCopy("9"));
REQUIRE("-3--9" == Representation(st));
REQUIRE(2 == st.Elements());
// Empty deletion at end -> no effect
st.DeleteRange(4, 0);
REQUIRE(2 == st.Elements());
REQUIRE(4 == st.Length());
REQUIRE("-3--9" == Representation(st));
// Delete value at start
st.InsertSpace(0, 1);
st.SetValueAt(0, UniqueStringCopy("0"));
REQUIRE(2 == st.Elements());
REQUIRE(5 == st.Length());
REQUIRE("0-3--9" == Representation(st));
st.DeleteRange(0, 1);
REQUIRE(2 == st.Elements());
REQUIRE(4 == st.Length());
REQUIRE("03--9" == Representation(st));
// Empty deletion at start -> no effect
st.InsertSpace(0, 1);
st.SetValueAt(0, UniqueStringCopy("1"));
REQUIRE(3 == st.Elements());
REQUIRE(5 == st.Length());
REQUIRE("103--9" == Representation(st));
st.DeleteRange(0, 0);
REQUIRE(3 == st.Elements());
REQUIRE(5 == st.Length());
REQUIRE("103--9" == Representation(st));
}
SECTION("DeleteStartingRange") {
REQUIRE(1 == st.Elements());
st.InsertSpace(0, 2);
st.SetValueAt(0, UniqueStringCopy("1"));
st.SetValueAt(1, UniqueStringCopy("2"));
REQUIRE(2 == st.Length());
REQUIRE("12-" == Representation(st));
st.DeleteRange(0,1);
REQUIRE(1 == st.Length());
REQUIRE("2-" == Representation(st));
st.DeleteRange(0,1);
REQUIRE(0 == st.Length());
REQUIRE("-" == Representation(st));
st.InsertSpace(0, 2);
st.SetValueAt(1, UniqueStringCopy("1"));
REQUIRE(2 == st.Length());
REQUIRE("-1-" == Representation(st));
st.DeleteRange(0, 2);
REQUIRE("-" == Representation(st));
st.InsertSpace(0, 4);
st.SetValueAt(1, UniqueStringCopy("1"));
st.SetValueAt(3, UniqueStringCopy("3"));
REQUIRE(4 == st.Length());
REQUIRE("-1-3-" == Representation(st));
st.DeleteRange(0, 3);
REQUIRE("3-" == Representation(st));
st.DeleteRange(0, 1);
REQUIRE("-" == Representation(st));
st.InsertSpace(0, 4);
st.SetValueAt(1, UniqueStringCopy("1"));
st.SetValueAt(4, UniqueStringCopy("4"));
st.SetValueAt(3, UniqueStringCopy("3"));
REQUIRE("-1-34" == Representation(st));
st.DeleteRange(1, 3);
REQUIRE("-4" == Representation(st));
st.InsertSpace(1, 3);
REQUIRE("----4" == Representation(st));
st.SetValueAt(4, UniqueStringCopy("4"));
st.SetValueAt(3, UniqueStringCopy("3"));
REQUIRE("---34" == Representation(st));
st.DeleteRange(1, 3);
REQUIRE("-4" == Representation(st));
}
}
TEST_CASE("SparseTextInt") {
SparseVector<int> st;
SECTION("InsertAndDeleteValue") {
st.InsertSpace(0, 5);
st.SetValueAt(3, 3);
REQUIRE(2 == st.Elements());
REQUIRE(0 == st.ValueAt(0));
REQUIRE(0 == st.ValueAt(1));
REQUIRE(0 == st.ValueAt(2));
REQUIRE(3 == st.ValueAt(3));
REQUIRE(0 == st.ValueAt(4));
st.SetValueAt(3, -3);
REQUIRE(2 == st.Elements());
REQUIRE(0 == st.ValueAt(0));
REQUIRE(0 == st.ValueAt(1));
REQUIRE(0 == st.ValueAt(2));
REQUIRE(-3 == st.ValueAt(3));
REQUIRE(0 == st.ValueAt(4));
st.SetValueAt(3, 0);
REQUIRE(1 == st.Elements());
REQUIRE(0 == st.ValueAt(0));
REQUIRE(0 == st.ValueAt(1));
REQUIRE(0 == st.ValueAt(2));
REQUIRE(0 == st.ValueAt(3));
REQUIRE(0 == st.ValueAt(4));
st.Check();
}
SECTION("IndexAfter") {
st.InsertSpace(0, 5);
REQUIRE(1 == st.Elements());
REQUIRE(0 == st.IndexAfter(-1));
REQUIRE(0 == st.PositionOfElement(0));
REQUIRE(1 == st.IndexAfter(0));
REQUIRE(5 == st.PositionOfElement(1));
st.SetValueAt(3, 3);
REQUIRE(2 == st.Elements());
REQUIRE(0 == st.IndexAfter(-1));
REQUIRE(0 == st.PositionOfElement(0));
REQUIRE(1 == st.IndexAfter(0));
REQUIRE(3 == st.PositionOfElement(1));
REQUIRE(2 == st.IndexAfter(3));
REQUIRE(5 == st.PositionOfElement(2));
REQUIRE(2 == st.IndexAfter(4));
}
SECTION("PositionNext") {
st.InsertSpace(0, 5);
REQUIRE(1 == st.Elements());
REQUIRE(5 == st.PositionNext(-1));
REQUIRE(5 == st.PositionNext(0));
REQUIRE(6 == st.PositionNext(5));
st.SetValueAt(3, 3);
REQUIRE(2 == st.Elements());
REQUIRE(3 == st.PositionNext(-1));
REQUIRE(3 == st.PositionNext(0));
REQUIRE(5 == st.PositionNext(3));
REQUIRE(6 == st.PositionNext(5));
}
}
TEST_CASE("SparseTextString") {
SparseVector<std::string> st;
SECTION("InsertAndDeleteValue") {
st.InsertSpace(0, 5);
REQUIRE(5 == st.Length());
st.SetValueAt(3, std::string("3"));
REQUIRE(2 == st.Elements());
REQUIRE("" == st.ValueAt(0));
REQUIRE("" == st.ValueAt(2));
REQUIRE("3" == st.ValueAt(3));
REQUIRE("" == st.ValueAt(4));
st.DeletePosition(0);
REQUIRE(4 == st.Length());
REQUIRE("3" == st.ValueAt(2));
st.DeletePosition(2);
REQUIRE(1 == st.Elements());
REQUIRE(3 == st.Length());
REQUIRE("" == st.ValueAt(0));
REQUIRE("" == st.ValueAt(1));
REQUIRE("" == st.ValueAt(2));
st.Check();
}
SECTION("SetAndMoveString") {
st.InsertSpace(0, 2);
REQUIRE(2u == st.Length());
const std::string s24("24");
st.SetValueAt(0, s24);
REQUIRE("24" == s24); // Not moved from
REQUIRE("" == st.ValueAt(-1));
REQUIRE("24" == st.ValueAt(0));
REQUIRE("" == st.ValueAt(1));
std::string s25("25");
st.SetValueAt(1, std::move(s25));
// Deliberate check of moved from: provokes warning from Visual C++ code analysis
REQUIRE("" == s25);
REQUIRE("25" == st.ValueAt(1));
}
}

View File

@ -0,0 +1,405 @@
/** @file testSplitVector.cxx
** Unit Tests for Scintilla internal data structures
**/
#include <cstddef>
#include <cstring>
#include <stdexcept>
#include <string_view>
#include <vector>
#include <optional>
#include <algorithm>
#include <memory>
#include "Debugging.h"
#include "Position.h"
#include "SplitVector.h"
#include "catch.hpp"
using namespace Scintilla::Internal;
// Test SplitVector.
using UniqueInt = std::unique_ptr<int>;
// Test SplitVector.
TEST_CASE("CompileCopying SplitVector") {
// These are compile-time tests to check that basic copy and move
// operations are defined correctly.
SECTION("CopyingMoving") {
SplitVector<int> s;
SplitVector<int> s2;
// Copy constructor fails
const SplitVector<int> sa(s);
// Copy assignment fails
SplitVector<int> sb;
sb = s;
// Move constructor fails
const SplitVector<int> sc(std::move(s));
// Move assignment fails
SplitVector<int> sd;
sd = (std::move(s2));
}
SECTION("MoveOnly") {
SplitVector<UniqueInt> s;
#if defined(SHOW_COPY_BUILD_FAILURES)
// Copy is not defined for std::unique_ptr
// Copy constructor fails
SplitVector<UniqueInt> sa(s);
// Copy assignment fails
SplitVector<UniqueInt> sb;
sb = s;
#endif
// Move constructor fails
const SplitVector<UniqueInt> sc(std::move(s));
// Move assignment fails
SplitVector<UniqueInt> sd;
sd = (std::move(s));
}
}
struct StringSetHolder {
SplitVector<std::string> sa;
[[nodiscard]] bool Check() const noexcept {
for (int i = 0; i < sa.Length(); i++) {
if (sa[i].empty()) {
return false;
}
}
return true;
}
};
constexpr int lengthTestArray = 4;
static const int testArray[4] = {3, 4, 5, 6};
TEST_CASE("SplitVector") {
SplitVector<int> sv;
SECTION("IsEmptyInitially") {
REQUIRE(0 == sv.Length());
}
SECTION("InsertOne") {
sv.InsertValue(0, 10, 0);
sv.Insert(5, 3);
REQUIRE(11 == sv.Length());
for (int i=0; i<sv.Length(); i++) {
REQUIRE(((i == 5) ? 3 : 0) == sv.ValueAt(i));
}
}
SECTION("Insertion") {
sv.InsertValue(0, 10, 0);
REQUIRE(10 == sv.Length());
for (int i=0; i<sv.Length(); i++) {
REQUIRE(0 == sv.ValueAt(i));
}
}
SECTION("InsertionString") {
// This test failed an earlier version of SplitVector that copied backwards incorrectly
StringSetHolder ssh;
ssh.sa.Insert(0, "Alpha");
REQUIRE(ssh.Check());
ssh.sa.Insert(0, "Beta");
REQUIRE(ssh.Check());
ssh.sa.Insert(0, "Cat");
REQUIRE(ssh.Check());
ssh.sa.Insert(1, "Dog");
REQUIRE(ssh.Check());
ssh.sa.Insert(0, "Elephant");
REQUIRE(ssh.Check());
ssh.sa.Insert(1, "Fox");
REQUIRE(ssh.Check());
ssh.sa.Insert(0, "Grass");
REQUIRE(ssh.Check());
ssh.sa.Insert(1, "Hat");
REQUIRE(ssh.Check());
ssh.sa.Delete(4);
REQUIRE(ssh.Check());
ssh.sa.Insert(0, "Indigo");
REQUIRE(ssh.Check());
ssh.sa.Insert(1, "Jackal");
REQUIRE(ssh.Check());
ssh.sa.Insert(0, "Kanga");
REQUIRE(ssh.Check());
ssh.sa.Insert(1, "Lion");
REQUIRE(ssh.Check());
ssh.sa.Insert(0, "Mango");
REQUIRE(ssh.Check());
ssh.sa.Insert(1, "Neon");
REQUIRE(ssh.Check());
}
SECTION("InsertionPattern") {
sv.Insert(0, 1); // 1
sv.Insert(0, 2); // 21
sv.Insert(0, 3); // 321
sv.Insert(1, 4); // 3421
sv.Insert(0, 5); // 53421
sv.Insert(1, 6); // 563421
sv.Insert(0, 7); // 7563421
sv.Insert(1, 8); // 78563421
REQUIRE(8 == sv.Length());
REQUIRE(7 == sv.ValueAt(0));
REQUIRE(8 == sv.ValueAt(1));
REQUIRE(5 == sv.ValueAt(2));
REQUIRE(6 == sv.ValueAt(3));
REQUIRE(3 == sv.ValueAt(4));
REQUIRE(4 == sv.ValueAt(5));
REQUIRE(2 == sv.ValueAt(6));
REQUIRE(1 == sv.ValueAt(7));
sv.Delete(4); // 7856421
REQUIRE(7 == sv.Length());
REQUIRE(7 == sv.ValueAt(0));
REQUIRE(8 == sv.ValueAt(1));
REQUIRE(5 == sv.ValueAt(2));
REQUIRE(6 == sv.ValueAt(3));
REQUIRE(4 == sv.ValueAt(4));
REQUIRE(2 == sv.ValueAt(5));
REQUIRE(1 == sv.ValueAt(6));
sv.Insert(0, 9); // 97856421
sv.Insert(1, 0xa); // 9a7856421
sv.Insert(0, 0xb); // b9a7856421
sv.Insert(1, 0xc); // bc9a7856421
sv.Insert(0, 0xd); // dbc9a7856421
sv.Insert(1, 0xe); // debc9a7856421
REQUIRE(13 == sv.Length());
REQUIRE(0xd == sv.ValueAt(0));
REQUIRE(0xe == sv.ValueAt(1));
REQUIRE(0xb == sv.ValueAt(2));
REQUIRE(0xc == sv.ValueAt(3));
REQUIRE(9 == sv.ValueAt(4));
REQUIRE(0xa == sv.ValueAt(5));
REQUIRE(7 == sv.ValueAt(6));
REQUIRE(8 == sv.ValueAt(7));
REQUIRE(5 == sv.ValueAt(8));
REQUIRE(6 == sv.ValueAt(9));
REQUIRE(4 == sv.ValueAt(10));
REQUIRE(2 == sv.ValueAt(11));
REQUIRE(1 == sv.ValueAt(12));
}
SECTION("EnsureLength") {
sv.EnsureLength(4);
REQUIRE(4 == sv.Length());
for (int i=0; i<sv.Length(); i++) {
REQUIRE(0 == sv.ValueAt(i));
}
}
SECTION("InsertFromArray") {
sv.InsertFromArray(0, testArray, 0, lengthTestArray);
REQUIRE(lengthTestArray == sv.Length());
for (int i=0; i<sv.Length(); i++) {
REQUIRE((i+3) == sv.ValueAt(i));
}
}
SECTION("InsertEmpty") {
sv.InsertEmpty(0, 0);
REQUIRE(0 == sv.Length());
int *pi = sv.InsertEmpty(0, 2);
REQUIRE(2 == sv.Length());
REQUIRE(0 == sv.ValueAt(0));
REQUIRE(0 == sv.ValueAt(1));
pi[0] = 4;
pi[1] = 5;
REQUIRE(4 == sv.ValueAt(0));
REQUIRE(5 == sv.ValueAt(1));
pi = sv.InsertEmpty(1, 2);
pi[0] = 6;
pi[1] = 7;
REQUIRE(4 == sv.Length());
REQUIRE(4 == sv.ValueAt(0));
REQUIRE(6 == sv.ValueAt(1));
REQUIRE(7 == sv.ValueAt(2));
REQUIRE(5 == sv.ValueAt(3));
}
SECTION("SetValue") {
sv.InsertValue(0, 10, 0);
sv.SetValueAt(5, 3);
REQUIRE(10 == sv.Length());
for (int i=0; i<sv.Length(); i++) {
REQUIRE(((i == 5) ? 3 : 0) == sv.ValueAt(i));
}
// Move the gap
sv.InsertValue(7, 1, 17);
REQUIRE(17 == sv.ValueAt(7));
REQUIRE(0 == sv.ValueAt(8));
// Set after the gap
sv.SetValueAt(8, 19);
REQUIRE(19 == sv.ValueAt(8));
}
SECTION("Indexing") {
sv.InsertValue(0, 10, 0);
sv.SetValueAt(5, 3);
REQUIRE(10 == sv.Length());
for (int i=0; i<sv.Length(); i++) {
REQUIRE(((i == 5) ? 3 : 0) == sv[i]);
}
}
SECTION("Fill") {
sv.InsertValue(0, 10, 0);
REQUIRE(10 == sv.Length());
sv.InsertValue(7, 1, 1);
REQUIRE(11 == sv.Length());
for (int i=0; i<sv.Length(); i++) {
REQUIRE(((i == 7) ? 1 : 0) == sv.ValueAt(i));
}
}
SECTION("DeleteOne") {
sv.InsertFromArray(0, testArray, 0, lengthTestArray);
sv.Delete(2);
REQUIRE((lengthTestArray-1) == sv.Length());
REQUIRE(3 == sv[0]);
REQUIRE(4 == sv[1]);
REQUIRE(6 == sv[2]);
}
SECTION("DeleteRange") {
sv.InsertValue(0, 10, 0);
REQUIRE(10 == sv.Length());
sv.InsertValue(7, 1, 1);
REQUIRE(11 == sv.Length());
sv.DeleteRange(2, 3);
REQUIRE(8 == sv.Length());
for (int i=0; i<sv.Length(); i++) {
REQUIRE(((i == 4) ? 1 : 0) == sv.ValueAt(i));
}
}
SECTION("DeleteAll") {
sv.InsertValue(0, 10, 0);
sv.InsertValue(7, 1, 1);
sv.DeleteRange(2, 3);
sv.DeleteAll();
REQUIRE(0 == sv.Length());
}
SECTION("GetRange") {
sv.InsertValue(0, 10, 0);
sv.InsertValue(7, 1, 1);
int retrieveArray[11] = {0};
sv.GetRange(retrieveArray, 0, 11);
for (int i=0; i<sv.Length(); i++) {
REQUIRE(((i==7) ? 1 : 0) == retrieveArray[i]);
}
}
SECTION("GetRangeOverGap") {
sv.InsertFromArray(0, testArray, 0, lengthTestArray);
REQUIRE(lengthTestArray == sv.Length());
int retrieveArray[lengthTestArray] = {0};
sv.GetRange(retrieveArray, 0, lengthTestArray);
for (int i=0; i<sv.Length(); i++) {
REQUIRE((i+3) == retrieveArray[i]);
}
}
SECTION("ReplaceUp") {
// Replace each element by inserting and then deleting the displaced element
// This should perform many moves
constexpr int testLength=105;
sv.EnsureLength(testLength);
for (int i=0; i<testLength; i++)
sv.SetValueAt(i, i+2);
REQUIRE(testLength == sv.Length());
for (int i=0; i<sv.Length(); i++) {
sv.InsertValue(i, 1, i+9);
sv.Delete(i+1);
}
for (int i=0; i<sv.Length(); i++)
REQUIRE((i+9) == sv.ValueAt(i));
}
SECTION("ReplaceDown") {
// From the end, replace each element by inserting and then deleting the displaced element
// This should perform many moves
constexpr int testLength=24;
sv.EnsureLength(testLength);
for (int i=0; i<testLength; i++)
sv.SetValueAt(i, i+12);
REQUIRE(testLength == sv.Length());
for (ptrdiff_t i=sv.Length()-1; i>=0; i--) {
sv.InsertValue(i, 1, static_cast<int>(i+5));
sv.Delete(i+1);
}
for (int i=0; i<sv.Length(); i++)
REQUIRE((i+5) == sv.ValueAt(i));
}
SECTION("BufferPointer") {
// Low-level access to the data
sv.InsertFromArray(0, testArray, 0, lengthTestArray);
sv.Insert(0, 99); // This moves the gap so that BufferPointer() must also move
REQUIRE(1 == sv.GapPosition());
constexpr int lengthAfterInsertion = 1 + lengthTestArray;
REQUIRE(lengthAfterInsertion == (sv.Length()));
const int *retrievePointer = sv.BufferPointer();
for (int i=1; i<sv.Length(); i++) {
REQUIRE((i+3-1) == retrievePointer[i]);
}
REQUIRE(lengthAfterInsertion == sv.Length());
// Gap was moved to end.
REQUIRE(lengthAfterInsertion == sv.GapPosition());
}
SECTION("DeleteBackAndForth") {
sv.InsertValue(0, 10, 87);
for (int i=0; i<10; i+=2) {
const int len = 10 - i;
REQUIRE(len == sv.Length());
for (int j=0; j<sv.Length(); j++) {
REQUIRE(87 == sv.ValueAt(j));
}
sv.Delete(len-1);
sv.Delete(0);
}
}
SECTION("GrowSize") {
sv.SetGrowSize(5);
REQUIRE(5 == sv.GetGrowSize());
}
SECTION("OutsideBounds") {
sv.InsertValue(0, 10, 87);
REQUIRE(0 == sv.ValueAt(-1));
REQUIRE(0 == sv.ValueAt(10));
/* Could be a death test as this asserts:
sv.SetValueAt(-1,98);
sv.SetValueAt(10,99);
REQUIRE(0 == sv.ValueAt(-1));
REQUIRE(0 == sv.ValueAt(10));
*/
}
}

View File

@ -0,0 +1,361 @@
/** @file testUniConversion.cxx
** Unit Tests for Scintilla internal data structures
**/
#include <cstring>
#include <string>
#include <string_view>
#include <vector>
#include <optional>
#include <algorithm>
#include <memory>
#include "Debugging.h"
#include "UniConversion.h"
#include "catch.hpp"
using namespace Scintilla::Internal;
// Test UniConversion.
// Use examples from Wikipedia:
// https://en.wikipedia.org/wiki/UTF-8
TEST_CASE("UTF16Length") {
SECTION("UTF16Length ASCII") {
// Latin Small Letter A
const char *s = "a";
const size_t len = UTF16Length(s);
REQUIRE(len == 1U);
}
SECTION("UTF16Length Example1") {
// Dollar Sign
const char *s = "\x24";
const size_t len = UTF16Length(s);
REQUIRE(len == 1U);
}
SECTION("UTF16Length Example2") {
// Cent Sign
const char *s = "\xC2\xA2";
const size_t len = UTF16Length(s);
REQUIRE(len == 1U);
}
SECTION("UTF16Length Example3") {
// Euro Sign
const char *s = "\xE2\x82\xAC";
const size_t len = UTF16Length(s);
REQUIRE(len == 1U);
}
SECTION("UTF16Length Example4") {
// Gothic Letter Hwair
const char *s = "\xF0\x90\x8D\x88";
const size_t len = UTF16Length(s);
REQUIRE(len == 2U);
}
SECTION("UTF16Length Invalid Trail byte in lead position") {
const char *s = "a\xB5yz";
const size_t len = UTF16Length(s);
REQUIRE(len == 4U);
}
SECTION("UTF16Length Invalid Lead byte at end") {
const char *s = "a\xC2";
const size_t len = UTF16Length(s);
REQUIRE(len == 2U);
}
SECTION("UTF16Length Invalid Lead byte implies 3 trails but only 2") {
const char *s = "a\xF1yz";
const size_t len = UTF16Length(s);
REQUIRE(len == 2U);
}
}
TEST_CASE("UniConversion") {
// UnicodeFromUTF8
SECTION("UnicodeFromUTF8 ASCII") {
const unsigned char s[]={'a', 0, 0, 0};
REQUIRE(UnicodeFromUTF8(s) == 'a');
}
SECTION("UnicodeFromUTF8 Example1") {
const unsigned char s[]={0x24, 0, 0, 0};
REQUIRE(UnicodeFromUTF8(s) == 0x24);
}
SECTION("UnicodeFromUTF8 Example2") {
const unsigned char s[]={0xC2, 0xA2, 0, 0};
REQUIRE(UnicodeFromUTF8(s) == 0xA2);
}
SECTION("UnicodeFromUTF8 Example3") {
const unsigned char s[]={0xE2, 0x82, 0xAC, 0};
REQUIRE(UnicodeFromUTF8(s) == 0x20AC);
}
SECTION("UnicodeFromUTF8 Example4") {
const unsigned char s[]={0xF0, 0x90, 0x8D, 0x88, 0};
REQUIRE(UnicodeFromUTF8(s) == 0x10348);
}
// UTF16FromUTF8
SECTION("UTF16FromUTF8 ASCII") {
const char s[] = {'a', 0};
wchar_t tbuf[1] = {0};
const size_t tlen = UTF16FromUTF8(s, tbuf, 1);
REQUIRE(tlen == 1U);
REQUIRE(tbuf[0] == 'a');
}
SECTION("UTF16FromUTF8 Example1") {
const char s[] = {'\x24', 0};
wchar_t tbuf[1] = {0};
const size_t tlen = UTF16FromUTF8(s, tbuf, 1);
REQUIRE(tlen == 1U);
REQUIRE(tbuf[0] == 0x24);
}
SECTION("UTF16FromUTF8 Example2") {
const char s[] = {'\xC2', '\xA2', 0};
wchar_t tbuf[1] = {0};
const size_t tlen = UTF16FromUTF8(s, tbuf, 1);
REQUIRE(tlen == 1U);
REQUIRE(tbuf[0] == 0xA2);
}
SECTION("UTF16FromUTF8 Example3") {
const char s[] = {'\xE2', '\x82', '\xAC', 0};
wchar_t tbuf[1] = {0};
const size_t tlen = UTF16FromUTF8(s, tbuf, 1);;
REQUIRE(tlen == 1U);
REQUIRE(tbuf[0] == 0x20AC);
}
SECTION("UTF16FromUTF8 Example4") {
const char s[] = {'\xF0', '\x90', '\x8D', '\x88', 0};
wchar_t tbuf[2] = {0, 0};
const size_t tlen = UTF16FromUTF8(s, tbuf, 2);
REQUIRE(tlen == 2U);
REQUIRE(tbuf[0] == 0xD800);
REQUIRE(tbuf[1] == 0xDF48);
}
SECTION("UTF16FromUTF8 Invalid Trail byte in lead position") {
const char s[] = "a\xB5yz";
wchar_t tbuf[4] = {};
const size_t tlen = UTF16FromUTF8(s, tbuf, 4);
REQUIRE(tlen == 4U);
REQUIRE(tbuf[0] == 'a');
REQUIRE(tbuf[1] == 0xB5);
REQUIRE(tbuf[2] == 'y');
REQUIRE(tbuf[3] == 'z');
}
SECTION("UTF16FromUTF8 Invalid Lead byte at end") {
const char s[] = "a\xC2";
wchar_t tbuf[2] = {};
const size_t tlen = UTF16FromUTF8(s, tbuf, 2);
REQUIRE(tlen == 2U);
REQUIRE(tbuf[0] == 'a');
REQUIRE(tbuf[1] == 0xC2);
}
SECTION("UTF16FromUTF8 Invalid Lead byte implies 3 trails but only 2") {
const char *s = "a\xF1yz";
wchar_t tbuf[4] = {};
const size_t tlen = UTF16FromUTF8(s, tbuf, 4);
REQUIRE(tlen == 2U);
REQUIRE(tbuf[0] == 'a');
REQUIRE(tbuf[1] == 0xF1);
}
// UTF32FromUTF8
SECTION("UTF32FromUTF8 ASCII") {
const char s[] = {'a', 0};
unsigned int tbuf[1] = {0};
const size_t tlen = UTF32FromUTF8(s, tbuf, 1);
REQUIRE(tlen == 1U);
REQUIRE(tbuf[0] == static_cast<unsigned int>('a'));
}
SECTION("UTF32FromUTF8 Example1") {
const char s[] = {'\x24', 0};
unsigned int tbuf[1] = {0};
const size_t tlen = UTF32FromUTF8(s, tbuf, 1);
REQUIRE(tlen == 1U);
REQUIRE(tbuf[0] == 0x24);
}
SECTION("UTF32FromUTF8 Example2") {
const char s[] = {'\xC2', '\xA2', 0};
unsigned int tbuf[1] = {0};
const size_t tlen = UTF32FromUTF8(s, tbuf, 1);
REQUIRE(tlen == 1U);
REQUIRE(tbuf[0] == 0xA2);
}
SECTION("UTF32FromUTF8 Example3") {
const char s[] = {'\xE2', '\x82', '\xAC', 0};
unsigned int tbuf[1] = {0};
const size_t tlen = UTF32FromUTF8(s, tbuf, 1);
REQUIRE(tlen == 1U);
REQUIRE(tbuf[0] == 0x20AC);
}
SECTION("UTF32FromUTF8 Example4") {
const char s[] = {'\xF0', '\x90', '\x8D', '\x88', 0};
unsigned int tbuf[1] = {0};
const size_t tlen = UTF32FromUTF8(s, tbuf, 1);
REQUIRE(tlen == 1U);
REQUIRE(tbuf[0] == 0x10348);
}
SECTION("UTF32FromUTF8 Invalid Trail byte in lead position") {
const char s[] = "a\xB5yz";
unsigned int tbuf[4] = {};
const size_t tlen = UTF32FromUTF8(s, tbuf, 4);
REQUIRE(tlen == 4U);
REQUIRE(tbuf[0] == static_cast<unsigned int>('a'));
REQUIRE(tbuf[1] == 0xB5);
REQUIRE(tbuf[2] == static_cast<unsigned int>('y'));
REQUIRE(tbuf[3] == static_cast<unsigned int>('z'));
}
SECTION("UTF32FromUTF8 Invalid Lead byte at end") {
const char s[] = "a\xC2";
unsigned int tbuf[2] = {};
const size_t tlen = UTF32FromUTF8(s, tbuf, 2);
REQUIRE(tlen == 2U);
REQUIRE(tbuf[0] == static_cast<unsigned int>('a'));
REQUIRE(tbuf[1] == 0xC2);
}
SECTION("UTF32FromUTF8 Invalid Lead byte implies 3 trails but only 2") {
const char *s = "a\xF1yz";
unsigned int tbuf[4] = {};
const size_t tlen = UTF32FromUTF8(s, tbuf, 4);
REQUIRE(tlen == 2U);
REQUIRE(tbuf[0] == static_cast<unsigned int>('a'));
REQUIRE(tbuf[1] == 0xF1);
}
}
namespace {
// Simple adapter to avoid casting
int UTFClass(std::string_view sv) noexcept {
return UTF8Classify(sv);
}
}
TEST_CASE("UTF8Classify") {
// These tests are supposed to hit every return statement in UTF8Classify in order
// with some hit multiple times.
// Single byte
SECTION("UTF8Classify Simple ASCII") {
REQUIRE(UTFClass("a") == 1);
}
SECTION("UTF8Classify Invalid Too large lead") {
REQUIRE(UTFClass("\xF5") == (1|UTF8MaskInvalid));
}
SECTION("UTF8Classify Overlong") {
REQUIRE(UTFClass("\xC0\x80") == (1 | UTF8MaskInvalid));
}
SECTION("UTF8Classify single trail byte") {
REQUIRE(UTFClass("\x80") == (1 | UTF8MaskInvalid));
}
// Invalid length tests
SECTION("UTF8Classify 2 byte lead, string less than 2 long") {
REQUIRE(UTFClass("\xD0") == (1 | UTF8MaskInvalid));
}
SECTION("UTF8Classify 3 byte lead, string less than 3 long") {
REQUIRE(UTFClass("\xEF") == (1 | UTF8MaskInvalid));
}
SECTION("UTF8Classify 4 byte lead, string less than 4 long") {
REQUIRE(UTFClass("\xF0") == (1 | UTF8MaskInvalid));
}
// Invalid first trail byte tests
SECTION("UTF8Classify 2 byte lead trail is invalid") {
REQUIRE(UTFClass("\xD0q") == (1 | UTF8MaskInvalid));
}
SECTION("UTF8Classify 3 byte lead invalid trails") {
REQUIRE(UTFClass("\xE2qq") == (1 | UTF8MaskInvalid));
}
SECTION("UTF8Classify 4 byte bad trails") {
REQUIRE(UTFClass("\xF0xyz") == (1 | UTF8MaskInvalid));
}
// 2 byte lead
SECTION("UTF8Classify 2 byte valid character") {
REQUIRE(UTFClass("\xD0\x80") == 2);
}
// 3 byte lead
SECTION("UTF8Classify 3 byte lead, overlong") {
REQUIRE(UTFClass("\xE0\x80\xAF") == (1 | UTF8MaskInvalid));
}
SECTION("UTF8Classify 3 byte lead, surrogate") {
REQUIRE(UTFClass("\xED\xA0\x80") == (1 | UTF8MaskInvalid));
}
SECTION("UTF8Classify FFFE non-character") {
REQUIRE(UTFClass("\xEF\xBF\xBE") == (3 | UTF8MaskInvalid));
}
SECTION("UTF8Classify FFFF non-character") {
REQUIRE(UTFClass("\xEF\xBF\xBF") == (3 | UTF8MaskInvalid));
}
SECTION("UTF8Classify FDD0 non-character") {
REQUIRE(UTFClass("\xEF\xB7\x90") == (3 | UTF8MaskInvalid));
}
SECTION("UTF8Classify 3 byte valid character") {
REQUIRE(UTFClass("\xE2\x82\xAC") == 3);
}
// 4 byte lead
SECTION("UTF8Classify 1FFFF non-character") {
REQUIRE(UTFClass("\xF0\x9F\xBF\xBF") == (4 | UTF8MaskInvalid));
}
SECTION("UTF8Classify 1 Greater than max Unicode 110000") {
// Maximum Unicode value is 10FFFF so 110000 is out of range
REQUIRE(UTFClass("\xF4\x90\x80\x80") == (1 | UTF8MaskInvalid));
}
SECTION("UTF8Classify 4 byte overlong") {
REQUIRE(UTFClass("\xF0\x80\x80\x80") == (1 | UTF8MaskInvalid));
}
SECTION("UTF8Classify 4 byte valid character") {
REQUIRE(UTFClass("\xF0\x9F\x8C\x90") == 4);
}
// Invalid 2nd or 3rd continuation bytes
SECTION("UTF8Classify 3 byte lead invalid 2nd trail") {
REQUIRE(UTFClass("\xE2\x82q") == (1 | UTF8MaskInvalid));
}
SECTION("UTF8Classify 4 byte lead invalid 2nd trail") {
REQUIRE(UTFClass("\xF0\x9Fq\x9F") == (1 | UTF8MaskInvalid));
}
SECTION("UTF8Classify 4 byte lead invalid 3rd trail") {
REQUIRE(UTFClass("\xF0\x9F\x9Fq") == (1 | UTF8MaskInvalid));
}
}

View File

@ -0,0 +1,75 @@
/** @file unitTest.cxx
** Unit Tests for Scintilla internal data structures
**/
/*
Currently tested:
SplitVector
Partitioning
RunStyles
ContractionState
CharClassify
Decoration
DecorationList
CellBuffer
UniConversion
To do:
PerLine *
Range
StyledText
CaseFolder ...
Document
RESearch
Selection
Style
lexlib:
Accessor
LexAccessor
CharacterSet
OptionSet
PropSetSimple
StyleContext
*/
#include <cstdio>
#include <cstdarg>
#include <string_view>
#include <vector>
#include <optional>
#include <memory>
#include "Debugging.h"
#if defined(__GNUC__)
// Want to avoid misleading indentation warnings in catch.hpp but the pragma
// may not be available so protect by turning off pragma warnings
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#pragma GCC diagnostic ignored "-Wpragmas"
#if !defined(__clang__)
#pragma GCC diagnostic ignored "-Wmisleading-indentation"
#endif
#endif
#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
#include "catch.hpp"
using namespace Scintilla::Internal;
// Needed for PLATFORM_ASSERT in code being tested
void Platform::Assert(const char *c, const char *file, int line) noexcept {
fprintf(stderr, "Assertion [%s] failed at %s %d\n", c, file, line);
abort();
}
void Platform::DebugPrintf(const char *format, ...) noexcept {
char buffer[2000];
va_list pArguments;
va_start(pArguments, format);
vsnprintf(buffer, std::size(buffer), format, pArguments);
va_end(pArguments);
fprintf(stderr, "%s", buffer);
}

View File

@ -0,0 +1,175 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Requires Python 2.7 or later
# These are tests that run only on Win32 as they use Win32 SendMessage call
# to send WM_* messages to Scintilla that are not implemented on other platforms.
# These help Scintilla behave like a Win32 text control and can help screen readers,
# for example.
from __future__ import with_statement
from __future__ import unicode_literals
import ctypes, unittest
from MessageNumbers import msgs
user32 = ctypes.windll.user32
import XiteWin as Xite
class TestWins(unittest.TestCase):
def setUp(self):
self.xite = Xite.xiteFrame
self.ed = self.xite.ed
self.sciHwnd = self.xite.sciHwnd
self.ed.ClearAll()
self.ed.EmptyUndoBuffer()
self.ed.SetCodePage(0)
self.ed.SetStatus(0)
# Helper methods
def Send(self, msg, wp, lp):
return user32.SendMessageW(self.sciHwnd, msgs[msg], wp, lp)
def GetTextLength(self):
return self.Send("WM_GETTEXTLENGTH", 0, 0)
def GetText(self, n, s):
# n = The maximum number of characters to be copied, including the terminating null character.
# returns the number of characters copied, not including the terminating null character
return self.Send("WM_GETTEXT", n, s)
def TextValue(self):
self.assertEqual(self.ed.GetStatus(), 0)
lenValue = self.GetTextLength()
lenValueWithNUL = lenValue + 1
value = ctypes.create_unicode_buffer(lenValueWithNUL)
lenData = self.GetText(lenValueWithNUL, value)
self.assertEqual(self.ed.GetStatus(), 0)
self.assertEqual(lenData, lenValue)
return value.value
def SetText(self, s):
return self.Send("WM_SETTEXT", 0, s)
# Tests
def testSetText(self):
self.SetText(b"ab")
self.assertEqual(self.ed.Length, 2)
def testGetTextLength(self):
self.SetText(b"ab")
self.assertEqual(self.GetTextLength(), 2)
def testGetText(self):
self.SetText(b"ab")
data = ctypes.create_unicode_buffer(100)
lenData = self.GetText(100, data)
self.assertEqual(lenData, 2)
self.assertEqual(len(data.value), 2)
self.assertEqual(data.value, "ab")
def testGetUTF8Text(self):
self.ed.SetCodePage(65001)
t = "å"
tu8 = t.encode("UTF-8")
self.SetText(tu8)
value = self.TextValue()
self.assertEqual(value, t)
def testGetBadUTF8Text(self):
self.ed.SetCodePage(65001)
tu8 = b't\xc2'
t = "t\xc2"
self.SetText(tu8)
value = self.TextValue()
self.assertEqual(len(value), 2)
self.assertEqual(value, t)
def testGetJISText(self):
self.ed.SetCodePage(932)
t = "\N{HIRAGANA LETTER KA}"
tu8 = t.encode("shift-jis")
self.SetText(tu8)
value = self.TextValue()
self.assertEqual(len(value), 1)
self.assertEqual(value, t)
def testGetBadJISText(self):
self.ed.SetCodePage(932)
# This is invalid Shift-JIS, surrounded by []
tu8 = b'[\x85\xff]'
# Win32 uses Katakana Middle Dot to indicate some invalid Shift-JIS text
# At other times \uF8F3 is used which is a private use area character
# See https://unicodebook.readthedocs.io/operating_systems.html
katakanaMiddleDot = '[\N{KATAKANA MIDDLE DOT}]'
privateBad = '[\uf8f3]'
self.SetText(tu8)
value = self.TextValue()
self.assertEqual(len(value), 3)
self.assertEqual(value, katakanaMiddleDot)
# This is even less valid Shift-JIS
tu8 = b'[\xff]'
self.SetText(tu8)
value = self.TextValue()
self.assertEqual(len(value), 3)
self.assertEqual(value, privateBad)
def testGetTextLong(self):
self.assertEqual(self.ed.GetStatus(), 0)
self.SetText(b"ab")
data = ctypes.create_unicode_buffer(100)
lenData = self.GetText(4, data)
self.assertEqual(self.ed.GetStatus(), 0)
self.assertEqual(lenData, 2)
self.assertEqual(data.value, "ab")
def testGetTextLongNonASCII(self):
# With 1 multibyte character in document ask for 4 and ensure 1 character
# returned correctly.
self.ed.SetCodePage(65001)
t = "å"
tu8 = t.encode("UTF-8")
self.SetText(tu8)
data = ctypes.create_unicode_buffer(100)
lenData = self.GetText(4, data)
self.assertEqual(self.ed.GetStatus(), 0)
self.assertEqual(lenData, 1)
self.assertEqual(data.value, t)
def testGetTextShort(self):
self.assertEqual(self.ed.GetStatus(), 0)
self.SetText(b"ab")
data = ctypes.create_unicode_buffer(100)
lenData = self.GetText(2, data)
self.assertEqual(self.ed.GetStatus(), 0)
self.assertEqual(lenData, 1)
self.assertEqual(data.value, "a")
def testGetTextJustNUL(self):
self.assertEqual(self.ed.GetStatus(), 0)
self.SetText(b"ab")
data = ctypes.create_unicode_buffer(100)
lenData = self.GetText(1, data)
self.assertEqual(self.ed.GetStatus(), 0)
#~ print(data)
self.assertEqual(lenData, 0)
self.assertEqual(data.value, "")
def testGetTextZeroLength(self):
self.assertEqual(self.ed.GetStatus(), 0)
self.SetText(b"ab")
data = ctypes.create_unicode_buffer(100)
lenData = self.GetText(0, data)
self.assertEqual(self.ed.GetStatus(), 0)
#~ print(data)
self.assertEqual(lenData, 0)
self.assertEqual(data.value, "")
if __name__ == '__main__':
uu = Xite.main("win32Tests")

View File

@ -0,0 +1,8 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Requires Python 3.6 or later
import XiteWin
if __name__ == "__main__":
XiteWin.main("")