Skip to content

Commit f0df450

Browse files
Merge pull request #99 from hybridmachine/feature/plugin-system-v1
feat: source-code-compatible plugin system (V1)
2 parents db5c29b + 29e4141 commit f0df450

16 files changed

Lines changed: 1230 additions & 4 deletions

macos/CMakeLists.txt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,8 @@ add_executable(MacNotePlusPlus
384384
"${CMAKE_CURRENT_SOURCE_DIR}/platform/print_support.mm"
385385
"${CMAKE_CURRENT_SOURCE_DIR}/platform/hash_tools.mm"
386386
"${CMAKE_CURRENT_SOURCE_DIR}/platform/macro_manager.mm"
387+
"${CMAKE_CURRENT_SOURCE_DIR}/platform/plugin_manager.mm"
388+
"${CMAKE_CURRENT_SOURCE_DIR}/platform/nppm_handler.mm"
387389
)
388390
target_include_directories(MacNotePlusPlus PRIVATE
389391
"${CMAKE_CURRENT_SOURCE_DIR}/platform"
@@ -392,6 +394,7 @@ target_include_directories(MacNotePlusPlus PRIVATE
392394
"${SCINTILLA_INCLUDE_DIR}"
393395
"${LEXILLA_INCLUDE_DIR}"
394396
"${UCHARDET_DIR}"
397+
"${NPP_SRC_DIR}/MISC/PluginsManager"
395398
)
396399
target_link_libraries(MacNotePlusPlus
397400
win32shim
@@ -410,6 +413,8 @@ target_compile_options(MacNotePlusPlus PRIVATE
410413
-fobjc-arc
411414
-Wno-deprecated-declarations
412415
)
416+
# Export symbols so plugins loaded via dlopen can resolve host functions (SendMessageW, etc.)
417+
target_link_options(MacNotePlusPlus PRIVATE -Wl,-export_dynamic)
413418
add_dependencies(MacNotePlusPlus AppIcon)
414419
add_custom_command(TARGET MacNotePlusPlus POST_BUILD
415420
COMMAND ${CMAKE_COMMAND} -E copy_if_different
@@ -421,6 +426,36 @@ add_custom_command(TARGET MacNotePlusPlus POST_BUILD
421426
COMMENT "Copying logo.png and AppIcon.icns"
422427
)
423428

429+
# ============================================================
430+
# Sample plugin: HelloMacNote
431+
# ============================================================
432+
add_library(HelloMacNote MODULE
433+
"${CMAKE_CURRENT_SOURCE_DIR}/plugin-sdk/example/HelloMacNote/HelloMacNote.cpp"
434+
)
435+
target_include_directories(HelloMacNote PRIVATE
436+
"${SHIM_INCLUDE_DIR}"
437+
"${NPP_SRC_DIR}/MISC/PluginsManager"
438+
"${SCINTILLA_INCLUDE_DIR}"
439+
)
440+
set_target_properties(HelloMacNote PROPERTIES
441+
PREFIX ""
442+
SUFFIX ".dylib"
443+
OUTPUT_NAME "HelloMacNote"
444+
)
445+
target_compile_options(HelloMacNote PRIVATE -Wno-deprecated-declarations)
446+
target_link_options(HelloMacNote PRIVATE -undefined dynamic_lookup)
447+
448+
# Opt-in install target: cmake --build . --target install_sample_plugin
449+
add_custom_target(install_sample_plugin
450+
DEPENDS HelloMacNote
451+
COMMAND ${CMAKE_COMMAND} -E make_directory
452+
"$ENV{HOME}/Library/Application Support/MacNote++/plugins/HelloMacNote"
453+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
454+
"$<TARGET_FILE:HelloMacNote>"
455+
"$ENV{HOME}/Library/Application Support/MacNote++/plugins/HelloMacNote/HelloMacNote.dylib"
456+
COMMENT "Installing HelloMacNote plugin to ~/Library/Application Support/MacNote++/plugins/"
457+
)
458+
424459
# ============================================================
425460
# Packaging: App bundle, code signing, and DMG
426461
# ============================================================

macos/platform/app_delegate.mm

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#include "scintilla_bridge.h"
2121
#include "handle_registry.h"
2222
#include "settings_manager.h"
23+
#include "plugin_manager.h"
24+
#include "Notepad_plus_msgs.h"
2325
#include "file_monitor_mac.h"
2426
#include "brace_match.h"
2527
#include "smart_highlight.h"
@@ -251,12 +253,41 @@ - (void)applicationDidFinishLaunching:(NSNotification*)notification
251253
return;
252254
}
253255

256+
// Register main Scintilla view with HandleRegistry so SendMessage(sciHandle, SCI_*, ...) works
257+
{
258+
HandleRegistry::WindowInfo sciInfo{};
259+
sciInfo.nativeView = ctx().scintillaView;
260+
sciInfo.className = L"Scintilla";
261+
sciInfo.isScintilla = true;
262+
sciInfo.parent = ctx().mainHwnd;
263+
ctx().scintillaMainHwnd = HandleRegistry::createWindow(std::move(sciInfo));
264+
}
265+
254266
configureScintilla(ctx().scintillaView);
255267
applyAppearance();
256268
if (ctx().documentMapEnabled)
257269
initializeDocumentMap();
258270
setSyncScrollingEnabled(ctx().syncScrolling);
259271

272+
// Pre-create the second Scintilla view (hidden) so plugins always have a valid handle.
273+
// doSplit() will reuse this view instead of creating a new one.
274+
{
275+
NSView* hiddenContainer = [[NSView alloc] initWithFrame:NSZeroRect];
276+
hiddenContainer.hidden = YES;
277+
[ctx().editorContainer addSubview:hiddenContainer];
278+
ctx().scintillaView2 = ScintillaBridge_createView((__bridge void*)hiddenContainer, 0, 0, 0, 0);
279+
if (ctx().scintillaView2)
280+
{
281+
HandleRegistry::WindowInfo sci2Info{};
282+
sci2Info.nativeView = ctx().scintillaView2;
283+
sci2Info.className = L"Scintilla";
284+
sci2Info.isScintilla = true;
285+
sci2Info.parent = ctx().mainHwnd;
286+
ctx().scintillaSecondHwnd = HandleRegistry::createWindow(std::move(sci2Info));
287+
configureScintilla(ctx().scintillaView2);
288+
}
289+
}
290+
260291
// Scintilla notification callback for main view
261292
ScintillaBridge_setNotifyCallback(ctx().scintillaView, (intptr_t)ctx().mainHwnd,
262293
[](intptr_t windowid, unsigned int iMessage, uintptr_t wParam, uintptr_t lParam) {
@@ -368,6 +399,9 @@ - (void)applicationDidFinishLaunching:(NSNotification*)notification
368399
if (MacroManager::instance().isRecording())
369400
MacroManager::instance().recordStep(scn->message, scn->wParam, scn->lParam);
370401
}
402+
403+
// Forward Scintilla notifications to plugins
404+
pluginManager().notify(reinterpret_cast<const SCNotification*>(scn));
371405
}
372406
});
373407

