Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions macos/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,8 @@ add_executable(MacNotePlusPlus
"${CMAKE_CURRENT_SOURCE_DIR}/platform/print_support.mm"
"${CMAKE_CURRENT_SOURCE_DIR}/platform/hash_tools.mm"
"${CMAKE_CURRENT_SOURCE_DIR}/platform/macro_manager.mm"
"${CMAKE_CURRENT_SOURCE_DIR}/platform/plugin_manager.mm"
"${CMAKE_CURRENT_SOURCE_DIR}/platform/nppm_handler.mm"
)
target_include_directories(MacNotePlusPlus PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/platform"
Expand All @@ -392,6 +394,7 @@ target_include_directories(MacNotePlusPlus PRIVATE
"${SCINTILLA_INCLUDE_DIR}"
"${LEXILLA_INCLUDE_DIR}"
"${UCHARDET_DIR}"
"${NPP_SRC_DIR}/MISC/PluginsManager"
)
target_link_libraries(MacNotePlusPlus
win32shim
Expand All @@ -410,6 +413,8 @@ target_compile_options(MacNotePlusPlus PRIVATE
-fobjc-arc
-Wno-deprecated-declarations
)
# Export symbols so plugins loaded via dlopen can resolve host functions (SendMessageW, etc.)
target_link_options(MacNotePlusPlus PRIVATE -Wl,-export_dynamic)
add_dependencies(MacNotePlusPlus AppIcon)
add_custom_command(TARGET MacNotePlusPlus POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
Expand All @@ -421,6 +426,36 @@ add_custom_command(TARGET MacNotePlusPlus POST_BUILD
COMMENT "Copying logo.png and AppIcon.icns"
)

# ============================================================
# Sample plugin: HelloMacNote
# ============================================================
add_library(HelloMacNote MODULE
"${CMAKE_CURRENT_SOURCE_DIR}/plugin-sdk/example/HelloMacNote/HelloMacNote.cpp"
)
target_include_directories(HelloMacNote PRIVATE
"${SHIM_INCLUDE_DIR}"
"${NPP_SRC_DIR}/MISC/PluginsManager"
"${SCINTILLA_INCLUDE_DIR}"
)
set_target_properties(HelloMacNote PROPERTIES
PREFIX ""
SUFFIX ".dylib"
OUTPUT_NAME "HelloMacNote"
)
target_compile_options(HelloMacNote PRIVATE -Wno-deprecated-declarations)
target_link_options(HelloMacNote PRIVATE -undefined dynamic_lookup)

# Opt-in install target: cmake --build . --target install_sample_plugin
add_custom_target(install_sample_plugin
DEPENDS HelloMacNote
COMMAND ${CMAKE_COMMAND} -E make_directory
"$ENV{HOME}/Library/Application Support/MacNote++/plugins/HelloMacNote"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_FILE:HelloMacNote>"
"$ENV{HOME}/Library/Application Support/MacNote++/plugins/HelloMacNote/HelloMacNote.dylib"
COMMENT "Installing HelloMacNote plugin to ~/Library/Application Support/MacNote++/plugins/"
)

# ============================================================
# Packaging: App bundle, code signing, and DMG
# ============================================================
Expand Down
73 changes: 73 additions & 0 deletions macos/platform/app_delegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
#include "scintilla_bridge.h"
#include "handle_registry.h"
#include "settings_manager.h"
#include "plugin_manager.h"
#include "Notepad_plus_msgs.h"
#include "file_monitor_mac.h"
#include "brace_match.h"
#include "smart_highlight.h"
Expand Down Expand Up @@ -251,12 +253,41 @@ - (void)applicationDidFinishLaunching:(NSNotification*)notification
return;
}

// Register main Scintilla view with HandleRegistry so SendMessage(sciHandle, SCI_*, ...) works
{
HandleRegistry::WindowInfo sciInfo{};
sciInfo.nativeView = ctx().scintillaView;
sciInfo.className = L"Scintilla";
sciInfo.isScintilla = true;
sciInfo.parent = ctx().mainHwnd;
ctx().scintillaMainHwnd = HandleRegistry::createWindow(std::move(sciInfo));
}

configureScintilla(ctx().scintillaView);
applyAppearance();
if (ctx().documentMapEnabled)
initializeDocumentMap();
setSyncScrollingEnabled(ctx().syncScrolling);

// Pre-create the second Scintilla view (hidden) so plugins always have a valid handle.
// doSplit() will reuse this view instead of creating a new one.
{
NSView* hiddenContainer = [[NSView alloc] initWithFrame:NSZeroRect];
hiddenContainer.hidden = YES;
[ctx().editorContainer addSubview:hiddenContainer];
ctx().scintillaView2 = ScintillaBridge_createView((__bridge void*)hiddenContainer, 0, 0, 0, 0);
if (ctx().scintillaView2)
{
HandleRegistry::WindowInfo sci2Info{};
sci2Info.nativeView = ctx().scintillaView2;
sci2Info.className = L"Scintilla";
sci2Info.isScintilla = true;
sci2Info.parent = ctx().mainHwnd;
ctx().scintillaSecondHwnd = HandleRegistry::createWindow(std::move(sci2Info));
configureScintilla(ctx().scintillaView2);
}
}

// Scintilla notification callback for main view
ScintillaBridge_setNotifyCallback(ctx().scintillaView, (intptr_t)ctx().mainHwnd,
[](intptr_t windowid, unsigned int iMessage, uintptr_t wParam, uintptr_t lParam) {
Expand Down Expand Up @@ -368,6 +399,9 @@ - (void)applicationDidFinishLaunching:(NSNotification*)notification
if (MacroManager::instance().isRecording())
MacroManager::instance().recordStep(scn->message, scn->wParam, scn->lParam);
}

// Forward Scintilla notifications to plugins
pluginManager().notify(reinterpret_cast<const SCNotification*>(scn));
}
});

