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++ ){