@@ -515,6 +549,25 @@ - (void)applicationDidFinishLaunching:(NSNotification*)notification
515549
bindDocumentMapToActiveView();
516550
updateDocumentMapViewport();
517551

552+
// Initialize plugin system
553+
{
554+
NppData nppData;
555+
nppData._nppHandle = ctx().mainHwnd;
556+
nppData._scintillaMainHandle = ctx().scintillaMainHwnd;
557+
nppData._scintillaSecondHandle = ctx().scintillaSecondHwnd;
558+
pluginManager().init(nppData);
559+
pluginManager().loadPlugins();
560+
pluginManager().initMenu(getPluginsMenuHandle());
561+
}
562+
563+
// Notify plugins that initialization is complete
564+
{
565+
SCNotification readyNotif{};
566+
readyNotif.nmhdr.hwndFrom = ctx().mainHwnd;
567+
readyNotif.nmhdr.code = NPPN_READY;
568+
pluginManager().notify(&readyNotif);
569+
}
570+
518571
NSLog(@"=== Notepad++ macOS Port — Phase 7 ===");
519572
NSLog(@"Settings, split view, edit commands, encoding, session, drag-and-drop!");
520573
}
@@ -590,6 +643,26 @@ - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender
590643

591644
- (void)applicationWillTerminate:(NSNotification*)notification
592645
{
646+
// Notify plugins that we are shutting down
647+
{
648+
SCNotification shutdownNotif{};
649+
shutdownNotif.nmhdr.hwndFrom = ctx().mainHwnd;
650+
shutdownNotif.nmhdr.code = NPPN_SHUTDOWN;
651+
pluginManager().notify(&shutdownNotif);
652+
}
653+
654+
// Destroy Scintilla HWNDs to release HandleRegistry-retained native views
655+
if (ctx().scintillaSecondHwnd)
656+
{
657+
HandleRegistry::destroyWindow(ctx().scintillaSecondHwnd);
658+
ctx().scintillaSecondHwnd = nullptr;
659+
}
660+
if (ctx().scintillaMainHwnd)
661+
{
662+
HandleRegistry::destroyWindow(ctx().scintillaMainHwnd);
663+
ctx().scintillaMainHwnd = nullptr;
664+
}
665+
593666
saveSession();
594667

595668
auto& s = SettingsManager::instance().settings;

macos/platform/app_state.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ struct AppContext
2424
{
2525
// Main editor
2626
void* scintillaView = nullptr;
27+
HWND scintillaMainHwnd = nullptr;
2728
NSWindow* mainWindow = nil;
2829
HWND mainHwnd = nullptr;
2930
HWND tabHwnd = nullptr;
@@ -62,6 +63,7 @@ struct AppContext
6263

6364
// Split view state
6465
void* scintillaView2 = nullptr;
66+
HWND scintillaSecondHwnd = nullptr;
6567
NSSplitView* splitView = nil;
6668
bool isSplit = false;
6769
int activeView = 0; // 0=main, 1=sub

macos/platform/document_manager.mm

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include "function_list_panel.h"
1515
#include "file_switcher_panel.h"
1616
#include "sync_scroll.h"
17+
#include "plugin_manager.h"
18+
#include "Notepad_plus_msgs.h"
1719
#include "windows.h"
1820
#include "commctrl.h"
1921
#include "handle_registry.h"
@@ -224,6 +226,15 @@ void closeTabFromView(int viewIndex, int tabIndex)
224226
return;
225227
if (!sci) return;
226228

229+
// Notify plugins that a file is about to be closed
230+
{
231+
SCNotification notif{};
232+
notif.nmhdr.hwndFrom = ctx().mainHwnd;
233+
notif.nmhdr.code = NPPN_FILEBEFORECLOSE;
234+
notif.nmhdr.idFrom = 0; // V1: no stable buffer ID yet
235+
pluginManager().notify(&notif);
236+
}
237+
227238
if (docs.size() <= 1)
228239
{
229240
if (docs[0].functionListDocumentId != 0)
@@ -248,6 +259,15 @@ void closeTabFromView(int viewIndex, int tabIndex)
248259
[ctx().mainWindow setTitle:@"Notepad++ — Untitled"];
249260
updateTabModifiedIndicator(viewIndex, 0);
250261
updateWindowDocumentEdited();
262+
263+
// Notify plugins that the file has been closed
264+
{
265+
SCNotification notif{};
266+
notif.nmhdr.hwndFrom = ctx().mainHwnd;
267+
notif.nmhdr.code = NPPN_FILECLOSED;
268+
notif.nmhdr.idFrom = 0;
269+
pluginManager().notify(&notif);
270+
}
251271
return;
252272
}
253273

@@ -281,6 +301,15 @@ void closeTabFromView(int viewIndex, int tabIndex)
281301
NSString* title = WideToNSString(doc.title.c_str());
282302
[ctx().mainWindow setTitle:[NSString stringWithFormat:@"Notepad++ — %@", title]];
283303
updateWindowDocumentEdited();
304+
305+
// Notify plugins that the file has been closed
306+
{
307+
SCNotification notif{};
308+
notif.nmhdr.hwndFrom = ctx().mainHwnd;
309+
notif.nmhdr.code = NPPN_FILECLOSED;
310+
notif.nmhdr.idFrom = 0;
311+
pluginManager().notify(&notif);
312+
}
284313
}
285314

286315
void closeTab(int tabIndex)

macos/platform/file_operations.mm

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#include "scintilla_bridge.h"
1717
#include "file_monitor_mac.h"
1818
#include "uchardet.h"
19+
#include "plugin_manager.h"
20+
#include "Notepad_plus_msgs.h"
1921
#include "windows.h"
2022
#include "commdlg.h"
2123
#include "commctrl.h"
@@ -206,6 +208,16 @@ bool openFileAtPath(NSString* path)
206208
addRecentFile(wpath);
207209
rebuildRecentMenu();
208210
updateStatusBar();
211+
212+
// Notify plugins that a file was opened
213+
{
214+
SCNotification notif{};
215+
notif.nmhdr.hwndFrom = ctx().mainHwnd;
216+
notif.nmhdr.code = NPPN_FILEOPENED;
217+
notif.nmhdr.idFrom = 0; // V1: no stable buffer ID yet
218+
pluginManager().notify(&notif);
219+
}
220+
209221
return true;
210222
}
211223

