From 60873cce1237d6f1e766c7f16983ed382e8cd5bf Mon Sep 17 00:00:00 2001 From: Ekopalypse Date: Sat, 1 Feb 2025 23:34:38 +0100 Subject: [PATCH 1/4] add new Notepad message NPPM_ADDSCNMODIFIEDFLAGS --- NppPlugin/include/Notepad_plus_msgs.h | 28 +++++++++++++++++++ .../tests/test_NotepadCallbackTestCase.py | 21 +++++++++++++- PythonScript/src/NotepadPlusWrapper.cpp | 8 ++++++ PythonScript/src/NotepadPlusWrapper.h | 3 ++ PythonScript/src/NotepadPython.cpp | 1 + docs/source/notepad.rst | 6 ++++ 6 files changed, 66 insertions(+), 1 deletion(-) diff --git a/NppPlugin/include/Notepad_plus_msgs.h b/NppPlugin/include/Notepad_plus_msgs.h index ee1e6a33..6aa3cd00 100644 --- a/NppPlugin/include/Notepad_plus_msgs.h +++ b/NppPlugin/include/Notepad_plus_msgs.h @@ -990,6 +990,34 @@ enum Platform { PF_UNKNOWN, PF_X86, PF_X64, PF_IA64, PF_ARM64 }; // lParam[out]: language file name string receives all copied native language file name string // Return the number of char copied/to copy + #define NPPM_ADDSCNMODIFIEDFLAGS (NPPMSG + 117) + // BOOL NPPM_ADDSCNMODIFIEDFLAGS(0, unsigned long scnMotifiedFlags2Add) + // Add the necessary SCN_MODIFIED flags so that your plugin will receive the SCN_MODIFIED notification for these events, enabling your specific treatments. + // By default, Notepad++ only forwards SCN_MODIFIED with the following 5 flags/events: + // SC_MOD_DELETETEXT | SC_MOD_INSERTTEXT | SC_PERFORMED_UNDO | SC_PERFORMED_REDO | SC_MOD_CHANGEINDICATOR to plugins. + // If your plugin needs to process other SCN_MODIFIED events, you should add the required flags by sending this message to Notepad++. You can send it immediately after receiving NPPN_READY, + // or only when your plugin needs to listen to specific events (to avoid penalizing Notepad++'s performance). Just ensure that the message is sent only once. + // wParam: 0 (not used) + // lParam[in]: scnMotifiedFlags2Add - Scintilla SCN_MODIFIED flags to add. + // Return TRUE + // + // Example: + // + // extern "C" __declspec(dllexport) void beNotified(SCNotification* notifyCode) + // { + // switch (notifyCode->nmhdr.code) + // { + // case NPPN_READY: + // { + // // Add SC_MOD_BEFOREDELETE and SC_MOD_BEFOREINSERT to listen to the 2 events of SCN_MODIFIED + // ::SendMessage(nppData._nppHandle, NPPM_ADDSCNMODIFIEDFLAGS, 0, SC_MOD_BEFOREDELETE | SC_MOD_BEFOREINSERT); + // } + // break; + // ... + // } + // ... + // } + // For RUNCOMMAND_USER #define VAR_NOT_RECOGNIZED 0 #define FULL_CURRENT_PATH 1 diff --git a/PythonScript/python_tests/tests/test_NotepadCallbackTestCase.py b/PythonScript/python_tests/tests/test_NotepadCallbackTestCase.py index 7aec63c3..05a9b679 100644 --- a/PythonScript/python_tests/tests/test_NotepadCallbackTestCase.py +++ b/PythonScript/python_tests/tests/test_NotepadCallbackTestCase.py @@ -3,7 +3,7 @@ import time import tempfile import os -from Npp import notepad, editor, NOTIFICATION, LANGTYPE, SCINTILLANOTIFICATION +from Npp import notepad, editor, NOTIFICATION, LANGTYPE, SCINTILLANOTIFICATION, MODIFICATIONFLAGS globalCallbackCalled = False @@ -188,5 +188,24 @@ def callback_with_disallowed_sync_method(self, args): notepad.activateBufferID(self.oldBufferID) self.callbackCalled = True + def callback_editor_before_modified(self, args): + flags = args["modificationType"] + self.assertTrue((flags & MODIFICATIONFLAGS.BEFOREDELETE != 0) or (flags & MODIFICATIONFLAGS.BEFOREINSERT != 0)) + self.callbackCalled = True + + def test_add_modification_flags(self): + original_mask = editor.getModEventMask() + self.assertTrue(notepad.addModificationFlags(MODIFICATIONFLAGS.BEFOREDELETE | MODIFICATIONFLAGS.BEFOREINSERT)) + editor.setModEventMask(MODIFICATIONFLAGS.BEFOREDELETE | MODIFICATIONFLAGS.BEFOREINSERT) + editor.callback(lambda a: self.callback_editor_before_modified(a), [SCINTILLANOTIFICATION.MODIFIED]) + editor.write('A') + self.poll_for_callback() + self.assertTrue(self.callbackCalled, "BEFOREINSERT test failed, callback not called") + self.callbackCalled = False + editor.undo() + self.poll_for_callback() + self.assertTrue(self.callbackCalled, "BEFOREDELETE test failed, callback not called") + editor.setModEventMask(original_mask) + suite = unittest.TestLoader().loadTestsFromTestCase(NotepadCallbackTestCase) diff --git a/PythonScript/src/NotepadPlusWrapper.cpp b/PythonScript/src/NotepadPlusWrapper.cpp index 7488b756..0299e122 100644 --- a/PythonScript/src/NotepadPlusWrapper.cpp +++ b/PythonScript/src/NotepadPlusWrapper.cpp @@ -14,6 +14,7 @@ #include "MainThread.h" #include "ScintillaCallbackCounter.h" #include "InvalidValueProvidedException.h" +#include "Enums.h" namespace NppPythonScript { @@ -1124,6 +1125,13 @@ bool NotepadPlusWrapper::isAutoIndention() { return static_cast(callNotepad(NPPM_ISAUTOINDENTON)); } +bool NotepadPlusWrapper::addModificationFlags(uint32_t flags) { + if ((flags & ~PYSCR_SC_MODEVENTMASKALL) != 0) { + throw InvalidValueProvidedException("Provided flags value is not a valid MODIFICATION enum value."); + } + return static_cast(callNotepad(NPPM_ADDSCNMODIFIEDFLAGS, 0, static_cast(flags))); +} + bool NotepadPlusWrapper::isSingleView() const { HWND splitter_hwnd = FindWindowEx(m_nppHandle, NULL, L"splitterContainer", NULL); diff --git a/PythonScript/src/NotepadPlusWrapper.h b/PythonScript/src/NotepadPlusWrapper.h index 6e86ecb1..b867cc2e 100644 --- a/PythonScript/src/NotepadPlusWrapper.h +++ b/PythonScript/src/NotepadPlusWrapper.h @@ -10,6 +10,7 @@ #endif #include "GILManager.h" +#include "Enums.h" struct SCNotification; namespace NppPythonScript @@ -869,6 +870,8 @@ class NotepadPlusWrapper boost::python::object NotepadPlusWrapper::getExternalLexerAutoIndentMode(const char* externalLexerName); bool NotepadPlusWrapper::setExternalLexerAutoIndentMode(const char* externalLexerName, AutoIndentMode indentMode); bool NotepadPlusWrapper::isAutoIndention(); + + bool NotepadPlusWrapper::addModificationFlags(uint32_t flags); bool isSingleView() const; void flashWindow(UINT count, DWORD timeout) const; diff --git a/PythonScript/src/NotepadPython.cpp b/PythonScript/src/NotepadPython.cpp index 55e7a5ac..16f08e93 100644 --- a/PythonScript/src/NotepadPython.cpp +++ b/PythonScript/src/NotepadPython.cpp @@ -134,6 +134,7 @@ void export_notepad() .def("getExternalLexerAutoIndentMode", &NotepadPlusWrapper::getExternalLexerAutoIndentMode, boost::python::args("externalLexerName"), "Get ExternalLexerAutoIndentMode for an installed external programming language.") .def("setExternalLexerAutoIndentMode", &NotepadPlusWrapper::setExternalLexerAutoIndentMode, boost::python::args("externalLexerName", "indentMode"), "Set ExternalLexerAutoIndentMode for an installed external programming language.") .def("isAutoIndention", &NotepadPlusWrapper::isAutoIndention, "Returns True if autoindention is enabled else False") + .def("addModificationFlags", &NotepadPlusWrapper::addModificationFlags, boost::python::args("flags"), "Returns True if successful, else False") .def("isSingleView", &NotepadPlusWrapper::isSingleView, "True if only one view is used, False otherwise") .def("flashWindow", &NotepadPlusWrapper::flashWindow, boost::python::args("count", "milliseconds"), "Flashes notepad++ for the given count and timeout"); diff --git a/docs/source/notepad.rst b/docs/source/notepad.rst index 09f687ea..ec5fa13c 100644 --- a/docs/source/notepad.rst +++ b/docs/source/notepad.rst @@ -29,6 +29,12 @@ Notepad++ Object *view* is 0 or 1. +.. method:: notepad.addModificationFlags(flags) + + A bitwise OR combination of values from the :class:`MODIFICATIONFLAGS` enum. + + + .. method:: notepad.allocateCmdID(numberRequested) -> int Allocates a range of Command ID for use in WM_COMMAND. Mainly used internally by plugins. From 3541bdf9c178ff7ab363aedaefcb1c45e889cae8 Mon Sep 17 00:00:00 2001 From: Ekopalypse Date: Sun, 2 Feb 2025 10:02:54 +0100 Subject: [PATCH 2/4] remove unneeded include Enums.h from NotepadPlusWrapper.h --- PythonScript/src/NotepadPlusWrapper.h | 1 - 1 file changed, 1 deletion(-) diff --git a/PythonScript/src/NotepadPlusWrapper.h b/PythonScript/src/NotepadPlusWrapper.h index b867cc2e..4a072b81 100644 --- a/PythonScript/src/NotepadPlusWrapper.h +++ b/PythonScript/src/NotepadPlusWrapper.h @@ -10,7 +10,6 @@ #endif #include "GILManager.h" -#include "Enums.h" struct SCNotification; namespace NppPythonScript From 7e87af14fdac692af29a0f833b0ef9369b599ce7 Mon Sep 17 00:00:00 2001 From: Ekopalypse Date: Sun, 2 Feb 2025 10:48:30 +0100 Subject: [PATCH 3/4] Add an add_modification_flag test to test invalid values --- .../python_tests/tests/test_NotepadCallbackTestCase.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/PythonScript/python_tests/tests/test_NotepadCallbackTestCase.py b/PythonScript/python_tests/tests/test_NotepadCallbackTestCase.py index 05a9b679..3abd11f3 100644 --- a/PythonScript/python_tests/tests/test_NotepadCallbackTestCase.py +++ b/PythonScript/python_tests/tests/test_NotepadCallbackTestCase.py @@ -207,5 +207,10 @@ def test_add_modification_flags(self): self.assertTrue(self.callbackCalled, "BEFOREDELETE test failed, callback not called") editor.setModEventMask(original_mask) + def test_add_modification_flags_invalid(self): + with self.assertRaises(RuntimeError): + notepad.addModificationFlags(0x800000) + + suite = unittest.TestLoader().loadTestsFromTestCase(NotepadCallbackTestCase) From a768fd5e24684a06b09ee43664290cd5be7a2d0a Mon Sep 17 00:00:00 2001 From: Ekopalypse Date: Sun, 2 Feb 2025 12:18:33 +0100 Subject: [PATCH 4/4] Fix test_scintillawrapper_int_int_stringresult_in_callback_getCurLine is calling wrong function --- .../python_tests/tests/test_ScintillaWrapperTestCase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PythonScript/python_tests/tests/test_ScintillaWrapperTestCase.py b/PythonScript/python_tests/tests/test_ScintillaWrapperTestCase.py index f3de7595..3441d10c 100644 --- a/PythonScript/python_tests/tests/test_ScintillaWrapperTestCase.py +++ b/PythonScript/python_tests/tests/test_ScintillaWrapperTestCase.py @@ -664,7 +664,7 @@ def callback_scintillawrapper_int_int_stringresult_getCurLine(self, args): self.callbackCalled = True def test_scintillawrapper_int_int_stringresult_in_callback_getCurLine(self): - editor.callback(lambda args: self.callback_scintillawrapper_int_int_stringresult_getLine(args), [SCINTILLANOTIFICATION.MODIFIED]) + editor.callback(lambda args: self.callback_scintillawrapper_int_int_stringresult_getCurLine(args), [SCINTILLANOTIFICATION.MODIFIED]) editor.write("One\r\nTwo\r\nThree") self.poll_for_callback() self.assertEqual(self.callbackCalled, True)