@@ -28,6 +28,8 @@ partial class Main : IDotNetPlugin {
2828
2929 static IniFile ini = null ;
3030 static bool isConfigDirty = false ;
31+ private static string [ ] currentCommandKeys = null ;
32+ private static bool currentCommandKeysAlerted = false ;
3133 internal static string iniDirectory , iniFilePath = null ;
3234
3335 public void OnBeNotified ( ScNotification notification )
@@ -69,7 +71,6 @@ public void OnBeNotified(ScNotification notification)
6971 /// </summary>
7072 public void OnSetInfo ( )
7173 {
72- int i = 0 ;
7374 var npp = new NotepadPPGateway ( ) ;
7475 iniDirectory = Path . Combine ( npp . GetConfigDirectory ( ) , PluginName ) ;
7576 _ = Directory . CreateDirectory ( iniDirectory ) ;
@@ -82,27 +83,7 @@ public void OnSetInfo()
8283 }
8384 catch { }
8485 LoadConfig ( ) ;
85- var actions = new Actions ( ini ) ;
86- foreach ( string key in actions . iniKeys ) {
87- var methodInfo = actions . GetCommand ( i ++ ) ;
88- if ( methodInfo == null )
89- break ;
90-
91- Utils . SetCommand (
92- $ "{ MenuCmdPrefix } { key } ",
93- ( ) =>
94- {
95- var cmds = new Actions ( ini ) ;
96- cmds . ExecuteCommand ( ini . Get ( "Commands" , key ) ) ;
97- } ) ;
98- }
99- Utils . SetCommand (
100- "Replace Tag" , ReplaceTag ,
101- new ShortcutKey ( FALSE , TRUE , FALSE , 13 ) ) ;
102- Utils . MakeSeparator ( ) ;
103- Utils . SetCommand ( "Edit Config" , EditConfig ) ;
104- Utils . SetCommand ( "Load Config" , LoadConfig ) ;
105- Utils . SetCommand ( "About..." , About ) ;
86+ SetMenuItemNames ( ) ;
10687 }
10788
10889 public NativeBool OnMessageProc ( uint msg , UIntPtr wParam , IntPtr lParam ) => TRUE ;
@@ -191,7 +172,6 @@ internal static void PluginCleanUp()
191172 {
192173 // This method is called when the plugin is notified about Npp shutdown.
193174 PluginData . PluginNamePtr = NULL ;
194- PluginData . FuncItems . Dispose ( ) ;
195175 }
196176
197177 /// <summary>
@@ -216,24 +196,42 @@ internal static void ReplaceTag()
216196 return ;
217197
218198 // Find the last occurrence of the tag in the current line before the caret
219- long tagStartPos = - 1 ;
220- long caretPosInLine = scintillaGateway . GetCurrentPos ( ) - lineStart ;
199+ // NOTE: Scintilla positions are byte offsets for the document encoding,
200+ // while string.IndexOf works on characters. Convert between bytes and chars
201+ // using the document encoding to make the search Unicode-aware.
202+ long tagStartCharPos = - 1 ;
203+
204+ // Byte position of the caret relative to the line start
205+ long caretBytePosInLine = scintillaGateway . GetCurrentPos ( ) - lineStart ;
206+
207+ // Get the encoded bytes of the line once
208+ byte [ ] lineBytes = scintillaGateway . CodePage . GetBytes ( lineText ) ;
209+
210+ // Clamp caret byte index to available bytes
211+ int caretByteIndex = ( int ) Math . Min ( Math . Max ( 0 , caretBytePosInLine ) , lineBytes . Length ) ;
212+
213+ // Convert the caret byte offset to a character index
214+ int caretCharPos = scintillaGateway . CodePage . GetCharCount ( lineBytes , 0 , caretByteIndex ) ;
215+
221216 int searchPos = 0 ;
222217 while ( searchPos < lineText . Length )
223218 {
224- int foundPos = lineText . IndexOf ( tag , searchPos , StringComparison . Ordinal ) ;
225- if ( foundPos == - 1 || foundPos + tag . Length > caretPosInLine )
226- break ;
227- tagStartPos = foundPos ;
228- searchPos = foundPos + 1 ;
219+ int foundPos = lineText . IndexOf ( tag , searchPos , StringComparison . Ordinal ) ;
220+ if ( foundPos == - 1 || foundPos + tag . Length > caretCharPos )
221+ break ;
222+ tagStartCharPos = foundPos ;
223+ searchPos = foundPos + 1 ;
229224 }
230225
231- if ( tagStartPos < 0 )
226+ if ( tagStartCharPos < 0 )
232227 return ;
233228
234- long selStart = lineStart + tagStartPos ;
235- long tagLength = scintillaGateway . CodePage . GetByteCount ( tag ) ;
236- long selEnd = selStart + tagLength ;
229+ // Convert the character position of the found tag into a byte position
230+ int tagStartByteOffset = scintillaGateway . CodePage . GetByteCount ( lineText . AsSpan ( 0 , ( int ) tagStartCharPos ) ) ;
231+ long selStart = lineStart + tagStartByteOffset ;
232+
233+ long tagLengthBytes = scintillaGateway . CodePage . GetByteCount ( tag ) ;
234+ long selEnd = selStart + tagLengthBytes ;
237235 scintillaGateway . SetSelection ( selStart , selEnd ) ;
238236 selectedText = scintillaGateway . GetSelText ( ) ;
239237 }
@@ -351,7 +349,58 @@ private static string GetIconPath(string icon)
351349 /// </remarks>
352350 private static unsafe void SetMenuItemNames ( )
353351 {
354- Actions actions = new ( ini ) ;
352+ var actions = new Actions ( ini ) ;
353+
354+ // Alert if the [Commands] section has changed
355+ if ( currentCommandKeys != null && ! currentCommandKeys . SequenceEqual ( actions . iniKeys ) )
356+ {
357+ if ( ! currentCommandKeysAlerted )
358+ {
359+ MsgBoxDialog (
360+ PluginData . NppData . NppHandle ,
361+ "The [Commands] configuration has changed. Please restart Notepad++ for all changes to take effect" ,
362+ MsgBoxCaption ,
363+ ( uint ) ( MsgBox . ICONWARNING | MsgBox . OK ) ) ;
364+ }
365+ currentCommandKeysAlerted = true ;
366+ return ;
367+ }
368+ currentCommandKeys = actions . iniKeys ;
369+
370+ // Add menu items for each command in the [Commands] section of the ini-file
371+ int i = 0 ;
372+ bool foundItem = false ;
373+ foreach ( string key in currentCommandKeys )
374+ {
375+ var methodInfo = actions . GetCommand ( i ++ ) ;
376+ if ( methodInfo == null )
377+ break ;
378+
379+ Utils . SetCommand (
380+ $ "{ MenuCmdPrefix } { key } ",
381+ ( ) =>
382+ {
383+ var cmds = new Actions ( ini ) ;
384+ cmds . ExecuteCommand ( ini . Get ( "Commands" , key ) ) ;
385+ } ) ;
386+ foundItem = true ;
387+ }
388+
389+ // Add other menu items (Replace Tag, Edit Config, Load Config and About)
390+ if ( foundItem )
391+ {
392+ Utils . MakeSeparator ( ) ; // Separator if "Commands" were found
393+ }
394+ Utils . SetCommand (
395+ "Replace Tag" , ReplaceTag ,
396+ new ShortcutKey ( FALSE , TRUE , FALSE , 13 ) ) ;
397+ Utils . MakeSeparator ( ) ;
398+ Utils . SetCommand ( "Edit Config" , EditConfig ) ;
399+ Utils . SetCommand ( "Load Config" , LoadConfig ) ;
400+ Utils . SetCommand ( "About..." , About ) ;
401+
402+ /* DEPRECATED (may cause non-expected behavior)
403+ // PluginData.FuncItems.Items.Clear();
355404 IntPtr hMenu = SendMessage(PluginData.NppData.NppHandle, (uint)NppMsg.NPPM_GETMENUHANDLE, (uint)NppMsg.NPPPLUGINMENU);
356405 for (int i = 0; i < actions.iniKeys.Length && i < PluginData.FuncItems.Items.Count; ++i)
357406 {
@@ -366,6 +415,7 @@ private static unsafe void SetMenuItemNames()
366415 }
367416 catch { }
368417 }
418+ */
369419 }
370420 }
371421}
0 commit comments