Expand Down Expand Up @@ -515,6 +549,25 @@ - (void)applicationDidFinishLaunching:(NSNotification*)notification
bindDocumentMapToActiveView();
updateDocumentMapViewport();

// Initialize plugin system
{
NppData nppData;
nppData._nppHandle = ctx().mainHwnd;
nppData._scintillaMainHandle = ctx().scintillaMainHwnd;
nppData._scintillaSecondHandle = ctx().scintillaSecondHwnd;
pluginManager().init(nppData);
pluginManager().loadPlugins();
pluginManager().initMenu(getPluginsMenuHandle());
}

// Notify plugins that initialization is complete
{
SCNotification readyNotif{};
readyNotif.nmhdr.hwndFrom = ctx().mainHwnd;
readyNotif.nmhdr.code = NPPN_READY;
pluginManager().notify(&readyNotif);
}

NSLog(@"=== Notepad++ macOS Port — Phase 7 ===");
NSLog(@"Settings, split view, edit commands, encoding, session, drag-and-drop!");
}
Expand Down Expand Up @@ -590,6 +643,26 @@ - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender

- (void)applicationWillTerminate:(NSNotification*)notification
{
// Notify plugins that we are shutting down
{
SCNotification shutdownNotif{};
shutdownNotif.nmhdr.hwndFrom = ctx().mainHwnd;
shutdownNotif.nmhdr.code = NPPN_SHUTDOWN;
pluginManager().notify(&shutdownNotif);
}

// Destroy Scintilla HWNDs to release HandleRegistry-retained native views
if (ctx().scintillaSecondHwnd)
{
HandleRegistry::destroyWindow(ctx().scintillaSecondHwnd);
ctx().scintillaSecondHwnd = nullptr;
}
if (ctx().scintillaMainHwnd)
{
HandleRegistry::destroyWindow(ctx().scintillaMainHwnd);
ctx().scintillaMainHwnd = nullptr;
}

saveSession();

auto& s = SettingsManager::instance().settings;
Expand Down
2 changes: 2 additions & 0 deletions macos/platform/app_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ struct AppContext
{
// Main editor
void* scintillaView = nullptr;
HWND scintillaMainHwnd = nullptr;
NSWindow* mainWindow = nil;
HWND mainHwnd = nullptr;
HWND tabHwnd = nullptr;
Expand Down Expand Up @@ -62,6 +63,7 @@ struct AppContext

// Split view state
void* scintillaView2 = nullptr;
HWND scintillaSecondHwnd = nullptr;
NSSplitView* splitView = nil;
bool isSplit = false;
int activeView = 0; // 0=main, 1=sub
Expand Down
29 changes: 29 additions & 0 deletions macos/platform/document_manager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include "function_list_panel.h"
#include "file_switcher_panel.h"
#include "sync_scroll.h"
#include "plugin_manager.h"
#include "Notepad_plus_msgs.h"
#include "windows.h"
#include "commctrl.h"
#include "handle_registry.h"
Expand Down Expand Up @@ -224,6 +226,15 @@ void closeTabFromView(int viewIndex, int tabIndex)
return;
if (!sci) return;

// Notify plugins that a file is about to be closed
{
SCNotification notif{};
notif.nmhdr.hwndFrom = ctx().mainHwnd;
notif.nmhdr.code = NPPN_FILEBEFORECLOSE;
notif.nmhdr.idFrom = 0; // V1: no stable buffer ID yet
pluginManager().notify(&notif);
}

if (docs.size() <= 1)
{
if (docs[0].functionListDocumentId != 0)
Expand All @@ -248,6 +259,15 @@ void closeTabFromView(int viewIndex, int tabIndex)
[ctx().mainWindow setTitle:@"Notepad++ — Untitled"];
updateTabModifiedIndicator(viewIndex, 0);
updateWindowDocumentEdited();

// Notify plugins that the file has been closed
{
SCNotification notif{};
notif.nmhdr.hwndFrom = ctx().mainHwnd;
notif.nmhdr.code = NPPN_FILECLOSED;
notif.nmhdr.idFrom = 0;
pluginManager().notify(&notif);
}
return;
}

Expand Down Expand Up @@ -281,6 +301,15 @@ void closeTabFromView(int viewIndex, int tabIndex)
NSString* title = WideToNSString(doc.title.c_str());
[ctx().mainWindow setTitle:[NSString stringWithFormat:@"Notepad++ — %@", title]];
updateWindowDocumentEdited();

// Notify plugins that the file has been closed
{
SCNotification notif{};
notif.nmhdr.hwndFrom = ctx().mainHwnd;
notif.nmhdr.code = NPPN_FILECLOSED;
notif.nmhdr.idFrom = 0;
pluginManager().notify(&notif);
}
}

void closeTab(int tabIndex)
Expand Down
21 changes: 21 additions & 0 deletions macos/platform/file_operations.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#include "scintilla_bridge.h"
#include "file_monitor_mac.h"
#include "uchardet.h"
#include "plugin_manager.h"
#include "Notepad_plus_msgs.h"
#include "windows.h"
#include "commdlg.h"
#include "commctrl.h"
Expand Down Expand Up @@ -206,6 +208,16 @@ bool openFileAtPath(NSString* path)
addRecentFile(wpath);
rebuildRecentMenu();
updateStatusBar();

// Notify plugins that a file was opened
{
SCNotification notif{};
notif.nmhdr.hwndFrom = ctx().mainHwnd;
notif.nmhdr.code = NPPN_FILEOPENED;
notif.nmhdr.idFrom = 0; // V1: no stable buffer ID yet
pluginManager().notify(&notif);
}
Comment on lines +212 to +219
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds NPPN_FILEOPENED notifications but always sets nmhdr.idFrom = 0. The PR description lists file notifications as a V2 limitation specifically because they require stable buffer IDs; sending these events in V1 (without IDs) can mislead plugins that treat the notification as fully supported. Either defer emitting these notifications until buffer IDs exist, or update the PR description/limitations to clarify the partial semantics.

Copilot uses AI. Check for mistakes.

return true;
}

