diff --git a/sakura/python.props b/sakura/python.props
new file mode 100644
index 0000000000..fbb8a21f66
--- /dev/null
+++ b/sakura/python.props
@@ -0,0 +1,22 @@
+
+
+
+
+ C:\Python38
+
+
+ $(PYTHON_DIR)\include;$(IncludePath)
+ $(PYTHON_DIR)\libs;$(LibraryPath)
+
+
+
+
+ python38.dll;%(DelayLoadDLLs)
+
+
+
+
+ $(PYTHON_DIR)
+
+
+
\ No newline at end of file
diff --git a/sakura/sakura.vcxproj b/sakura/sakura.vcxproj
index 4a962cbf47..f65530b9cd 100644
--- a/sakura/sakura.vcxproj
+++ b/sakura/sakura.vcxproj
@@ -52,15 +52,19 @@
+
+
+
+
@@ -440,6 +444,7 @@
+
@@ -805,6 +810,7 @@
+
diff --git a/sakura/sakura.vcxproj.filters b/sakura/sakura.vcxproj.filters
index 205b9f2b03..ffc74d3b56 100644
--- a/sakura/sakura.vcxproj.filters
+++ b/sakura/sakura.vcxproj.filters
@@ -1097,6 +1097,9 @@
Cpp Source Files
+
+ Cpp Source Files\macro
+
@@ -2276,6 +2279,9 @@
Cpp Source Files
+
+ Cpp Source Files\macro
+
diff --git a/sakura_core/Makefile b/sakura_core/Makefile
index a2a250e456..4f348fd326 100644
--- a/sakura_core/Makefile
+++ b/sakura_core/Makefile
@@ -254,6 +254,7 @@ macro/CMacroFactory.o \
macro/CMacroManagerBase.o \
macro/CPPA.o \
macro/CPPAMacroMgr.o \
+macro/CPythonMacroManager.o \
macro/CSMacroMgr.o \
macro/CWSH.o \
macro/CWSHIfObj.o \
diff --git a/sakura_core/dlg/CDlgOpenFile_CommonFileDialog.cpp b/sakura_core/dlg/CDlgOpenFile_CommonFileDialog.cpp
index 3566df0cca..7e091185dc 100644
--- a/sakura_core/dlg/CDlgOpenFile_CommonFileDialog.cpp
+++ b/sakura_core/dlg/CDlgOpenFile_CommonFileDialog.cpp
@@ -735,10 +735,11 @@ bool CDlgOpenFile_CommonFileDialog::DoModal_GetOpenFileName( WCHAR* pszPath, EFi
cFileExt.AppendExtRaw( LS(STR_DLGOPNFL_EXTNAME2), L"*.txt" );
break;
case EFITER_MACRO:
- cFileExt.AppendExtRaw( L"Macros", L"*.js;*.vbs;*.ppa;*.mac" );
+ cFileExt.AppendExtRaw( L"Macros", L"*.js;*.vbs;*.ppa;*.py;*.mac" );
cFileExt.AppendExtRaw( L"JScript", L"*.js" );
cFileExt.AppendExtRaw( L"VBScript", L"*.vbs" );
cFileExt.AppendExtRaw( L"Pascal", L"*.ppa" );
+ cFileExt.AppendExtRaw( L"Python", L"*.py" );
cFileExt.AppendExtRaw( L"Key Macro", L"*.mac" );
break;
case EFITER_NONE:
diff --git a/sakura_core/dlg/CDlgOpenFile_CommonItemDialog.cpp b/sakura_core/dlg/CDlgOpenFile_CommonItemDialog.cpp
index 21f67bbf1a..3eebdb01c1 100644
--- a/sakura_core/dlg/CDlgOpenFile_CommonItemDialog.cpp
+++ b/sakura_core/dlg/CDlgOpenFile_CommonItemDialog.cpp
@@ -472,10 +472,11 @@ bool CDlgOpenFile_CommonItemDialog::DoModal_GetOpenFileName( WCHAR* pszPath, EFi
specs.push_back(COMDLG_FILTERSPEC{strs.back().c_str(), L"*.txt"});
break;
case EFITER_MACRO:
- specs.push_back(COMDLG_FILTERSPEC{L"Macros", L"*.js;*.vbs;*.ppa;*.mac"});
+ specs.push_back(COMDLG_FILTERSPEC{L"Macros", L"*.js;*.vbs;*.ppa;*.py;*.mac"});
specs.push_back(COMDLG_FILTERSPEC{L"JScript", L"*.js"});
specs.push_back(COMDLG_FILTERSPEC{L"VBScript", L"*.vbs"});
specs.push_back(COMDLG_FILTERSPEC{L"Pascal", L"*.ppa"});
+ specs.push_back(COMDLG_FILTERSPEC{L"Python", L"*.py"});
specs.push_back(COMDLG_FILTERSPEC{L"Key Macro", L"*.mac"});
break;
case EFITER_NONE:
diff --git a/sakura_core/macro/CPythonMacroManager.cpp b/sakura_core/macro/CPythonMacroManager.cpp
new file mode 100644
index 0000000000..2cdf428814
--- /dev/null
+++ b/sakura_core/macro/CPythonMacroManager.cpp
@@ -0,0 +1,354 @@
+/*! @file
+ @brief Python Macro Manager
+*/
+/*
+ Copyright (C) 2018-2020 Sakura Editor Organization
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented;
+ you must not claim that you wrote the original software.
+ If you use this software in a product, an acknowledgment
+ in the product documentation would be appreciated but is
+ not required.
+
+ 2. Altered source versions must be plainly marked as such,
+ and must not be misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source
+ distribution.
+*/
+
+#include "StdAfx.h"
+#include
+#include
+#include "CPythonMacroManager.h"
+
+#include "macro/CSMacroMgr.h"
+#include "macro/CMacroFactory.h"
+#include "_os/OleTypes.h"
+#include "CMacro.h"
+
+#ifdef _DEBUG
+#undef _DEBUG
+#include
+#define _DEBUG
+#else
+#include
+#endif
+
+namespace {
+
+PyMethodDef g_moduleMethods[] = {
+ {NULL, NULL, 0, NULL}
+};
+
+PyModuleDef g_moduleDef = {
+ PyModuleDef_HEAD_INIT,
+ "SakuraEditor", // name of module
+ "", // module documentation, may be NULL
+ -1, // size of per-interpreter state of the module, or -1 if the module keeps state in global variables.
+ g_moduleMethods,
+};
+
+PyMODINIT_FUNC
+PyInit_SakuraEditor(void)
+{
+ auto module = PyModule_Create(&g_moduleDef);
+ return module;
+}
+
+std::vector g_commandNames;
+std::vector g_commandDescs;
+std::vector g_functionNames;
+std::vector g_functionDescs;
+CEditView* g_pEditView;
+
+std::wstring utf8_to_utf16le(const char* src)
+{
+ int newLen = MultiByteToWideChar(CP_UTF8, 0, src, -1, NULL, 0);
+ std::wstring ret;
+ ret.resize(newLen + 1);
+ MultiByteToWideChar(CP_UTF8, 0, src, -1, &ret[0], newLen);
+ return ret;
+}
+
+PyObject* handleCommand(PyObject* self, PyObject* args)
+{
+ const PyMethodDef* pDesc = (const PyMethodDef*) PyCapsule_GetPointer(self, nullptr);
+ ptrdiff_t idx = pDesc - &g_commandDescs[0];
+ assert(idx >= 0);
+ assert(idx < g_commandNames.size());
+ const MacroFuncInfo* info = &CSMacroMgr::m_MacroFuncInfoCommandArr[idx];
+
+ size_t nArgs = PyTuple_GET_SIZE(args);
+ std::vector strArguments(nArgs);
+ for (size_t i=0; im_varArguments[i];
+ }else {
+ if (!info->m_pData || info->m_pData->m_nArgMaxSize >= i) {
+ varType = VT_EMPTY;
+ }else {
+ varType = info->m_pData->m_pVarArgEx[i - 4];
+ }
+ }
+ if (varType == VT_EMPTY) {
+ PyErr_BadArgument();
+ return NULL;
+ }
+
+ if (varType == VT_BSTR) {
+ wchar_t* str = PyUnicode_AsWideCharString(arg, NULL);
+ strArguments[i] = str;
+ PyMem_Free(str);
+ }else if (varType == VT_I4) {
+ if (PyLong_Check(arg)) {
+ long value = PyLong_AsLong(arg);
+ strArguments[i] = std::to_wstring(value);
+ }else {
+ PyErr_BadArgument();
+ return NULL;
+ }
+ }else {
+ assert(false);
+ }
+ }
+
+ const wchar_t* arguments[8]{};
+ int argLengths[8]{};
+ for (size_t i=0; im_nFuncID, arguments, argLengths, nArgs);
+
+ PyObject* none = Py_BuildValue("");
+ Py_INCREF(none);
+ return none;
+}
+
+PyObject* handleFunction(PyObject* self, PyObject* args)
+{
+ const PyMethodDef* pDesc = (const PyMethodDef*) PyCapsule_GetPointer(self, nullptr);
+ ptrdiff_t idx = pDesc - &g_functionDescs[0];
+ assert(idx >= 0);
+ assert(idx < g_functionNames.size());
+ const MacroFuncInfo* info = &CSMacroMgr::m_MacroFuncInfoArr[idx];
+
+ VARIANT vtArgs[8];
+ size_t nArgs = PyTuple_GET_SIZE(args);
+ size_t i;
+ for (i=0; im_varArguments[i];
+ }else {
+ if (!info->m_pData || info->m_pData->m_nArgMaxSize >= i) {
+ varType = VT_EMPTY;
+ }else {
+ varType = info->m_pData->m_pVarArgEx[i - 4];
+ }
+ }
+ if (varType == VT_EMPTY) {
+ PyErr_BadArgument();
+ break;
+ }
+
+ ::VariantInit(&vtArgs[i]);
+ if (varType == VT_BSTR) {
+ const char* str = PyUnicode_AsUTF8(arg);
+ SysString S(str, strlen(str));
+ Wrap(&vtArgs[i])->Receive(S);
+ }else if (varType == VT_I4) {
+ vtArgs[i].vt = VT_I4;
+ vtArgs[i].lVal = PyLong_AsLong(arg);
+ }else {
+ assert(false);
+ }
+ }
+
+ PyObject* retObj = NULL;
+ if (i == nArgs) {
+ VARIANT vtResult;
+ ::VariantInit(&vtResult);
+ bool ret = CMacro::HandleFunction(g_pEditView, (EFunctionCode)info->m_nFuncID, vtArgs, nArgs, vtResult);
+ std::wstring str;
+ switch (vtResult.vt) {
+ case VT_I4:
+ retObj = PyLong_FromLong(vtResult.lVal);
+ break;
+ case VT_BSTR:
+ Wrap(&vtResult.bstrVal)->GetW(&str);
+ retObj = PyUnicode_FromWideChar(str.c_str(), str.size());
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ ::VariantClear(&vtResult);
+ }
+ for (size_t j = 0; j < i; ++j) {
+ ::VariantClear(&vtArgs[j]);
+ }
+
+ return retObj;
+}
+
+} // namespace
+
+CPythonMacroManager::CPythonMacroManager()
+{
+ static bool s_initialized = false;
+ if (s_initialized)
+ return;
+
+ for (const MacroFuncInfo* info = &CSMacroMgr::m_MacroFuncInfoCommandArr[0]; info->m_nFuncID != F_INVALID; ++info) {
+ g_commandNames.push_back(to_achar(info->m_pszFuncName));
+ }
+ for (const MacroFuncInfo* info = &CSMacroMgr::m_MacroFuncInfoArr[0]; info->m_nFuncID != F_INVALID; ++info) {
+ g_functionNames.push_back(to_achar(info->m_pszFuncName));
+ }
+ for (auto& name : g_commandNames) {
+ g_commandDescs.push_back({&name[0], (PyCFunction)handleCommand, METH_VARARGS, NULL});
+ }
+ for (auto& name : g_functionNames) {
+ g_functionDescs.push_back({&name[0], (PyCFunction)handleFunction, METH_VARARGS, NULL});
+ }
+
+ s_initialized = true;
+}
+
+// virtual
+CPythonMacroManager::~CPythonMacroManager()
+{
+}
+
+bool CPythonMacroManager::ExecKeyMacro(CEditView *EditView, int flags) const
+{
+ static HMODULE s_hModule;
+ if (!s_hModule) {
+ s_hModule = LoadLibrary(L"python38.dll");
+ if (!s_hModule) {
+ return false;
+ }
+ }
+
+ if (PyImport_AppendInittab("SakuraEditor", PyInit_SakuraEditor) == -1) {
+ fprintf(stderr, "Error: could not extend in-built modules SakuraEditor\n");
+ exit(1);
+ }
+
+ Py_InitializeEx(0);
+
+ if (!Py_IsInitialized()) {
+ return 0;
+ }
+
+ const char* version = Py_GetVersion();
+ const char* compiler = Py_GetCompiler();
+
+ PyObject* module = PyImport_ImportModule("SakuraEditor");
+ if (!module) {
+ PyErr_Print();
+ fprintf(stderr, "Error: could not import module 'SakuraEditor'\n");
+ assert(false);
+ }
+
+ for (auto& desc : g_commandDescs) {
+ int ret = PyModule_AddObject(module, desc.ml_name, PyCFunction_New(&desc, PyCapsule_New(&desc, nullptr, nullptr)));
+ }
+ for (auto& desc : g_functionDescs) {
+ int ret = PyModule_AddObject(module, desc.ml_name, PyCFunction_New(&desc, PyCapsule_New(&desc, nullptr, nullptr)));
+ }
+
+ g_pEditView = EditView;
+
+#if 0
+ auto mainModule = PyImport_AddModule("__main__");
+ auto globals = PyModule_GetDict(mainModule);
+ auto locals = PyDict_New();
+ auto result = PyRun_String(m_str.c_str(), Py_file_input, globals, locals);
+ if (result == NULL) {
+ PyObject *pType, *pValue, *pTraceback;
+ PyErr_Fetch(&pType, &pValue, &pTraceback);
+ if (pType) {
+ PyObject* pRepr = PyObject_Repr(pType);
+ const char* str = PyUnicode_AsUTF8(pRepr);
+ Py_DecRef(pRepr);
+ Py_DecRef(pType);
+ }
+ if (pValue) {
+ PyObject* pRepr = PyObject_Repr(pValue);
+ const char* str = PyUnicode_AsUTF8(pRepr);
+ Py_DecRef(pRepr);
+ Py_DecRef(pValue);
+ }
+ if (pTraceback) {
+ PyObject* pRepr = PyObject_Repr(pTraceback);
+ const char* str = PyUnicode_AsUTF8(pRepr);
+ Py_DecRef(pRepr);
+ Py_DecRef(pTraceback);
+ }
+ }
+ Py_XDECREF(result);
+ Py_XDECREF(locals);
+#else
+ PyRun_SimpleString(m_str.c_str());
+#endif
+
+ if (Py_FinalizeEx() < 0) {
+ return false;
+ }
+
+ return true;
+}
+
+BOOL CPythonMacroManager::LoadKeyMacro(HINSTANCE hInstance, const WCHAR* pszPath)
+{
+ FILE* f = _wfopen(pszPath, L"rb");
+ long sz = _filelength(_fileno(f));
+ m_str.resize(sz);
+ fread(&m_str[0], 1, sz, f);
+ fclose(f);
+ return TRUE;
+}
+
+BOOL CPythonMacroManager::LoadKeyMacroStr(HINSTANCE hInstance, const WCHAR* pszCode)
+{
+ int newLen = WideCharToMultiByte(CP_UTF8, 0, pszCode, -1, NULL, 0, NULL, NULL);
+ m_str.resize(newLen);
+ WideCharToMultiByte(CP_UTF8, 0, pszCode, -1, &m_str[0], newLen, NULL, NULL);
+ return TRUE;
+}
+
+// static
+CMacroManagerBase* CPythonMacroManager::Creator(const WCHAR* FileExt)
+{
+ if (wcscmp( FileExt, L"py" ) == 0) {
+ return new CPythonMacroManager;
+ }
+ return NULL;
+}
+
+// static
+void CPythonMacroManager::declare()
+{
+ CMacroFactory::getInstance()->RegisterCreator( Creator );
+}
+
diff --git a/sakura_core/macro/CPythonMacroManager.h b/sakura_core/macro/CPythonMacroManager.h
new file mode 100644
index 0000000000..ba52d9b9f6
--- /dev/null
+++ b/sakura_core/macro/CPythonMacroManager.h
@@ -0,0 +1,49 @@
+/*! @file
+ @brief Python Macro Manager
+*/
+/*
+ Copyright (C) 2018-2020 Sakura Editor Organization
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented;
+ you must not claim that you wrote the original software.
+ If you use this software in a product, an acknowledgment
+ in the product documentation would be appreciated but is
+ not required.
+
+ 2. Altered source versions must be plainly marked as such,
+ and must not be misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source
+ distribution.
+*/
+
+#pragma once
+
+#include
+#include
+#include "macro/CMacroManagerBase.h"
+class CEditView;
+
+class CPythonMacroManager final : public CMacroManagerBase {
+public:
+ CPythonMacroManager();
+ virtual ~CPythonMacroManager();
+
+ bool ExecKeyMacro(CEditView *EditView, int flags) const override;
+ BOOL LoadKeyMacro(HINSTANCE hInstance, const WCHAR* pszPath) override;
+ BOOL LoadKeyMacroStr(HINSTANCE hInstance, const WCHAR* pszCode) override;
+
+ static CMacroManagerBase* Creator(const WCHAR* FileExt);
+ static void declare();
+
+protected:
+ std::string m_str;
+};
diff --git a/sakura_core/macro/CSMacroMgr.cpp b/sakura_core/macro/CSMacroMgr.cpp
index 8dc1cc7d76..7b28f805e9 100644
--- a/sakura_core/macro/CSMacroMgr.cpp
+++ b/sakura_core/macro/CSMacroMgr.cpp
@@ -26,6 +26,7 @@
#include "macro/CSMacroMgr.h"
#include "macro/CPPAMacroMgr.h"
#include "macro/CWSHManager.h"
+#include "macro/CPythonMacroManager.h"
#include "macro/CMacroFactory.h"
#include "env/CShareData.h"
#include "view/CEditView.h"
@@ -496,6 +497,7 @@ CSMacroMgr::CSMacroMgr()
CPPAMacroMgr::declare();
CKeyMacroMgr::declare();
CWSHMacroManager::declare();
+ CPythonMacroManager::declare();
int i;
for ( i = 0 ; i < MAX_CUSTMACRO ; i++ ){
diff --git a/tests/unittests/tests1.vcxproj b/tests/unittests/tests1.vcxproj
index e2661f6600..bd01842c58 100644
--- a/tests/unittests/tests1.vcxproj
+++ b/tests/unittests/tests1.vcxproj
@@ -35,6 +35,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
(ProjectDir)..\..\..\build\$(Platform)\$(Configuration)\unittests\
$(Platform)\$(Configuration)\