Skip to content

Commit 7f4e2e2

Browse files
committed
Improve Unicode support and slightly refactor plugin logic
Enhanced Unicode handling in `IniFiles.cs` and tag replacement logic in `Main.cs` to ensure proper processing of non-ASCII characters. Improved error handling for empty or invalid commands in `Actions.cs`. Refactored `Main.cs` to upgrade the `SetMenuItemNames` method for (dynamic) menu setup and user alerts when `[Commands]` configuration changes. Removed earlier menu modification logic to prevent unexpected behavior. Updated version information in `AssemblyInfo.cs` to `2.7.rc.4`.
1 parent 11f79a0 commit 7f4e2e2

4 files changed

Lines changed: 92 additions & 39 deletions

File tree

WebEdit/Actions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ internal void ExecuteCommand(string command)
3333

3434
public PluginFunc GetCommand(int index)
3535
{
36-
return _commands.TryGetValue(index, out string cmdString) ? () => ExecuteCommand(cmdString) : null;
36+
return _commands.TryGetValue(index, out string cmdString) && !string.IsNullOrWhiteSpace(cmdString) ? () => ExecuteCommand(cmdString) : null;
3737
}
3838
}
3939
}

WebEdit/IniFiles.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ partial class IniFile(string fileName)
1919
private (string, string) ExtractKeyAndValue(string line)
2020
{
2121
var kv =
22-
Regex.Split(line ?? string.Empty, $@"(?i)(^[a-z0-9 _\-\&]{{1,{Main.MaxKeyLen}}}){_keyValueSeparator}")
22+
Regex.Split(line ?? string.Empty, $@"(?i)(^[\p{{L}}\p{{N}} _\-\&]{{1,{Main.MaxKeyLen}}}){_keyValueSeparator}") // For Unicode support: `a-z0-9` » `\p{{L}}\p{{N}}`
2323
.Where(s => s.Trim() != string.Empty);
2424
return (kv?.Count() > 1) ? (kv.First(), string.Join("", kv.Skip(1)).Trim()) : ("", "");
2525
}
@@ -55,6 +55,9 @@ public string Get(string section, string key = null)
5555
}
5656

5757
public string[] GetKeys(string section)
58-
=> Get(section).Trim('\0').Split('\0');
58+
{
59+
var keys = Get(section).Trim('\0');
60+
return string.IsNullOrEmpty(keys) ? Array.Empty<string>() : keys.Split('\0');
61+
}
5962
}
6063
}

WebEdit/Main.cs

Lines changed: 85 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

WebEdit/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@
2929
// [assembly: AssemblyVersion("1.0.*")]
3030
[assembly: AssemblyVersion("2.7.0.0")]
3131
[assembly: AssemblyFileVersion("2.7.0.0")]
32-
[assembly: AssemblyInformationalVersion("2.7.rc.3")]
32+
[assembly: AssemblyInformationalVersion("2.7.rc.4")]

0 commit comments

Comments
 (0)