Expand Down Expand Up @@ -322,6 +334,15 @@ void saveCurrentFile()

addRecentFile(doc.filePath);
rebuildRecentMenu();

// Notify plugins that the file was saved
{
SCNotification notif{};
notif.nmhdr.hwndFrom = ctx().mainHwnd;
notif.nmhdr.code = NPPN_FILESAVED;
notif.nmhdr.idFrom = 0; // V1: no stable buffer ID yet
pluginManager().notify(&notif);
}
Comment on lines +338 to +345
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the file-open case: NPPN_FILESAVED is emitted with nmhdr.idFrom = 0, despite the PR description calling file notifications a V2 item due to needing stable buffer IDs. Consider deferring or clearly documenting that these are emitted with an invalid/placeholder BufferID in V1.

Copilot uses AI. Check for mistakes.
}

delete[] buf;
Expand Down
3 changes: 3 additions & 0 deletions macos/platform/menu_builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
#include "windows.h"

HMENU buildMenuBar();

// Returns the Plugins submenu handle (populated by MacPluginManager after loading)
HMENU getPluginsMenuHandle();
12 changes: 12 additions & 0 deletions macos/platform/menu_builder.mm
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include "language_defs.h"
#include "windows.h"

static HMENU s_pluginsMenuHandle = nullptr;

HMENU buildMenuBar()
{
HMENU hMenuBar = CreateMenu();
Expand Down Expand Up @@ -196,6 +198,11 @@ HMENU buildMenuBar()
AppendMenuW(hToolsMenu, MF_POPUP, reinterpret_cast<UINT_PTR>(hHashMenu), L"&Hash");
AppendMenuW(hMenuBar, MF_POPUP, reinterpret_cast<UINT_PTR>(hToolsMenu), L"&Tools");

// Plugins menu (populated by MacPluginManager after loading)
HMENU hPluginsMenu = CreatePopupMenu();
s_pluginsMenuHandle = hPluginsMenu;
AppendMenuW(hMenuBar, MF_POPUP, reinterpret_cast<UINT_PTR>(hPluginsMenu), L"&Plugins");

// Language menu
HMENU hLangMenu = CreatePopupMenu();
for (int i = 0; i < g_numLanguages; ++i)
Expand All @@ -213,3 +220,8 @@ HMENU buildMenuBar()

return hMenuBar;
}

HMENU getPluginsMenuHandle()
{
return s_pluginsMenuHandle;
}
9 changes: 9 additions & 0 deletions macos/platform/nppm_handler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// nppm_handler.h — NPPM_* and RUNCOMMAND_USER message handlers
// Handles plugin messages sent via SendMessage(nppHandle, NPPM_*, ...)

#pragma once

#include "windows.h"

LRESULT handleNppmMessage(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT handleRunCommandMessage(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
Loading
Loading