@@ -322,6 +334,15 @@ void saveCurrentFile()
322334

323335
addRecentFile(doc.filePath);
324336
rebuildRecentMenu();
337+
338+
// Notify plugins that the file was saved
339+
{
340+
SCNotification notif{};
341+
notif.nmhdr.hwndFrom = ctx().mainHwnd;
342+
notif.nmhdr.code = NPPN_FILESAVED;
343+
notif.nmhdr.idFrom = 0; // V1: no stable buffer ID yet
344+
pluginManager().notify(&notif);
345+
}
325346
}
326347

327348
delete[] buf;

macos/platform/menu_builder.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@
77
#include "windows.h"
88

99
HMENU buildMenuBar();
10+
11+
// Returns the Plugins submenu handle (populated by MacPluginManager after loading)
12+
HMENU getPluginsMenuHandle();

macos/platform/menu_builder.mm

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include "language_defs.h"
99
#include "windows.h"
1010

11+
static HMENU s_pluginsMenuHandle = nullptr;
12+
1113
HMENU buildMenuBar()
1214
{
1315
HMENU hMenuBar = CreateMenu();
@@ -196,6 +198,11 @@ HMENU buildMenuBar()
196198
AppendMenuW(hToolsMenu, MF_POPUP, reinterpret_cast<UINT_PTR>(hHashMenu), L"&Hash");
197199
AppendMenuW(hMenuBar, MF_POPUP, reinterpret_cast<UINT_PTR>(hToolsMenu), L"&Tools");
198200

201+
// Plugins menu (populated by MacPluginManager after loading)
202+
HMENU hPluginsMenu = CreatePopupMenu();
203+
s_pluginsMenuHandle = hPluginsMenu;
204+
AppendMenuW(hMenuBar, MF_POPUP, reinterpret_cast<UINT_PTR>(hPluginsMenu), L"&Plugins");
205+
199206
// Language menu
200207
HMENU hLangMenu = CreatePopupMenu();
201208
for (int i = 0; i < g_numLanguages; ++i)
@@ -213,3 +220,8 @@ HMENU buildMenuBar()
213220

214221
return hMenuBar;
215222
}
223+
224+
HMENU getPluginsMenuHandle()
225+
{
226+
return s_pluginsMenuHandle;
227+
}

macos/platform/nppm_handler.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// nppm_handler.h — NPPM_* and RUNCOMMAND_USER message handlers
2+
// Handles plugin messages sent via SendMessage(nppHandle, NPPM_*, ...)
3+
4+
#pragma once
5+
6+
#include "windows.h"
7+
8+
LRESULT handleNppmMessage(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
9+
LRESULT handleRunCommandMessage(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

0 commit comments

Comments
 (0)