From 4fc21d259d9968609c1361c4891d9f0efb93b797 Mon Sep 17 00:00:00 2001 From: Maurice Beckman Date: Mon, 6 Jan 2025 20:16:06 +0100 Subject: [PATCH 1/3] Fixes #43 --- nppRandomStringGenerator/Forms/ConfigAndGenerate.cs | 7 +++++++ nppRandomStringGenerator/Modules/StringGenerator.cs | 1 - nppRandomStringGenerator/Properties/AssemblyInfo.cs | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/nppRandomStringGenerator/Forms/ConfigAndGenerate.cs b/nppRandomStringGenerator/Forms/ConfigAndGenerate.cs index 590f25b..cf0ecc7 100644 --- a/nppRandomStringGenerator/Forms/ConfigAndGenerate.cs +++ b/nppRandomStringGenerator/Forms/ConfigAndGenerate.cs @@ -259,12 +259,19 @@ private void RadioButtonInline_CheckedChanged(object sender, EventArgs e) NumericUpDownQuantity.Maximum = this.Editor.GetLineCount(); NumericUpDownQuantity.Value = this.Editor.GetLineCount(); TextboxSeperator.Enabled = true; + + NumericUpDownGUIDQuantity.Enabled = false; + NumericUpDownGUIDQuantity.Maximum = this.Editor.GetLineCount(); + NumericUpDownGUIDQuantity.Value = this.Editor.GetLineCount(); } else { NumericUpDownQuantity.Maximum = 4096000; NumericUpDownQuantity.Enabled = true; TextboxSeperator.Enabled = false; + + NumericUpDownGUIDQuantity.Maximum = 4096000; + NumericUpDownGUIDQuantity.Enabled = true; } } diff --git a/nppRandomStringGenerator/Modules/StringGenerator.cs b/nppRandomStringGenerator/Modules/StringGenerator.cs index d7e6d56..f5962d9 100644 --- a/nppRandomStringGenerator/Modules/StringGenerator.cs +++ b/nppRandomStringGenerator/Modules/StringGenerator.cs @@ -193,7 +193,6 @@ public void GenerateStrings() lock (this.LockingEditor) { - this.Editor.GotoLine(line); this.Editor.LineEnd(); this.Editor.AddText(sb.Length, sb.ToString()); diff --git a/nppRandomStringGenerator/Properties/AssemblyInfo.cs b/nppRandomStringGenerator/Properties/AssemblyInfo.cs index 3d90d53..873ea09 100644 --- a/nppRandomStringGenerator/Properties/AssemblyInfo.cs +++ b/nppRandomStringGenerator/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.9.2")] -[assembly: AssemblyFileVersion("1.9.2")] +[assembly: AssemblyVersion("1.9.3")] +[assembly: AssemblyFileVersion("1.9.3")] From bf57a8979e80abf234a428aa920e12bb676afce9 Mon Sep 17 00:00:00 2001 From: Maurice Beckman Date: Tue, 11 Feb 2025 21:45:47 +0100 Subject: [PATCH 2/3] Needs some tweaking --- nppRandomStringGenerator/ILMerge.props | 67 - nppRandomStringGenerator/ILMergeOrder.txt | 12 - nppRandomStringGenerator/JSON_Tools/JNode.cs | 1846 +++++++++++++++++ .../JSON_Tools/JsonParser.cs | 1725 +++++++++++++++ nppRandomStringGenerator/Main.cs | 7 +- .../DllExport/DllExportAttribute.cs | 9 +- .../DllExport/Mono.Cecil.dll | Bin 275968 -> 0 bytes .../DllExport/NppPlugin.DllExport.MSBuild.dll | Bin 41472 -> 0 bytes .../DllExport/NppPlugin.DllExport.dll | Bin 90112 -> 0 bytes .../DllExport/NppPlugin.DllExport.targets | 46 - .../PluginInfrastructure/GatewayDomain.cs | 1512 +++++++------- .../PluginInfrastructure/IScintillaGateway.cs | 25 +- .../PluginInfrastructure/MenuCmdID_h.cs | 2 +- .../PluginInfrastructure/Msgs_h.cs | 133 +- .../PluginInfrastructure/NotepadPPGateway.cs | 279 ++- .../PluginInfrastructure/NppPluginNETBase.cs | 169 +- .../NppPluginNETHelper.cs | 26 +- .../PluginInfrastructure/Preference_h.cs | 4 - .../PluginInfrastructure/Resource_h.cs | 4 - .../PluginInfrastructure/ScintillaGateway.cs | 269 +-- .../PluginInfrastructure/Scintilla_iface.cs | 17 +- .../PluginInfrastructure/UnmanagedExports.cs | 4 +- .../PluginInfrastructure/Win32.cs | 145 +- .../Properties/AssemblyInfo.cs | 6 +- .../Utils/ArrayExtensions.cs | 399 ++++ nppRandomStringGenerator/Utils/FormStyle.cs | 116 ++ nppRandomStringGenerator/Utils/NanInf.cs | 21 + nppRandomStringGenerator/Utils/Npp.cs | 352 ++++ nppRandomStringGenerator/Utils/NppListener.cs | 36 + nppRandomStringGenerator/Utils/Translator.cs | 599 ++++++ nppRandomStringGenerator/app.manifest | 79 - .../nppRandomStringGenerator.csproj | 40 +- nppRandomStringGenerator/packages.config | 5 - 33 files changed, 6734 insertions(+), 1220 deletions(-) delete mode 100644 nppRandomStringGenerator/ILMerge.props delete mode 100644 nppRandomStringGenerator/ILMergeOrder.txt create mode 100644 nppRandomStringGenerator/JSON_Tools/JNode.cs create mode 100644 nppRandomStringGenerator/JSON_Tools/JsonParser.cs delete mode 100644 nppRandomStringGenerator/PluginInfrastructure/DllExport/Mono.Cecil.dll delete mode 100644 nppRandomStringGenerator/PluginInfrastructure/DllExport/NppPlugin.DllExport.MSBuild.dll delete mode 100644 nppRandomStringGenerator/PluginInfrastructure/DllExport/NppPlugin.DllExport.dll delete mode 100644 nppRandomStringGenerator/PluginInfrastructure/DllExport/NppPlugin.DllExport.targets create mode 100644 nppRandomStringGenerator/Utils/ArrayExtensions.cs create mode 100644 nppRandomStringGenerator/Utils/FormStyle.cs create mode 100644 nppRandomStringGenerator/Utils/NanInf.cs create mode 100644 nppRandomStringGenerator/Utils/Npp.cs create mode 100644 nppRandomStringGenerator/Utils/NppListener.cs create mode 100644 nppRandomStringGenerator/Utils/Translator.cs delete mode 100644 nppRandomStringGenerator/app.manifest delete mode 100644 nppRandomStringGenerator/packages.config diff --git a/nppRandomStringGenerator/ILMerge.props b/nppRandomStringGenerator/ILMerge.props deleted file mode 100644 index aaadb12..0000000 --- a/nppRandomStringGenerator/ILMerge.props +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/nppRandomStringGenerator/ILMergeOrder.txt b/nppRandomStringGenerator/ILMergeOrder.txt deleted file mode 100644 index 0e79a7f..0000000 --- a/nppRandomStringGenerator/ILMergeOrder.txt +++ /dev/null @@ -1,12 +0,0 @@ -# this file contains the partial list of the merged assemblies in the merge order -# you can fill it from the obj\CONFIG\PROJECT.ilmerge generated on every build -# and finetune merge order to your satisfaction -nppRandomStringGenerator.dll -System.IO.dll -System.Runtime.dll -System.Runtime.Serialization.dll -System.Runtime.Serialization.json.dll -System.Threading.Tasks.dll -Microsoft.Threading.Tasks.dll -Microsoft.Threading.Tasks.Extensions.Desktop.dll -Microsoft.Threading.Tasks.Extensions.dll diff --git a/nppRandomStringGenerator/JSON_Tools/JNode.cs b/nppRandomStringGenerator/JSON_Tools/JNode.cs new file mode 100644 index 0000000..aac2a35 --- /dev/null +++ b/nppRandomStringGenerator/JSON_Tools/JNode.cs @@ -0,0 +1,1846 @@ +/* +A class for representing arbitrary JSON. +This was copy-pasted from https://github.com/molsonkiko/JsonToolsNppPlugin/blob/9eadb9f8995215bd0eb4f3349e99a29d2f35061d/JsonToolsNppPlugin/JSONTools/JNode.cs with some minor naming changes and removals of irrelevant code. +*/ +using System; +using System.Collections.Generic; // for dictionary, list +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace NppPluginNET.JSON_Tools +{ + /// + /// JNode type indicator + /// + public enum Dtype : ushort + { + /// useful only in JSON schema + TYPELESS = 0, + /// represented as booleans + BOOL = 1, + /// represented as longs + INT = 2, + /// represented as doubles + FLOAT = 4, + /// represented as strings + STR = 8, + NULL = 16, + /// JObject Dtype. Represented as Dictionary(string, JNode). + OBJ = 32, + /// JArray Dtype. Represented as List(JNode). + ARR = 64, + /// The type of a CurJson node in RemesPathFunctions with unknown type + UNKNOWN = 128, + /// A regular expression, made by RemesPath + REGEX = 256, + /// a string representing an array slice + SLICE = 512, + ///// + ///// A YYYY-MM-DD date + ///// + //DATE = 1024, + ///// + ///// A YYYY-MM-DD hh:mm:ss.sss datetime + ///// + //DATETIME = 2048, + /// + /// NO JNODES ACTUALLY HAVE THE FUNCTION TYPE.

+ /// It is used in RemesPath to distinguish CurJson from non-CurJson + ///
+ FUNCTION = 4096, + /********* COMPOSITE TYPES *********/ + FLOAT_OR_INT = FLOAT | INT, + INT_OR_BOOL = INT | BOOL, + /// + /// a float, int, or bool + /// + NUM = FLOAT | INT | BOOL, + ITERABLE = UNKNOWN | ARR | OBJ, + STR_OR_REGEX = STR | REGEX, + INT_OR_SLICE = INT | SLICE, + ARR_OR_OBJ = ARR | OBJ, + SCALAR = FLOAT | INT | BOOL | STR | NULL | REGEX, // | TIME + ANYTHING = SCALAR | ITERABLE, + } + + /// + /// The artistic style used for pretty-printing.

+ /// Controls things like whether the start bracket of an array/object are on the same line as the parent key.

+ /// See http://astyle.sourceforge.net/astyle.html#_style=google + ///
+ public enum PrettyPrintStyle + { + /// + /// Formats + /// {"a": {"b": {"c": 2}, "d": [3, [4]]}} + /// like so:

+ /// + ///{ + /// "a": { + /// "b": { + /// "c": 2 + /// }, + /// "d": [ + /// 3, + /// [ + /// 4 + /// ] + /// ] + /// } + ///} + /// + ///
+ Google, + /// + /// Formats + /// {"a": {"b": {"c": 2}, "d": [3, [4]]}} + /// like so:

+ /// + ///{ + ///"a": + /// { + /// "b": + /// { + /// "c": 2 + /// }, + /// "d": + /// [ + /// 3, + /// [ + /// 4 + /// ] + /// ] + /// } + ///} + /// + /// This is a bit different from the Whitesmith style described on astyle.sourceforge.net,

+ /// but it's closer to that style than it is to anything else. + ///
+ Whitesmith, + /// + /// + ///{ + /// "algorithm": [ + /// ["start", "each", "child", "on", "a", "new", "line"], + /// ["if", "the", "line", "would", "have", "length", "at", "least", 80], + /// [ + /// "follow", + /// "this", + /// "algorithm", + /// ["starting", "from", "the", "beginning"] + /// ], + /// ["else", "print", "it", "out", "on", 1, "line"] + /// ], + /// "style": "PPrint", + /// "useful": true + ///} + /// + /// + PPrint, + } + + /// + /// Whether a given string can be accessed using dot syntax + /// or square brackets and quotes, and what kind of quotes to use + /// + public enum KeyStyle + { + /// + /// dot syntax for strings starting with _ or letters and containing only _, letters, and digits

+ /// else singlequotes if it doesn't contain '

+ /// else double quotes + ///
+ JavaScript, + /// + /// Singlequotes if it doesn't contain '

+ /// else double quotes + ///
+ Python, + /// + /// dot syntax for strings starting with _ or letters and containing only _, letters, and digits

+ /// else backticks + ///
+ RemesPath, + } + + /// + /// types of documents that can be parsed in JsonTools + /// + public enum DocumentType + { + /// null value for use in some functions + NONE, + JSON, + /// JSON Lines documents + JSONL, + /// ini files + INI, + /// regex search results (includes CSV files parsed with s_csv) + REGEX, + } + + /// + /// JSON documents are parsed as JNodes, JObjects, and JArrays. JObjects and JArrays are subclasses of JNode. + /// A JSON node, for use in creating a drop-down tree + /// Here's an example of how a small JSON document (with newlines as shown, and assuming `\r\n` newline) + /// would be parsed as JNodes: + /// + ///example.json + /// + ///{ + ///"a": [ + /// 1, + /// true, + /// {"b": 0.5, "c": "a"}, + /// null + /// ] + ///} + /// + ///should be parsed as: + /// node1: JObject(type = Dtype.OBJ, position = 0, children = Dictionary{"a": node2}) + /// node2: JArray(type = Dtype.ARR, position = 8, children = List{node3, node4, node5, node8}) + /// node3: JNode(value = 1, type = Dtype.INT, position = 15) + /// node4: JNode(value = true, type = Dtype.BOOL, position = 23) + /// node5: JObject(type = Dtype.OBJ, position = 38, + /// children = Dictionary{"b": node6, "c": node7}) + /// node6: JNode(value = 0.5, type = Dtype.FLOAT, position = 44) + /// node7: JNode(value = "a", type = Dtype.STR, position = 55) + /// node8: JNode(value = null, type = Dtype.NULL, position = 65) + /// + /// + [System.Diagnostics.DebuggerDisplay("JNode({ToString()})")] + public class JNode : IComparable + { + public const int PPRINT_LINE_LENGTH = 79; + + public const string NL = "\r\n"; + + public IComparable value; // null for arrays and objects + // IComparable is good here because we want easy comparison of JNodes + public Dtype type; + /// + /// Start position of the JNode in a UTF-8 encoded source string.

+ /// Note that this could disagree its position in the C# source string. + ///
+ public int position; + + //public ExtraJNodeProperties? extras; + + public JNode(IComparable value, + Dtype type, + int position) + { + this.position = position; + this.type = type; + this.value = value; + //extras = null; + } + + /// + /// instantiates a JNode with null value, type Dtype.NULL, and position 0 + /// + public JNode() + { + this.position = 0; + this.type = Dtype.NULL; + this.value = null; + //extras = null; + } + + public JNode(long value, int position = 0) + { + this.position = position; + this.type = Dtype.INT; + this.value = value; + } + + public JNode(string value, int position = 0) + { + this.position = position; + this.type = Dtype.STR; + this.value = value; + } + + public JNode(double value, int position = 0) + { + this.position = position; + this.type = Dtype.FLOAT; + this.value = value; + } + + public JNode(bool value, int position = 0) + { + this.position = position; + this.type = Dtype.BOOL; + this.value = value; + } + + // in some places like Germany they use ',' as the normal decimal separator. + // need to override this to ensure that we parse JSON correctly + public static readonly NumberFormatInfo DOT_DECIMAL_SEP = new NumberFormatInfo + { + NumberDecimalSeparator = "." + }; + #region TOSTRING_FUNCS + /// + /// appends the JSON representation of char c to a StringBuilder.

+ /// for most characters, this just means appending the character itself, but for example '\n' would become "\\n", '\t' would become "\\t",

+ /// and most other chars less than 32 would be appended as "\\u00{char value in hex}" (e.g., '\x14' becomes "\\u0014") + ///
+ public static void CharToSb(StringBuilder sb, char c) + { + switch (c) + { + case '\\': sb.Append("\\\\" ); break; + case '"': sb.Append("\\\"" ); break; + case '\x01': sb.Append("\\u0001"); break; + case '\x02': sb.Append("\\u0002"); break; + case '\x03': sb.Append("\\u0003"); break; + case '\x04': sb.Append("\\u0004"); break; + case '\x05': sb.Append("\\u0005"); break; + case '\x06': sb.Append("\\u0006"); break; + case '\x07': sb.Append("\\u0007"); break; + case '\x08': sb.Append("\\b" ); break; + case '\x09': sb.Append("\\t" ); break; + case '\x0A': sb.Append("\\n" ); break; + case '\x0B': sb.Append("\\v" ); break; + case '\x0C': sb.Append("\\f" ); break; + case '\x0D': sb.Append("\\r" ); break; + case '\x0E': sb.Append("\\u000E"); break; + case '\x0F': sb.Append("\\u000F"); break; + case '\x10': sb.Append("\\u0010"); break; + case '\x11': sb.Append("\\u0011"); break; + case '\x12': sb.Append("\\u0012"); break; + case '\x13': sb.Append("\\u0013"); break; + case '\x14': sb.Append("\\u0014"); break; + case '\x15': sb.Append("\\u0015"); break; + case '\x16': sb.Append("\\u0016"); break; + case '\x17': sb.Append("\\u0017"); break; + case '\x18': sb.Append("\\u0018"); break; + case '\x19': sb.Append("\\u0019"); break; + case '\x1A': sb.Append("\\u001A"); break; + case '\x1B': sb.Append("\\u001B"); break; + case '\x1C': sb.Append("\\u001C"); break; + case '\x1D': sb.Append("\\u001D"); break; + case '\x1E': sb.Append("\\u001E"); break; + case '\x1F': sb.Append("\\u001F"); break; + default: sb.Append(c); break; + } + } + + /// + /// the string representation of a JNode with value s, + /// with or without the enclosing quotes that a Dtype.STR JNode normally has + /// + /// + /// + public static string StrToString(string s, bool quoted) + { + int slen = s.Length; + int ii = 0; + for (; ii < slen; ii++) + { + char c = s[ii]; + if (c < 32 || c == '\\' || c == '"') + break; + } + if (ii == slen) + return quoted ? $"\"{s}\"" : s; + var sb = new StringBuilder(); + if (quoted) + sb.Append('"'); + if (ii > 0) + { + ii--; + sb.Append(s, 0, ii); + } + for (; ii < slen; ii++) + CharToSb(sb, s[ii]); + if (quoted) + sb.Append('"'); + return sb.ToString(); + } + + /// + /// mostly useful for quickly converting a JObject key (which must be escaped during parsing) into the raw string that it represents

+ /// Choose strAlreadyQuoted = true only if str is already wrapped in double quotes. + ///
+ public static string UnescapedJsonString(string str, bool strAlreadyQuoted) + { + return (string)new JsonParser().ParseString(strAlreadyQuoted ? str : $"\"{str}\"").value; + } + + /// + /// adds the escaped JSON representation (BUT WITHOUT ENCLOSING QUOTES) of a string s to StringBuilder sb. + /// + public static void StrToSb(StringBuilder sb, string s) + { + int ii = 0; + int slen = s.Length; + // if s contains no control chars + for (; ii < slen; ii++) + { + char c = s[ii]; + if (c < 32 || c == '\\' || c == '"') + break; + } + if (ii == slen) + sb.Append(s); + else + { + if (ii > 0) + { + ii--; + sb.Append(s, 0, ii); + } + for (; ii < slen; ii++) + { + CharToSb(sb, s[ii]); + } + } + } + + /// + /// Compactly prints the JSON.

+ /// If sortKeys is true, the keys of objects are printed in alphabetical order.

+ /// keyValueSep (default ": ") is the separator between the key and the value in an object. Use ":" instead if you want minimal whitespace.

+ /// itemSep (default ", ") is the separator between key-value pairs in an object or items in an array. Use "," instead if you want minimal whitespace. + ///
+ /// The compressed form of the JSON. + public virtual string ToString(bool sortKeys = true, string keyValueSep = ": ", string itemSep = ", ", int maxLength = int.MaxValue) + { + switch (type) + { + case Dtype.STR: + { + return StrToString((string)value, true); + } + case Dtype.FLOAT: + { + double v = (double)value; + if (double.IsInfinity(v)) + { + return (v < 0) ? "-Infinity" : "Infinity"; + } + if (double.IsNaN(v)) { return "NaN"; } + string dubstring = v.ToString(DOT_DECIMAL_SEP); + if (v == Math.Round(v) && !(v > long.MaxValue || v < long.MinValue) && dubstring.IndexOf('E') < 0) + { + // add ending ".0" to distinguish doubles equal to integers from actual integers + // unless they use exponential notation, in which case you mess things up + // by turning something like 3.123E+15 into 3.123E+15.0 (a non-JSON number representation) + return dubstring + ".0"; + } + return dubstring; + } + case Dtype.INT: return Convert.ToInt64(value).ToString(); + case Dtype.NULL: return "null"; + case Dtype.BOOL: return (bool)value ? "true" : "false"; + case Dtype.REGEX: return StrToString(((JRegex)this).regex.ToString(), true); + //case Dtype.DATETIME: return '"' + ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss.fff") + '"'; + //case Dtype.DATE: return '"' + ((DateTime)value).ToString("yyyy-MM-dd") + '"'; + default: return ((object)this).ToString(); // just show the type name for it + } + } + + /// + /// return this.value if this happens to have a string value, else this.ToString() + /// + /// + public string ValueOrToString() + { + return value is string s ? s : ToString(); + } + + internal virtual int ToStringHelper(bool sortKeys, string keyValueSep, string itemSep, StringBuilder sb, bool changePositions, int extraUtf8_bytes, int maxLength) + { + if (changePositions) + position = sb.Length + extraUtf8_bytes; + var str = ToString(); + sb.Append(str); + if (type == Dtype.STR) + return extraUtf8_bytes + JsonParser.ExtraUTF8BytesBetween(str, 1, str.Length - 1); + return extraUtf8_bytes; // only ASCII characters in non-strings + } + + /// + /// Pretty-prints the JSON with each array value and object key-value pair on a separate line, + /// and indentation proportional to nesting depth.

+ /// For JNodes that are not JArrays or JObjects, the inden argument does nothing.

+ /// The indent argument sets the number of spaces per level of depth.

+ /// If sortKeys is true, the keys of objects are printed in alphabetical order.

+ /// The style argument controls various stylistic details of pretty-printing. + /// See the documentation for the PrettyPrintStyle enum and its members. + ///
+ /// the number of spaces per level of nesting. + /// a pretty-printed JSON string + public virtual string PrettyPrint(int indent = 4, bool sortKeys = true, PrettyPrintStyle style = PrettyPrintStyle.Google, int maxLength = int.MaxValue, char indentChar = ' ') + { + return ToString(); + } + + /// + /// Called by JArray.PrettyPrintAndChangePositions and JObject.PrettyPrintAndChangePositions during recursions.

+ /// Returns the number of the final line in this node's string representation and this JNode's PrettyPrint() string.

+ /// The style argument controls various stylistic details of pretty-printing. + /// See the documentation for the PrettyPrintStyle enum and its members.

+ /// If sortKeys is true, the keys of objects are printed in alphabetical order.

+ /// maxLength is the maximum length of the string representation + ///
+ /// + /// + /// + /// + internal virtual int PrettyPrintHelper(int indent, bool sortKeys, PrettyPrintStyle style, int depth, StringBuilder sb, bool changePositions, int extraUtf8_bytes, int maxLength, char indentChar) + { + return ToStringHelper(sortKeys, ": ", ", ", sb, changePositions, extraUtf8_bytes, maxLength); + } + + /// + /// Compactly prints the JNode - see the documentation for ToString.

+ /// If sortKeys is true, the keys of objects are printed in alphabetical order. + /// keyValueSep (default ": ") is the separator between the key and the value in an object. Use ":" instead if you want minimal whitespace.

+ /// itemSep (default ", ") is the separator between key-value pairs in an object or items in an array. Use "," instead if you want minimal whitespace.

+ /// maxLength is the maximum length that this string representation can have. + ///
+ /// + public virtual string ToStringAndChangePositions(bool sortKeys = true, string keyValueSep = ": ", string itemSep = ", ", int maxLength = int.MaxValue) + { + position = 0; + return ToString(); + } + + /// + /// Pretty-prints the JNode - see documentation for PrettyPrint.

+ /// Also changes the line numbers of all the JNodes that are pretty-printed.

+ /// If sortKeys is true, the keys of objects are printed in ASCIIbetical order.

+ /// The style argument controls various stylistic details of pretty-printing. + /// See the documentation for the PrettyPrintStyle enum and its members.

+ /// maxLength is the maximum length that this string representation can have. + /// EXAMPLE: TODO

+ ///
+ /// + /// + public virtual string PrettyPrintAndChangePositions(int indent = 4, bool sortKeys = true, PrettyPrintStyle style = PrettyPrintStyle.Google, int maxLength = int.MaxValue, char indentChar = ' ') + { + position = 0; + return ToString(); + } + + public virtual int PPrintHelper(int indent, int depth, bool sortKeys, StringBuilder sb, bool changePositions, int extraUtf8_bytes, int maxLineEnd, int maxLength, char indentChar) + { + return ToStringHelper(sortKeys, ":", ",", sb, changePositions, extraUtf8_bytes, int.MaxValue); + } + + /// + /// All JSON elements follow the same algorithm when compactly printing with comments:

+ /// All comments come before all the JSON, and the JSON is compactly printed with non-minimal whitespace + /// (i.e., one space after colons in objects and one space after commas in iterables) + ///
+ public string ToStringWithComments(List comments, bool sortKeys = true) + { + comments.Sort((x, y) => x.position.CompareTo(y.position)); + var sb = new StringBuilder(); + Comment.AppendAllCommentsBeforePosition(sb, comments, 0, 0, int.MaxValue, ""); + ToStringHelper(sortKeys, ": ", ", ", sb, false, 0, int.MaxValue); + return sb.ToString(); + } + + /// + /// As ToStringWithComments, but changes each JSON element's position to reflect where it is in the UTF-8 encoded form of the generated string. + /// + public string ToStringWithCommentsAndChangePositions(List comments, bool sortKeys = true) + { + comments.Sort((x, y) => x.position.CompareTo(y.position)); + var sb = new StringBuilder(); + (_, int extraUtf8_bytes) = Comment.AppendAllCommentsBeforePosition(sb, comments, 0, 0, int.MaxValue, ""); + position = extraUtf8_bytes; + ToStringHelper(sortKeys, ": ", ", ", sb, true, extraUtf8_bytes, int.MaxValue); + return sb.ToString(); + } + + /// + /// for non-iterables, pretty-printing with comments is the same as compactly printing with comments.

+ /// For iterables, pretty-printing with comments means Google-style pretty-printing (unless prettyPrintStyle is PPrint),

+ /// with each comment having the same position relative to each JSON element and each other comment + /// that those comments did when the JSON was parsed.

+ /// If prettyPrintStyle is PPrint, the algorith works as illustrated in PrettyPrintWithCommentsHelper below. + ///
+ public string PrettyPrintWithComments(List comments, int indent = 4, bool sortKeys = true, char indentChar = ' ', PrettyPrintStyle prettyPrintStyle=PrettyPrintStyle.Google) + { + return PrettyPrintWithComentsStarter(comments, false, indent, sortKeys, indentChar, prettyPrintStyle); + } + + /// + /// As PrettyPrintWithComments, but changes the position of each JNode to wherever it is in the UTF-8 encoded form of the returned string + /// + public string PrettyPrintWithCommentsAndChangePositions(List comments, int indent = 4, bool sortKeys = true, char indentChar = ' ', PrettyPrintStyle prettyPrintStyle=PrettyPrintStyle.Google) + { + return PrettyPrintWithComentsStarter(comments, true, indent, sortKeys, indentChar, prettyPrintStyle); + } + + private string PrettyPrintWithComentsStarter(List comments, bool changePositions, int indent, bool sortKeys, char indentChar, PrettyPrintStyle prettyPrintStyle) + { + comments.Sort((x, y) => x.position.CompareTo(y.position)); + bool pprint = prettyPrintStyle == PrettyPrintStyle.PPrint; + var sb = new StringBuilder(); + (int commentIdx, int extraUtf8_bytes) = Comment.AppendAllCommentsBeforePosition(sb, comments, 0, 0, position, ""); + (commentIdx, _) = PrettyPrintWithCommentsHelper(comments, commentIdx, indent, sortKeys, 0, sb, changePositions, extraUtf8_bytes, indentChar, pprint); + sb.Append(NL); + Comment.AppendAllCommentsBeforePosition(sb, comments, commentIdx, 0, int.MaxValue, ""); // add all comments after the last JNode + return sb.ToString(); + } + + /// + /// goal is to look like this EXAMPLE: + /// + /// //comment at the beginning + /// /* multiline start comment*/ + ///{ + /// /* every comment has a newline + /// after it, even multiliners */ + /// "a": { + /// "b": { + /// "c": 2 + /// }, + /// "d": [ + /// // comment indentation is the same as + /// // whatever it comes before + /// 3, + /// [ + /// 4 + /// ] + /// ] + /// } + ///} + /// // comment at the end + /// + /// If pprint, the algorithm instead works like this:

+ /// // comment at start

+ /// [

+ /// // comment before first element

+ /// ["short", {"iterables": "get", "printed on": 1.0}, "line"],

+ /// {

+ /// "but": [

+ /// "this",

+ /// /* has a comment in it */

+ /// "and gets more lines",

+ /// true

+ /// ]

+ /// },

+ /// [

+ /// "and this array is too long",

+ /// "so it goes Google-style",

+ /// "even though it has",

+ /// [0.0, "comments"]

+ /// ]

+ /// ]

+ ///
+ public virtual (int commentIdx, int extraUtf8_bytes) PrettyPrintWithCommentsHelper(List comments, int commentIdx, int indent, bool sortKeys, int depth, StringBuilder sb, bool changePositions, int extraUtf8_bytes, char indentChar, bool pprint) + { + return (commentIdx, ToStringHelper(sortKeys, ": ", ", ", sb, changePositions, extraUtf8_bytes, int.MaxValue)); + } + + public virtual (int commentIdx, int extraUtf8_bytes) PPrintWithCommentsHelper(List comments, int commentIdx, int indent, bool sortKeys, int depth, StringBuilder sb, bool changePositions, int extraUtf8_bytes, char indentChar, int maxLineEnd) + { + return (commentIdx, ToStringHelper(sortKeys, ": ", ", ", sb, changePositions, extraUtf8_bytes, int.MaxValue)); + } + + /// + /// return -1 if and only if either of the following is true:

+ /// * compressing this JSON (non-minimal whitespace) would JSON would push sbLength over maxSbLen

+ /// * this JNode has a position greater than maxInitialPosition

+ /// If neither is true, return sbLength plus the length of this JSON's compressed string repr (non-minimal whitespace) + ///
+ public virtual int CompressedLengthAndPositionsWithinThresholds(int sbLength, int maxInitialPosition, int maxSbLen) + { + if (position >= maxInitialPosition || sbLength >= maxSbLen) + return -1; + return sbLength + ToString().Length; + } + #endregion + /// + /// A magic method called behind the scenes when sorting things.

+ /// It only works if other also implements IComparable.

+ /// Assuming this and other are sorted ascending, CompareTo should do the following:

+ /// return a negative number if this < other

+ /// return 0 if this == other

+ /// return a positive number if this > other + /// + ///
+ /// + /// If an attempt is made to compare two things of different type. + /// + public int CompareTo(object other) + { + if (other is JNode jother) + { + return CompareTo(jother.value); + } + switch (type) + { + // we could simply say value.CompareTo(other) after checking if value is null. + // It is more user-friendly to attempt to allow comparison of different numeric types, so we do this instead + case Dtype.STR: return ((string)value).CompareTo(other); + case Dtype.INT: // Convert.ToInt64 has some weirdness where it rounds half-integers to the nearest even + // so it is generally preferable to have ints and floats compared the same way + // this way e.g. "3.5 < 4" will be treated the same as "4 > 3.5", + // which is the same comparison but with different operand order. + // The only downside of this approach is that integers between + // 4.5036e15 (2 ^ 52) and 9.2234e18 (2 ^ 63) + // can be precisely represented by longs but not by doubles, + // so very large integers will have a loss of precision. + case Dtype.FLOAT: + if (!(other is long || other is double || other is bool)) + throw new ArgumentException("Can't compare numbers to non-numbers"); + return Convert.ToDouble(value).CompareTo(Convert.ToDouble(other)); + case Dtype.BOOL: + if (!(other is long || other is double || other is bool)) + throw new ArgumentException("Can't compare numbers to non-numbers"); + if ((bool)value) return (1.0).CompareTo(Convert.ToDouble(other)); + return (0.0).CompareTo(Convert.ToDouble(other)); + case Dtype.NULL: + if (other != null) + throw new ArgumentException("Cannot compare null to non-null"); + return 0; + //case Dtype.DATE: return ((DateOnly)value).CompareTo((DateOnly)other); + //case Dtype.DATETIME: return ((DateTime)value).CompareTo((DateTime)other); + case Dtype.ARR: + case Dtype.OBJ: throw new ArgumentException("Cannot compare JArrays or JObjects"); + default: throw new ArgumentException($"Cannot compare JNodes of type {type}"); + } + } + + public virtual bool Equals(JNode other) + { + return CompareTo(other) == 0; + } + + /// + /// Returns false and sets errorMessage to ex.ToString() + /// if calling this.Equals(other) throws exception ex.

+ /// If Equals throws no exception or showErrorMessage is false, errorMessage is null. + ///
+ public bool TryEquals(JNode other, out string errorMessage, bool showErrorMessage = false) + { + errorMessage = null; + try + { + return Equals(other); + } + catch (Exception ex) + { + if (showErrorMessage) + errorMessage = ex.ToString(); + return false; + } + } + + /// + /// return a deep copy of this JNode (same in every respect except memory location)

+ /// Also recursively copies all the children of a JArray or JObject. + ///
+ /// + public virtual JNode Copy() + { + //if (value is DateTime dt) + //{ + // // DateTimes are mutable, unlike all other valid JNode values. We need to deal with them separately + // return new JNode(new DateTime(dt.Ticks), type, position); + //} + return new JNode(value, type, position); + } + + #region MISC_FUNCS + /// + /// get the parent of this JNode and this JNode's parent + /// assuming that this JNode is in the tree rooted at root + /// + /// + public (object keyInParent, JNode parent) ParentAndKey(JNode root) + { + //if (extras is ExtraJNodeProperties ext + // && ext.parent != null + // && ext.parent.TryGetTarget(out JNode parent)) + //{ + // return (ext.keyInParent, parent); + //} + return ParentHierarchy(root).Last(); + } + + public List<(object keyInParent, JNode parent)> ParentHierarchy(JNode root) + { + var parents = new List(); + var keys = new List(); + //if (extras is ExtraJNodeProperties ext) + // ParentHierarchyHelperWithExtras(keys, parents); + //else + ParentHierarchyHelper(root, this, keys, parents); + return keys.Zip(parents, (k, p) => (k, p)).Reverse().ToList(); + } + + //public void ParentHierarchyHelperWithExtras(List keys, List parents) + //{ + // if (extras is ExtraJNodeProperties ext + // && ext.parent != null + // && ext.parent.TryGetTarget(out JNode parent)) + // { + // keys.Add(ext.keyInParent); + // parents.Add(parent); + // ParentHierarchyHelperWithExtras(keys, parents); + // } + //} + + public bool ParentHierarchyHelper(JNode root, JNode current, List keys, List parents) + { + if (object.ReferenceEquals(current, this)) + return true; + if (current is JArray arr) + { + for (int ii = 0; ii < arr.children.Count; ii++) + { + JNode child = arr.children[ii]; + parents.Add(arr); + keys.Add(ii); + if (ParentHierarchyHelper(root, child, keys, parents)) + return true; + keys.RemoveAt(keys.Count - 1); + parents.RemoveAt(parents.Count - 1); + } + } + else if (current is JObject obj) + { + foreach (KeyValuePair kv in obj.children) + { + parents.Add(obj); + keys.Add(kv.Key); + if (ParentHierarchyHelper(root, kv.Value, keys, parents)) + return true; + keys.RemoveAt(keys.Count - 1); + parents.RemoveAt(parents.Count - 1); + } + } + return false; + } + + private static readonly Regex DOT_COMPATIBLE_REGEX = new Regex("^[_a-zA-Z][_a-zA-Z\\d]*$"); + // "dot compatible" means a string that starts with a letter or underscore + // and contains only letters, underscores, and digits + + public bool ContainsPosition(int pos) + { + if (position == pos) + return true; + if ((type & Dtype.ARR_OR_OBJ) != 0) + return false; + //if (extras is ExtraJNodeProperties ext) + // return pos > position && pos <= ext.endPosition; + string str = ToString(); + int utf8len = (type == Dtype.STR) + ? Encoding.UTF8.GetByteCount(str) + : str.Length; + return pos > position && pos <= position + utf8len; + } + + /// + /// Get the the path to the JNode that contains position pos in a UTF-8 encoded document.

+ /// See PathToTreeNode for information on how paths are formatted. + ///
+ /// + /// + /// + /// + /// + public string PathToPosition(int pos, KeyStyle style = KeyStyle.Python) + { + return PathToPositionHelper(pos, style, new List()); + } + + public string PathToPositionHelper(int pos, KeyStyle style, List path) + { + string result; + if (ContainsPosition(pos)) + return FormatPath(path, style); + if (this is JArray arr) + { + if (arr.Length == 0) + return ""; + int ii = 0; + while (ii < arr.Length - 1 && arr[ii + 1].position <= pos) + { + ii++; + } + JNode child = arr[ii]; + path.Add(ii); + result = child.PathToPositionHelper(pos, style, path); + if (result.Length > 0) + return result; + path.RemoveAt(path.Count - 1); + } + else if (this is JObject obj) + { + foreach (KeyValuePair kv in obj.children) + { + path.Add(kv.Key); + result = kv.Value.PathToPositionHelper(pos, style, path); + if (result.Length > 0) + return result; + path.RemoveAt(path.Count - 1); + } + } + return ""; + } + + private static string FormatPath(List path, KeyStyle style) + { + StringBuilder sb = new StringBuilder(); + foreach (object member in path) + { + if (member is int ii) + { + sb.Append($"[{ii}]"); + } + else if (member is string key) + { + sb.Append(FormatKey(key, style)); + } + } + return sb.ToString(); + } + + /// + /// Get the key in square brackets or prefaced by a quote as determined by the style.

+ /// Style: one of 'p' (Python), 'j' (JavaScript), or 'r' (RemesPath)

+ /// EXAMPLES (using the JSON {"a b": [1, {"c": 2}], "d": [4]}

+ /// Using key "a b'":

+ /// - JavaScript style: ["a b'"]

+ /// - Python style: ["a b'"]

+ /// - RemesPath style: [`a b'`]

+ /// Using key "c":

+ /// - JavaScript style: .c

+ /// - RemesPath style: .c

+ /// - Python style: ['c'] + ///
+ /// + /// + /// + /// + public static string FormatKey(string key, KeyStyle style = KeyStyle.Python) + { + switch (style) + { + case KeyStyle.RemesPath: + { + if (DOT_COMPATIBLE_REGEX.IsMatch(key)) + return $".{key}"; + string escapedKey = StrToString(key, false); + string keyDubquotesUnescaped = escapedKey.Replace("\\\"", "\"").Replace("`", "\\`"); + return $"[`{keyDubquotesUnescaped}`]"; + } + case KeyStyle.JavaScript: + { + if (DOT_COMPATIBLE_REGEX.IsMatch(key)) + return $".{key}"; + string escapedKey = StrToString(key, false); + if (key.Contains('\'')) + { + return $"[\"{escapedKey}\"]"; + } + string keyDubquotesUnescaped = escapedKey.Replace("\\\"", "\""); + return $"['{keyDubquotesUnescaped}']"; + } + case KeyStyle.Python: + { + string escapedKey = StrToString(key, false); + if (escapedKey.Contains('\'')) + { + return $"[\"{escapedKey}\"]"; + } + string keyDubquotesUnescaped = escapedKey.Replace("\\\"", "\""); + return $"['{keyDubquotesUnescaped}']"; + } + default: throw new ArgumentException("style argument for PathToTreeNode must be a KeyStyle member"); + } + } + + public static Dictionary DtypeStrings = new Dictionary + { + [Dtype.SCALAR] = "scalar", + [Dtype.ITERABLE] = "iterable", + [Dtype.NUM] = "numeric", // mixed types come first, so that they're checked before pure + [Dtype.ARR] = "array", + [Dtype.BOOL] = "boolean", + [Dtype.FLOAT] = "float", + [Dtype.INT] = "integer", + [Dtype.NULL] = "null", + [Dtype.OBJ] = "object", + [Dtype.STR] = "string", + [Dtype.UNKNOWN] = "unknown", + [Dtype.SLICE] = "slice", + //[Dtype.DATE] = "date", + [Dtype.REGEX] = "regex", + //[Dtype.DATETIME] = "datetime", + }; + + /// + /// By default, a pure enum value (e.g., Dtype.INT) has INT as its string representation.

+ /// However, the bitwise OR of multiple enum values just has an integer as its string repr.

+ /// This function allows, e.g., Dtype.INT | Dtype.BOOL to be represented as "boolean|integer" + /// rather than 3 (its numeric value) + ///
+ /// + /// + public static string FormatDtype(Dtype type) + { + List typestrs = new List(); + // it's pure (or a mixture with a previously designated name) + if (DtypeStrings.TryGetValue(type, out string val)) + { + return val; + } + // it's an undesignated mixture. + // Cut it apart by making a list of each designated type/mixture it contains. + ushort typeint = (ushort)type; + foreach (Dtype typ in DtypeStrings.Keys) + { + ushort shortyp = (ushort)typ; + if ((typeint & shortyp) == shortyp) + { + typestrs.Add(DtypeStrings[typ]); + typeint -= shortyp; + // subtract to not double-count types that are in a designated mixture + } + } + return string.Join("|", typestrs); + } + + public static bool BothTypesIntersect(Dtype atype, Dtype btype, Dtype shouldMatch) + { + return (atype & shouldMatch) != 0 && (btype & shouldMatch) != 0; + } + #endregion + } + + /// + /// + /// A class representing JSON objects. + /// + [System.Diagnostics.DebuggerDisplay("JObject({ToString(maxLength: 200)})")] + public class JObject : JNode + { + public Dictionary children; + + public int Length { get { return children.Count; } } + + public JObject(int position, Dictionary children) : base(null, Dtype.OBJ, position) + { + this.children = children; + } + + /// + /// instantiates a new empty JObject + /// + public JObject() : base(null, Dtype.OBJ, 0) + { + children = new Dictionary(); + } + + /// + /// return the JNode associated with key + /// + /// + /// + public JNode this[string key] + { + get { return children[key]; } + set { children[key] = value; } + } + + public bool TryGetValue(string key, out JNode val) + { + val = null; + return !(children is null) && children.TryGetValue(key, out val); + } + + /// + public override string ToString(bool sortKeys = true, string keyValueSep = ": ", string itemSep = ", ", int maxLength = int.MaxValue) + { + var sb = new StringBuilder(7 * Length); + ToStringHelper(sortKeys, keyValueSep, itemSep, sb, false, position, maxLength); + if (sb.Length >= maxLength) + sb.Append("..."); + return sb.ToString(); + } + + internal override int ToStringHelper(bool sortKeys, string keyValueSep, string itemSep, StringBuilder sb, bool changePositions, int extraUtf8_bytes, int maxLength) + { + if (sb.Length >= maxLength) + return -1; + if (changePositions) position = sb.Length + extraUtf8_bytes; + sb.Append('{'); + int ctr = 0; + IEnumerable keys; + if (sortKeys) + { + keys = children.Keys.ToArray(); + Array.Sort((string[])keys, StringComparer.CurrentCultureIgnoreCase); + } + else keys = children.Keys; + foreach (string k in keys) + { + JNode v = children[k]; + string escapedK = StrToString(k, false); + sb.Append('"'); + sb.Append(escapedK); + sb.Append('"'); + sb.Append(keyValueSep); + extraUtf8_bytes += JsonParser.ExtraUTF8BytesBetween(escapedK, 0, escapedK.Length); + extraUtf8_bytes = v.ToStringHelper(sortKeys, keyValueSep, itemSep, sb, changePositions, extraUtf8_bytes, maxLength); + if (sb.Length >= maxLength) + return -1; + if (++ctr < children.Count) + sb.Append(itemSep); + } + sb.Append('}'); + return extraUtf8_bytes; + } + + /// + public override string PrettyPrint(int indent = 4, bool sortKeys = true, PrettyPrintStyle style = PrettyPrintStyle.Google, int maxLength = int.MaxValue, char indentChar = ' ') + { + var sb = new StringBuilder(8 * Length); + PrettyPrintHelper(indent, sortKeys, style, 0, sb, false, position, maxLength, indentChar); + if (sb.Length >= maxLength) + sb.Append("..."); + return sb.ToString(); + } + + /// + internal override int PrettyPrintHelper(int indent, bool sortKeys, PrettyPrintStyle style, int depth, StringBuilder sb, bool changePositions, int extraUtf8_bytes, int maxLength, char indentChar) + { + if (sb.Length >= maxLength) + return -1; + string dent = new string(indentChar, indent * depth); + int ctr = 0; + IEnumerable keys; + if (sortKeys) + { + keys = children.Keys.ToArray(); + Array.Sort((string[])keys, StringComparer.CurrentCultureIgnoreCase); + } + else keys = children.Keys; + switch (style) + { + case PrettyPrintStyle.Whitesmith: + sb.Append(dent); + if (changePositions) position = sb.Length + extraUtf8_bytes; + sb.Append('{'); + sb.Append(NL); + foreach (string k in keys) + { + JNode v = children[k]; + extraUtf8_bytes += AppendKeyAndGetUtf8Extra(sb, dent, k, "\":"); + if (v is JObject || v is JArray) + sb.Append(NL); + else + sb.Append(' '); + extraUtf8_bytes = v.PrettyPrintHelper(indent, sortKeys, style, depth + 1, sb, changePositions, extraUtf8_bytes, maxLength, indentChar); + if (sb.Length >= maxLength) + return -1; + if (++ctr < children.Count) + sb.Append(','); + sb.Append(NL); + } + sb.Append($"{dent}}}"); + break; + case PrettyPrintStyle.Google: + if (changePositions) position = sb.Length + extraUtf8_bytes; + sb.Append('{'); + sb.Append(NL); + string extraDent = new string(indentChar, (depth + 1) * indent); + foreach (string k in keys) + { + JNode v = children[k]; + extraUtf8_bytes += AppendKeyAndGetUtf8Extra(sb, extraDent, k, "\": "); + extraUtf8_bytes = v.PrettyPrintHelper(indent, sortKeys, style, depth + 1, sb, changePositions, extraUtf8_bytes, maxLength, indentChar); + if (sb.Length >= maxLength) + return -1; + if (++ctr < children.Count) + sb.Append(','); + sb.Append(NL); + } + sb.Append($"{dent}}}"); + break; + case PrettyPrintStyle.PPrint: + if (changePositions) position = sb.Length + extraUtf8_bytes; + int childDentLen = (depth + 1) * indent; + sb.Append('{'); + sb.Append(NL); + extraDent = new string(indentChar, childDentLen); + foreach (string k in keys) + { + int maxLineEnd = sb.Length + PPRINT_LINE_LENGTH; + JNode v = children[k]; + extraUtf8_bytes += AppendKeyAndGetUtf8Extra(sb, extraDent, k, "\": "); + extraUtf8_bytes = v.PPrintHelper(indent, depth, sortKeys, sb, changePositions, extraUtf8_bytes, maxLineEnd, maxLength, indentChar); + if (sb.Length >= maxLength) + return -1; + if (++ctr < children.Count) + sb.Append(','); + sb.Append(NL); + } + sb.Append($"{dent}}}"); + break; + default: throw new ArgumentOutOfRangeException("style"); + } + return extraUtf8_bytes; + } + + private static int AppendKeyAndGetUtf8Extra(StringBuilder sb, string dent, string k, string closeQuoteAndColon) + { + string escapedK = StrToString(k, false); + sb.Append(dent); + sb.Append('"'); + sb.Append(escapedK); + sb.Append(closeQuoteAndColon); + return JsonParser.ExtraUTF8BytesBetween(escapedK, 0, escapedK.Length); + } + + /// + public override string ToStringAndChangePositions(bool sortKeys = true, string keyValueSep = ": ", string itemSep = ", ", int maxLength = int.MaxValue) + { + var sb = new StringBuilder(7 * Length); + ToStringHelper(sortKeys, keyValueSep, itemSep, sb, true, 0, maxLength); + if (sb.Length >= maxLength) + sb.Append("..."); + return sb.ToString(); + } + + /// + public override string PrettyPrintAndChangePositions(int indent = 4, bool sortKeys = true, PrettyPrintStyle style = PrettyPrintStyle.Google, int maxLength = int.MaxValue, char indentChar = ' ') + { + var sb = new StringBuilder(8 * Length); + PrettyPrintHelper(indent, sortKeys, style, 0, sb, true, 0, maxLength, indentChar); + if (sb.Length >= maxLength) + sb.Append("..."); + return sb.ToString(); + } + + public override int PPrintHelper(int indent, int depth, bool sortKeys, StringBuilder sb, bool changePositions, int extraUtf8_bytes, int maxLineEnd, int maxLength, char indentChar) + { + if (Length > PPRINT_LINE_LENGTH / 8) // an non-minimal-whitespace-compressed object has at least 8 chars per element ("\"a\": 1, ") + return PrettyPrintHelper(indent, sortKeys, PrettyPrintStyle.PPrint, depth + 1, sb, changePositions, extraUtf8_bytes, maxLength, indentChar); + int ogSbLen = sb.Length; + int childUtf8_extra = ToStringHelper(sortKeys, ": ", ", ", sb, changePositions, extraUtf8_bytes, maxLineEnd); + if (childUtf8_extra == -1) + { + // child is too long, so we do PPrint-style printing of it + sb.Length = ogSbLen; + return PrettyPrintHelper(indent, sortKeys, PrettyPrintStyle.PPrint, depth + 1, sb, changePositions, extraUtf8_bytes, maxLength, indentChar); + } + // child is small enough when compact, so use compact repr + return childUtf8_extra; + } + + /// + public override (int commentIdx, int extraUtf8_bytes) PrettyPrintWithCommentsHelper(List comments, int commentIdx, int indent, bool sortKeys, int depth, StringBuilder sb, bool changePositions, int extraUtf8_bytes, char indentChar, bool pprint) + { + if (changePositions) position = sb.Length + extraUtf8_bytes; + int ctr = 0; + IEnumerable keys; + if (sortKeys) + { + keys = children.Keys.ToArray(); + Array.Sort((string[])keys, StringComparer.CurrentCultureIgnoreCase); + } + else keys = children.Keys; + sb.Append('{'); + sb.Append(NL); + string dent = new string(indentChar, indent * depth); + string extraDent = new string(indentChar, (depth + 1) * indent); + foreach (string k in keys) + { + JNode v = children[k]; + (commentIdx, extraUtf8_bytes) = Comment.AppendAllCommentsBeforePosition(sb, comments, commentIdx, extraUtf8_bytes, v.position, extraDent); + int pprintLineEnd = sb.Length + PPRINT_LINE_LENGTH; + extraUtf8_bytes += AppendKeyAndGetUtf8Extra(sb, extraDent, k, "\": "); + (commentIdx, extraUtf8_bytes) = pprint + ? v.PPrintWithCommentsHelper(comments, commentIdx, indent, sortKeys, depth + 1, sb, changePositions, extraUtf8_bytes, indentChar, pprintLineEnd) + : v.PrettyPrintWithCommentsHelper(comments, commentIdx, indent, sortKeys, depth + 1, sb, changePositions, extraUtf8_bytes, indentChar, false); + if (++ctr < children.Count) + sb.Append(','); + sb.Append(NL); + } + sb.Append($"{dent}}}"); + return (commentIdx, extraUtf8_bytes); + } + + public override (int commentIdx, int extraUtf8_bytes) PPrintWithCommentsHelper(List comments, int commentIdx, int indent, bool sortKeys, int depth, StringBuilder sb, bool changePositions, int extraUtf8_bytes, char indentChar, int maxSbLen) + { + if (Length > PPRINT_LINE_LENGTH / 8) // an non-minimal-whitespace-compressed object has at least 8 chars per element ("\"a\": 1, ") + goto printOnMultipleLines; + int ogSbLen = sb.Length; + int nextCommentPos = commentIdx >= comments.Count ? int.MaxValue : comments[commentIdx].position; + if (CompressedLengthAndPositionsWithinThresholds(ogSbLen, nextCommentPos, maxSbLen) == -1) + { + // child is too long or has a comment inside; need to print on multiple lines + goto printOnMultipleLines; + } + // child is small enough when compact and has no comments inside, so use compact repr + return (commentIdx, ToStringHelper(sortKeys, ": ", ", ", sb, changePositions, extraUtf8_bytes, int.MaxValue)); + printOnMultipleLines: + return PrettyPrintWithCommentsHelper(comments, commentIdx, indent, sortKeys, depth, sb, changePositions, extraUtf8_bytes, indentChar, true); + } + + /// + public override int CompressedLengthAndPositionsWithinThresholds(int sbLength, int maxInitialPosition, int maxSbLen) + { + if (position >= maxInitialPosition || sbLength >= maxSbLen) + return -1; + int ctr = 0; + sbLength += 1; // opening '{' + foreach (string key in children.Keys) + { + JNode child = children[key]; + sbLength += StrToString(key, true).Length + 2; // json-encoded key, then ": " + sbLength = child.CompressedLengthAndPositionsWithinThresholds(sbLength, maxInitialPosition, maxSbLen); + if (sbLength == -1 || sbLength >= maxSbLen) + return -1; + sbLength += ctr < children.Count - 1 + ? 2 // ", " between key-value pairs + : 1; // closing "}" + } + return sbLength; + } + + /// + /// if this JObject represents an ini file, all the values must be strings. Calling this method ensures that this is so. + /// + /// + public void StringifyAllValuesInIniFile() + { + var sectionKeysToChange = new List<(string sectionName, string key, string newValue)>(); + foreach (KeyValuePair kv in children) + { + if (!(kv.Value is JObject section)) + { + throw new InvalidOperationException("Only objects where all children are objects with only string values can be converted to ini files"); + } + foreach (KeyValuePair sectKv in section.children) + { + string key = sectKv.Key; + JNode value = sectKv.Value; + if (!(value.value is string)) + { + sectionKeysToChange.Add((kv.Key, key, value.ToString())); + } + } + } + foreach ((string sectionName, string key, string newValue) in sectionKeysToChange) + { + JObject section = (JObject)this[sectionName]; + section[key] = new JNode(newValue); + } + } + + /// + /// dump this JObject as an ini file + /// + public string ToIniFile(List comments) + { + var sb = new StringBuilder(); + int positionInComments = 0; + int utf8ExtraBytes = 0; + foreach (KeyValuePair kv in children) + { + string header = kv.Key; + if (!(kv.Value is JObject section)) + { + throw new InvalidOperationException("Only objects where all children are objects with only string values can be converted to ini files"); + } + // section is treated as beginning just before the open squarebrace of the header + (positionInComments, utf8ExtraBytes) = Comment.AppendAllCommentsBeforePosition(sb, comments, positionInComments, utf8ExtraBytes, section.position, "", DocumentType.INI); + sb.Append($"[{header}]\r\n"); + utf8ExtraBytes += JsonParser.ExtraUTF8BytesBetween(header, 0, header.Length); + foreach (KeyValuePair sectKv in section.children) + { + string key = sectKv.Key; + JNode value = sectKv.Value; + if (!(value.value is string valueStr)) + { + throw new InvalidOperationException("Only objects where all children are objects with only string values can be converted to ini files"); + } + (positionInComments, utf8ExtraBytes) = Comment.AppendAllCommentsBeforePosition(sb, comments, positionInComments, utf8ExtraBytes, value.position, "", DocumentType.INI); + sb.Append($"{key}={valueStr}\r\n"); + utf8ExtraBytes += JsonParser.ExtraUTF8BytesBetween(key, 0, key.Length); + utf8ExtraBytes += JsonParser.ExtraUTF8BytesBetween(valueStr, 0, valueStr.Length); + } + } + Comment.AppendAllCommentsBeforePosition(sb, comments, positionInComments, utf8ExtraBytes, int.MaxValue, "", DocumentType.INI); + return sb.ToString(); + } + + /// + /// Returns true if and only if other is a JObject with all the same key-value pairs.

+ /// Throws an ArgumentException if other is not a JObject. + ///
+ /// Another JObject + /// + /// + public override bool Equals(JNode other) + { + if (!(other is JObject othobj)) + { + throw new ArgumentException($"Cannot compare object {ToString()} to non-object {other.ToString()}"); + } + if (children.Count != othobj.children.Count) + { + return false; + } + foreach (KeyValuePair kv in children) + { + bool otherHaskey = othobj.children.TryGetValue(kv.Key, out JNode valobj); + if (!otherHaskey || !kv.Value.Equals(valobj)) + { + return false; + } + } + return true; + } + + /// + public override JNode Copy() + { + JObject copy = new JObject(position, new Dictionary(children.Count)); + foreach (KeyValuePair kv in children) + { + copy[kv.Key] = kv.Value.Copy(); + } + return copy; + } + } + + [System.Diagnostics.DebuggerDisplay("JArray({ToString(maxLength: 200)})")] + public class JArray : JNode + { + public List children; + + public int Length { get { return children.Count; } } + + public JArray(int position, List children) : base(null, Dtype.ARR, position) + { + this.children = children; + } + + /// + /// instantiates a new empty JArray + /// + public JArray() : base(null, Dtype.ARR, 0) + { + children = new List(); + } + + /// + /// return the JNode associated with index + /// + /// + /// + public JNode this[int index] + { + get { return children[index]; } + set { children[index] = value; } + } + + /// + public override string ToString(bool sortKeys = true, string keyValueSep = ": ", string itemSep = ", ", int maxLength = int.MaxValue) + { + var sb = new StringBuilder(4 * Length); + ToStringHelper(sortKeys, keyValueSep, itemSep, sb, false, 0, maxLength); + if (sb.Length >= maxLength) + sb.Append("..."); + return sb.ToString(); + } + + /// + public override string PrettyPrint(int indent = 4, bool sortKeys = true, PrettyPrintStyle style = PrettyPrintStyle.Google, int maxLength = int.MaxValue, char indentChar = ' ') + { + var sb = new StringBuilder(6 * Length); + PrettyPrintHelper(indent, sortKeys, style, 0, sb, false, 0, maxLength, indentChar); + if (sb.Length >= maxLength) + sb.Append("..."); + return sb.ToString(); + } + + internal override int ToStringHelper(bool sortKeys, string keyValueSep, string itemSep, StringBuilder sb, bool changePositions, int extraUtf8_bytes, int maxLength) + { + if (sb.Length >= maxLength) + return -1; + if (changePositions) position = sb.Length + extraUtf8_bytes; + sb.Append('['); + int ctr = 0; + foreach (JNode v in children) + { + extraUtf8_bytes = v.ToStringHelper(sortKeys, keyValueSep, itemSep, sb, changePositions, extraUtf8_bytes, maxLength); + if (sb.Length >= maxLength) + return -1; + if (++ctr < children.Count) + sb.Append(itemSep); + } + sb.Append(']'); + return extraUtf8_bytes; + } + + /// + public override string ToStringAndChangePositions(bool sortKeys = true, string keyValueSep = ": ", string itemSep = ", ", int maxLength = int.MaxValue) + { + var sb = new StringBuilder(4 * Length); + ToStringHelper(sortKeys, keyValueSep, itemSep, sb, true, 0, maxLength); + if (sb.Length >= maxLength) + sb.Append("..."); + return sb.ToString(); + } + + /// + public override string PrettyPrintAndChangePositions(int indent = 4, bool sortKeys = true, PrettyPrintStyle style = PrettyPrintStyle.Google, int maxLength = int.MaxValue, char indentChar = ' ') + { + var sb = new StringBuilder(6 * Length); + PrettyPrintHelper(indent, sortKeys, style, 0, sb, true, 0, maxLength, indentChar); + if (sb.Length >= maxLength) + sb.Append("..."); + return sb.ToString(); + } + + /// + internal override int PrettyPrintHelper(int indent, bool sortKeys, PrettyPrintStyle style, int depth, StringBuilder sb, bool changePositions, int extraUtf8_bytes, int maxLength, char indentChar) + { + if (sb.Length >= maxLength) + return -1; + string dent = new string(indentChar, indent * depth); + switch (style) + { + case PrettyPrintStyle.Whitesmith: + sb.Append(dent); + if (changePositions) position = sb.Length + extraUtf8_bytes; + sb.Append('['); + sb.Append(NL); + int ctr = 0; + foreach (JNode v in children) + { + if (!(v is JObject || v is JArray)) + sb.Append(dent); + extraUtf8_bytes = v.PrettyPrintHelper(indent, sortKeys, style, depth + 1, sb, changePositions, extraUtf8_bytes, maxLength, indentChar); + if (sb.Length >= maxLength) + return -1; + if (++ctr < children.Count) + sb.Append(','); + sb.Append(NL); + } + sb.Append($"{dent}]"); + break; + case PrettyPrintStyle.Google: + if (changePositions) position = sb.Length + extraUtf8_bytes; + sb.Append('['); + sb.Append(NL); + string extraDent = new string(indentChar, (depth + 1) * indent); + ctr = 0; + foreach (JNode v in children) + { + sb.Append(extraDent); + extraUtf8_bytes = v.PrettyPrintHelper(indent, sortKeys, style, depth + 1, sb, changePositions, extraUtf8_bytes, maxLength, indentChar); + if (sb.Length >= maxLength) + return -1; + if (++ctr < children.Count) + sb.Append(','); + sb.Append(NL); + } + sb.Append($"{dent}]"); + break; + case PrettyPrintStyle.PPrint: + if (changePositions) position = sb.Length + extraUtf8_bytes; + int childDentLen = (depth + 1) * indent; + sb.Append('['); + sb.Append(NL); + extraDent = new string(indentChar, childDentLen); + ctr = 0; + foreach (JNode v in children) + { + int maxLineEnd = sb.Length + PPRINT_LINE_LENGTH; + sb.Append(extraDent); + extraUtf8_bytes = v.PPrintHelper(indent, depth, sortKeys, sb, changePositions, extraUtf8_bytes, maxLineEnd, maxLength, indentChar); + if (sb.Length >= maxLength) + return -1; + if (++ctr < children.Count) + sb.Append(','); + sb.Append(NL); + } + sb.Append($"{dent}]"); + break; + default: throw new ArgumentOutOfRangeException("style"); + } + return extraUtf8_bytes; + } + + public override int PPrintHelper(int indent, int depth, bool sortKeys, StringBuilder sb, bool changePositions, int extraUtf8_bytes, int maxLineEnd, int maxLength, char indentChar) + { + if (Length > PPRINT_LINE_LENGTH / 3) // an non-minimal-whitespace-compressed array has at least 3 chars per element ("1, ") + return PrettyPrintHelper(indent, sortKeys, PrettyPrintStyle.PPrint, depth + 1, sb, changePositions, extraUtf8_bytes, maxLength, indentChar); + int ogSbLen = sb.Length; + int childUtf8_extra = ToStringHelper(sortKeys, ": ", ", ", sb, changePositions, extraUtf8_bytes, maxLineEnd); + if (childUtf8_extra == -1) + { + // child is too long, so we do PPrint-style printing of it + sb.Length = ogSbLen; + return PrettyPrintHelper(indent, sortKeys, PrettyPrintStyle.PPrint, depth + 1, sb, changePositions, extraUtf8_bytes, maxLength, indentChar); + } + // child is small enough when compact, so use compact repr + return childUtf8_extra; + } + + public override (int commentIdx, int extraUtf8_bytes) PrettyPrintWithCommentsHelper(List comments, int commentIdx, int indent, bool sortKeys, int depth, StringBuilder sb, bool changePositions, int extraUtf8_bytes, char indentChar, bool pprint) + { + if (changePositions) position = sb.Length + extraUtf8_bytes; + sb.Append('['); + sb.Append(NL); + string dent = new string(indentChar, indent * depth); + string extraDent = new string(indentChar, (depth + 1) * indent); + int ctr = 0; + foreach (JNode v in children) + { + (commentIdx, extraUtf8_bytes) = Comment.AppendAllCommentsBeforePosition(sb, comments, commentIdx, extraUtf8_bytes, v.position, extraDent); + int maxLineEnd = sb.Length + PPRINT_LINE_LENGTH; + sb.Append(extraDent); + (commentIdx, extraUtf8_bytes) = pprint + ? v.PPrintWithCommentsHelper(comments, commentIdx, indent, sortKeys, depth + 1, sb, changePositions, extraUtf8_bytes, indentChar, maxLineEnd) + : v.PrettyPrintWithCommentsHelper(comments, commentIdx, indent, sortKeys, depth + 1, sb, changePositions, extraUtf8_bytes, indentChar, false); + if (++ctr < children.Count) + sb.Append(','); + sb.Append(NL); + } + sb.Append($"{dent}]"); + return (commentIdx, extraUtf8_bytes); + } + + public override (int commentIdx, int extraUtf8_bytes) PPrintWithCommentsHelper(List comments, int commentIdx, int indent, bool sortKeys, int depth, StringBuilder sb, bool changePositions, int extraUtf8_bytes, char indentChar, int maxSbLen) + { + if (Length > PPRINT_LINE_LENGTH / 3) // an non-minimal-whitespace-compressed array has at least 3 chars per element ("1, ") + goto printOnMultipleLines; + int ogSbLen = sb.Length; + int nextCommentPos = commentIdx >= comments.Count ? int.MaxValue : comments[commentIdx].position; + if (CompressedLengthAndPositionsWithinThresholds(ogSbLen, nextCommentPos, maxSbLen) == -1) + { + // child is too long or has a comment inside; need to print on multiple lines + goto printOnMultipleLines; + } + // child is small enough when compact and has no comments inside, so use compact repr + return (commentIdx, ToStringHelper(sortKeys, ": ", ", ", sb, changePositions, extraUtf8_bytes, int.MaxValue)); + printOnMultipleLines: + return PrettyPrintWithCommentsHelper(comments, commentIdx, indent, sortKeys, depth, sb, changePositions, extraUtf8_bytes, indentChar, true); + } + + /// + public override int CompressedLengthAndPositionsWithinThresholds(int sbLength, int maxInitialPosition, int maxSbLen) + { + if (position >= maxInitialPosition || sbLength >= maxSbLen) + return -1; + sbLength += 1; // opening '[' + for (int ii = 0; ii < children.Count; ii++) + { + JNode child = children[ii]; + sbLength = child.CompressedLengthAndPositionsWithinThresholds(sbLength, maxInitialPosition, maxSbLen); + if (sbLength == -1 || sbLength >= maxSbLen) + return -1; + sbLength += ii < children.Count - 1 + ? 2 // ", " between elements + : 1; // closing "]" + } + return sbLength; + } + + /// + /// Returns true if and only if other is a JArray such that + /// other[i] == this[i] for all i < this.Length.

+ /// Throws an ArgumentException if other is not a JArray. + ///
+ /// Another JArray + /// + /// + public override bool Equals(JNode other) + { + if (!(other is JArray otharr)) + { + throw new ArgumentException($"Cannot compare array {ToString()} to non-array {other.ToString()}"); + } + if (children.Count != otharr.children.Count) + { + return false; + } + for (int ii = 0; ii < children.Count; ii++) + { + JNode val = children[ii]; + JNode othval = otharr[ii]; + if (!val.Equals(othval)) + { + return false; + } + } + return true; + } + + /// + public override JNode Copy() + { + JArray copy = new JArray(position, new List(children.Count)); + foreach (JNode child in children) + { + copy.children.Add(child.Copy()); + } + return copy; + } + + /// + /// Return a '\n'-delimited JSON Lines document + /// where the i^th line has the i^th element in this JArray. + /// + /// + public string ToJsonLines(bool sortKeys = true, string keyValueSep = ": ", string itemSep = ", ") + { + StringBuilder sb = new StringBuilder(); + for (int ii = 0; ii < children.Count; ii++) + { + sb.Append(children[ii].ToString(sortKeys, keyValueSep, itemSep)); + if (ii < children.Count - 1) + sb.Append('\n'); + } + return sb.ToString(); + } + } + + [System.Diagnostics.DebuggerDisplay("JRegex({regex})")] + /// + /// A holder for Regex objects (assigned to the regex property).

+ /// The value is always null and the type is always Dtype.REGEX. + ///
+ public class JRegex : JNode + { + // has to be a separate property because Regex objects do not implement IComparable + public Regex regex; + + public JRegex(Regex regex) : base(null, Dtype.REGEX, 0) + { + this.regex = regex; + } + } + + [System.Diagnostics.DebuggerDisplay("JSlicer({slicer})")] + /// + /// A holder for arrays of 1-3 nullable ints. This is a convenience class for parsing of Remespath queries. + /// + public class JSlicer : JNode + { + // has to be a separate property because arrays don't implement IComparable + public int?[] slicer; + + public JSlicer(int?[] slicer) : base(null, Dtype.SLICE, 0) + { + this.slicer = slicer; + } + } + + public readonly struct Comment + { + public readonly string content; + public readonly bool isMultiline; + public readonly int position; + + public Comment(string content, bool isMultiline, int position) + { + this.content = content; + this.isMultiline = isMultiline; + this.position = position; + } + + public override string ToString() + { + return $"Comment(content=\"{content}\", isMultiline={isMultiline}, position={position})"; + } + + /// + /// appends the comment representation (on its own line) of a comment to sb

+ /// (e.g., "//" + comment.content for single-line comments in JSON, "/*" + comment.content + "*/\r\n" for multiline comments

+ /// and returns the number of extra utf8 bytes in the comment's content. + ///
+ public static int ToStringHelper(StringBuilder sb, Comment comment, string singleLineCommentStart) + { + sb.Append(comment.isMultiline ? "/*" : singleLineCommentStart); + sb.Append(comment.content); + if (comment.isMultiline) + sb.Append("*/"); + sb.Append(JNode.NL); + return JsonParser.ExtraUTF8BytesBetween(comment.content, 0, comment.content.Length); + } + + public static readonly Dictionary singleLineCommentStarts = new Dictionary + { + [DocumentType.JSON] = "//", + [DocumentType.JSONL] = "//", + [DocumentType.INI] = ";", + }; + + /// + /// assumes that comments are sorted by comment.position ascending

+ /// Keeps appending the JSON comment representations of the comments (as discussed in ToStringHelper above), each on a separate line preceded by dent

+ /// to sb while commentIdx is less than comments.Count + /// and comments[commentIdx].position < position + ///
+ public static (int commentIdx, int extraUtf8_bytes) AppendAllCommentsBeforePosition(StringBuilder sb, List comments, int commentIdx, int extraUtf8_bytes, int position, string dent, DocumentType commentType = DocumentType.JSON) + { + if (!singleLineCommentStarts.TryGetValue(commentType, out string singleLineCommentStart)) + throw new ArgumentException($"{commentType} is not a valid comment type"); + for (; commentIdx < comments.Count; commentIdx++) + { + Comment comment = comments[commentIdx]; + if (comment.position > position) + break; + sb.Append(dent); + extraUtf8_bytes += ToStringHelper(sb, comment, singleLineCommentStart); + } + return (commentIdx, extraUtf8_bytes); + } + } + + /// + /// Extra properties that can improve the performance of certain methods + /// and that the parser may choose to add when parsing a document. + /// Currently not in use + /// + public class ExtraJNodeProperties + { + /// + /// null if this is the root + /// + public WeakReference parent; + + /// + /// The end position of this JNode.

+ /// In most cases this will just be its position + /// plus the length of its string representation. + ///
+ public int endPosition; + + /// + /// either an int (index in parent array) + /// or string (key in parent object) + /// or null (no parent) + /// + public object keyInParent; + + public ExtraJNodeProperties(JNode parent, int endPosition, object keyInParent) + { + this.parent = new WeakReference(parent); + this.endPosition = endPosition; + this.keyInParent = keyInParent; + } + } +} diff --git a/nppRandomStringGenerator/JSON_Tools/JsonParser.cs b/nppRandomStringGenerator/JSON_Tools/JsonParser.cs new file mode 100644 index 0000000..29078e3 --- /dev/null +++ b/nppRandomStringGenerator/JSON_Tools/JsonParser.cs @@ -0,0 +1,1725 @@ +/* +A parser and linter for JSON. +This was copied from https://github.com/molsonkiko/JsonToolsNppPlugin/blob/9eadb9f8995215bd0eb4f3349e99a29d2f35061d/JsonToolsNppPlugin/JSONTools/JsonParser.cs with minor changes to naming. +*/ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; +using NppPluginNET.Utils; + +namespace NppPluginNET.JSON_Tools +{ + /// + /// An exception that may be thrown when the parser encounters syntactically invalid JSON. + /// Subclasses FormatException. + /// + public class JsonParserException : FormatException + { + public new string Message { get; set; } + public char CurChar { get; set; } + public int Position { get; set; } + + public JsonParserException(string Message, char c, int pos) + { + this.Message = Message; + this.CurChar = c; + this.Position = pos; + } + + public override string ToString() + { + return $"{Message} at position {Position} (char {JsonLint.CharDisplay(CurChar)})"; + } + } + + /// + /// A syntax error caught and logged by the linter. + /// + public struct JsonLint + { + public string message; + /// + /// the position of the error in the UTF-8 encoding of the document + /// + public int pos; + /// + /// the UTF-16 character where the error began + /// + public char curChar; + public ParserState severity; + + /// + /// + /// + /// + /// the position of the error in the UTF8 encoding of the JSON document + /// the UTF-16 character where the error began + /// + public JsonLint(string message, int pos, char curChar, ParserState severity) + { + this.message = message; + this.pos = pos; + this.curChar = curChar; + this.severity = severity; + } + + public override string ToString() + { + return $"Syntax error (severity = {severity}) at position {pos} (char {CharDisplay(curChar)}): {message}"; + } + + /// + /// Display a char wrapped in singlequotes in a way that makes it easily recognizable. + /// For example, '\n' is represented as '\n' and '\'' is represented as '\''. + /// + /// + /// + public static string CharDisplay(char c) + { + switch (c) + { + case '\x00': return "'\\x00'"; + case '\t': return "'\\t'"; + case '\r': return "'\\r'"; + case '\n': return "'\\n'"; + case '\'': return "'\\''"; + default: return $"'{c}'"; + } + } + + /// + /// the object {"message": this.message, "position": this.pos, "severity": this.severity} + /// + public JNode ToJson() + { + return new JObject(0, new Dictionary + { + ["message"] = new JNode(message), + ["position"] = new JNode((long)pos), + ["severity"] = new JNode(severity.ToString()), + }); + } + } + + /// + /// Any errors above this level are reported by a JsonParser.

+ /// The integer value of a state reflects how seriously the input deviates from the original JSON spec. + ///
+ public enum LoggerLevel + { + /// + /// Valid according to the exact original JSON specification.

+ /// This is pretty annoying to use because horizontal tabs ('\t') are forbidden. + ///
+ STRICT, + /// + /// Valid according to a slightly relaxed version of the JSON specification.

+ /// In addition to the JSON spec, it tolerates:

+ /// * control characters with ASCII codes below 0x20 + ///
+ OK, + /// + /// Everything at the OK level plus NaN, Infinity, and -Infinity. + /// + NAN_INF, + /// + /// Everything at the NAN_INF level, plus JavaScript comments.

+ /// Note that this differs slightly from the standard JSONC spec + /// because NaN and +/-Infinity are not part of that spec. + ///
+ JSONC, + /// + /// JSON that follows the specification described here: https://json5.org/

+ /// Includes everything at the JSONC level, plus various things, including:

+ /// * unquoted object keys

+ /// * comma after last element of iterable

+ /// * singlequoted strings + ///
+ JSON5, + } + + /// + /// the sequence of states the JSON parser can be in.

+ /// The first five states (STRICT, OK, NAN_INF, JSONC, JSON5) have the same + /// meaning as in the LoggerLevel enum. + /// The last two states (BAD and FATAL) reflect errors that are + /// always logged and thus do not belong in the LoggerLevel enum. + ///
+ public enum ParserState + { + /// + /// see LoggerLevel.STRICT + /// + STRICT, + /// + /// see LoggerLevel.OK + /// + OK, + /// + /// see LoggerLevel.NAN_INF + /// + NAN_INF, + /// + /// see LoggerLevel.JSONC + /// + JSONC, + /// + /// see LoggerLevel.JSON5 + /// + JSON5, + /// + /// JSON with syntax errors that my parser can handle but that should always be logged, such as:

+ /// * unterminated strings

+ /// * missing commas after array elements

+ /// * Python-style single-line comments (start with '#') + ///
+ BAD, + /// + /// errors that are always fatal, such as:

+ /// * recursion depth hits the recursion limit

+ /// * empty input + ///
+ FATAL, + /// + /// reserved for JSON Schema validation errors + /// + SCHEMA + } + + /// + /// Parses a JSON document into a tree. + /// + public class JsonParser + { + /// + /// need to track recursion depth because stack overflow causes a panic that makes Notepad++ crash + /// + public const int MAX_RECURSION_DEPTH = 512; + + #region JSON_PARSER_ATTRS + ///// + ///// If true, any strings in the standard formats of ISO 8601 dates (yyyy-MM-dd) and datetimes (yyyy-MM-dd hh:mm:ss.sss) + ///// will be automatically parsed as the appropriate type. + ///// Not currently supported. May never be. + ///// + //public bool parseDatetimes; + + /// + /// If line is not null, most forms of invalid syntax will not cause the parser to stop,

+ /// but instead the syntax error will be recorded in a list. + ///
+ public List lint; + + /// + /// position in JSON string + /// + public int ii; + + private bool _rememberComments; + + public bool rememberComments { + get { return _rememberComments; } + set + { + _rememberComments = value; + comments = value ? new List() : null; + } + } + + public List comments; + + /// + /// the number of extra bytes in the UTF-8 encoding of the text consumed + /// so far.

+ /// For example, if

+ /// "words": "Thế nào rồi?"

+ /// has been consumed, utf8ExtraBytes is 5 because all the characters + /// are 1-byte ASCII

+ /// except 'ồ' and 'ế', which are both 3-byte characters

+ /// and 'à' which is a 2-byte character + ///
+ private int utf8ExtraBytes; + + public ParserState state { get; private set; } + + /// + /// errors above this + /// + public LoggerLevel loggerLevel; + + /// + /// Any error above the logger level causes an error to be thrown.

+ /// If false, parse functions will return everything logged up until a fatal error + /// and will parse everything if there were no fatal errors.

+ /// Present primarily for backwards compatibility. + ///
+ public bool throwIfLogged; + + public bool throwIfFatal; + + /// + /// attach ExtraJNodeProperties to each JNode parsed + /// + public bool includeExtraProperties; + + /// + /// the number of bytes in the utf-8 representation + /// before the current position in the current document + /// + public int utf8Pos { get { return ii + utf8ExtraBytes; } } + + public bool fatal + { + get { return state == ParserState.FATAL; } + } + + public bool hasLogged + { + get { return (int)state > (int)loggerLevel; } + } + + public bool exitedEarly + { + get { return fatal || (throwIfLogged && hasLogged); } + } + + /// + /// if parsing failed, this will be the final error logged. If parsing succeeded, this is null. + /// + public JsonLint? fatalError + { + get + { + if (exitedEarly) + return lint[lint.Count - 1]; + return null; + } + } + + public JsonParser(LoggerLevel loggerLevel = LoggerLevel.NAN_INF, bool throwIfLogged = true, bool throwIfFatal = true, bool rememberComments = false) + //, bool includeExtraProperties = false) + { + this.loggerLevel = loggerLevel; + this.throwIfLogged = throwIfLogged; + this.throwIfFatal = throwIfFatal; + //this.includeExtraProperties = includeExtraProperties; + ii = 0; + lint = new List(); + state = ParserState.STRICT; + utf8ExtraBytes = 0; + this.rememberComments = rememberComments; + } + + #endregion + #region HELPER_METHODS + + public static int ExtraUTF8Bytes(char c) + { + return (c < 128) + ? 0 + : (c > 2047) + ? // check if it's in the surrogate pair region + (c >= 0xd800 && c <= 0xdfff) + ? 1 // each member of a surrogate pair counts as 2 bytes + // for a total of 4 bytes for the unicode characters over 65535 + : 2 // other chars bigger than 2047 take up 3 bytes + : 1; // non-ascii chars less than 2048 take up 2 bytes + } + + /// + /// gets the number of extra bytes (greater than end - start) in inp + /// beteeen 0-based index start (inclusive) and end (exclusive) + /// + public static int ExtraUTF8BytesBetween(string inp, int start, int end) + { + int count = 0; + for (int ii = start; ii < end; ii++) + { + count += ExtraUTF8Bytes(inp[ii]); + } + return count; + } + + /// + /// Set the parser's state to severity, unless the state was already higher.

+ /// If the severity is above the parser's loggerLevel:

+ /// * if throwIfLogged or (FATAL and throwIfFatal), throw a JsonParserException

+ /// * otherwise, add new JsonLint with the appropriate message, position, curChar, and severity.

+ /// Return whether current state is FATAL. + ///
+ /// + /// + /// + /// + /// + private bool HandleError(string message, string inp, int pos, ParserState severity) + { + if (state < severity) + state = severity; + bool fatal = this.fatal; + if ((int)severity > (int)loggerLevel) + { + char c = (pos >= inp.Length) + ? '\x00' + : inp[pos]; + lint.Add(new JsonLint(message, utf8Pos, c, severity)); + if (throwIfLogged || (fatal && throwIfFatal)) + { + throw new JsonParserException(message, c, utf8Pos); + } + } + return fatal; + } + + /// + /// consumes characters until a '\n' is found (and consumes that too) + /// + /// + private void ConsumeLine(string inp) + { + while (ii < inp.Length) + { + char c = inp[ii++]; + if (c == '\n') + return; + utf8ExtraBytes += ExtraUTF8Bytes(c); + } + } + + /// assumes that ii is at the start of a line or at the end of the document + /// + public static int EndOfPreviousLine(string inp, int ii, int start) + { + int pos = ii >= inp.Length ? inp.Length - 1 : ii - 1; + if (pos <= start) + { + if (start == inp.Length - 1) + // we special case this so that a substring from start to EndOfPreviousLine + // will still have a length of 1 + return inp.Length; + return start; + } + char c = inp[pos]; + if (c != '\n') // end of line is the end of the document + return pos + 1; + if (pos >= 1 && inp[pos - 1] == '\r') + return pos - 1; // last newline was CRLF + return pos; // last newline was LF + } + + /// + /// Consume comments and whitespace until the next character that is not + /// '#', '/', or whitespace. + /// Return false if an unacceptable error occurred. + /// + /// + /// + private bool ConsumeInsignificantChars(string inp) + { + while (ii < inp.Length) + { + char c = inp[ii]; + switch (c) + { + case ' ': + case '\t': + case '\r': + case '\n': ii++; break; + case '/': + int commentStartUtf8 = utf8Pos; + int commentContentStartII = ii + 2; + int commentContentEndII; + bool isMultiline; + ii++; + if (ii == inp.Length) + { + HandleError("Expected JavaScript comment after '/'", inp, inp.Length - 1, ParserState.FATAL); + return false; + } + HandleError("JavaScript comments are not part of the original JSON specification", inp, ii, ParserState.JSONC); + c = inp[ii]; + if (c == '/') + { + isMultiline = false; + ConsumeLine(inp); + commentContentEndII = EndOfPreviousLine(inp, ii, commentContentStartII); + } + else if (c == '*') + { + isMultiline = true; + bool commentEnded = false; + while (ii < inp.Length - 1) + { + c = inp[ii++]; + if (c == '*') + { + if (inp[ii] == '/') + { + commentEnded = true; + ii++; + break; + } + } + else + utf8ExtraBytes += ExtraUTF8Bytes(c); + } + if (!commentEnded) + { + HandleError("Unterminated multi-line comment", inp, inp.Length - 1, ParserState.BAD); + ii++; + return false; + } + commentContentEndII = ii - 2; + } + else + { + HandleError("Expected JavaScript comment after '/'", inp, ii, ParserState.FATAL); + return false; + } + if (rememberComments) + comments.Add(new Comment(inp.Substring(commentContentStartII, commentContentEndII - commentContentStartII), isMultiline, commentStartUtf8)); + break; + case '#': + // Python-style single-line comment + commentStartUtf8 = utf8Pos; + commentContentStartII = ii + 1; + HandleError("Python-style '#' comments are not part of any well-accepted JSON specification", + inp, ii, ParserState.BAD); + ConsumeLine(inp); + commentContentEndII = EndOfPreviousLine(inp, ii, commentContentStartII); + if (rememberComments) + comments.Add(new Comment(inp.Substring(commentContentStartII, commentContentEndII - commentContentStartII), false, commentStartUtf8)); + break; + case '\u2028': // line separator + case '\u2029': // paragraph separator + case '\ufeff': // Byte-order mark + // the next 16 (plus '\x20', normal whitespace) comprise the unicode space separator category + case '\xa0': // non-breaking space + case '\u1680': // Ogham Space Mark + case '\u2000': // En Quad + case '\u2001': // Em Quad + case '\u2002': // En Space + case '\u2003': // Em Space + case '\u2004': // Three-Per-Em Space + case '\u2005': // Four-Per-Em Space + case '\u2006': // Six-Per-Em Space + case '\u2007': // Figure Space + case '\u2008': // Punctuation Space + case '\u2009': // Thin Space + case '\u200A': // Hair Space + case '\u202F': // Narrow No-Break Space + case '\u205F': // Medium Mathematical Space + case '\u3000': // Ideographic Space + HandleError("Whitespace characters other than ' ', '\\t', '\\r', and '\\n' are only allowed in JSON5", inp, ii, ParserState.JSON5); + utf8ExtraBytes += ExtraUTF8Bytes(c); + ii++; + break; + default: return true; + } + } + return true; + } + + /// + /// read a hexadecimal integer representation of length `length` at position `index` in `inp`. + /// sets the parser's state to FATAL if the integer is not valid hexadecimal + /// or if `index` is less than `length` from the end of `inp`. + /// + private int ParseHexChar(string inp, int length) + { + if (ii >= inp.Length - length) + { + HandleError("Could not find valid hexadecimal of length " + length, + inp, ii, ParserState.FATAL); + return -1; + } + int end = ii + length > inp.Length + ? inp.Length + : ii + length; + var hexNum = inp.Substring(ii, end - ii); + ii = end - 1; + // the -1 is because ParseString increments by 1 after every escaped sequence anyway + int charval; + try + { + charval = int.Parse(hexNum, NumberStyles.HexNumber); + } + catch + { + HandleError("Could not find valid hexadecimal of length " + length, + inp, ii, ParserState.FATAL); + return -1; + } + return charval; + } + + /// + /// check if char c is a control character (less than 0x20) + /// and then check if it is '\n' or the null character or negative. + /// Handle errors accordingly. + /// + /// + /// + /// + /// + /// + private bool HandleCharErrors(int c, string inp, int ii) + { + if (c < 0x20) + { + if (c == '\n') + return HandleError($"String literal contains newline", inp, ii, ParserState.BAD); + if (c == 0) + return HandleError("'\\x00' is the null character, which is illegal in JsonTools", inp, ii, ParserState.FATAL); + if (c < 0) + return true; + return HandleError("Control characters (ASCII code less than 0x20) are disallowed inside strings under the strict JSON specification", + inp, ii, ParserState.OK); + } + return false; + } + + public static Dictionary ESCAPE_MAP = new Dictionary + { + { '\\', '\\' }, + { 'n', '\n' }, + { 'r', '\r' }, + { 'b', '\b' }, + { 't', '\t' }, + { 'f', '\f' }, + { '/', '/' }, // the '/' char is often escaped in JSON + { 'v', '\x0b' }, // vertical tab + { '\'', '\'' }, + { '"', '"' }, + }; + + #endregion + #region PARSER_FUNCTIONS + + /// + /// Parse a string literal in a JSON string.

+ /// Sets the parser's state to BAD if:

+ /// 1. The end of the input is reached before the closing quote char

+ /// 2. A '\n' is encountered before the closing quote char + /// 3. Contains invalid hexadecimal

+ /// 4. Contains "\\" escaping a character other than 'n', 'b', 'r', '\', '/', '"', 'f', or 't'. + ///
+ /// the json string + /// a JNode of type Dtype.STR, and the position of the end of the string literal + ///
+ public JNode ParseString(string inp) + { + int startUtf8Pos = ii + utf8ExtraBytes; + char quoteChar = inp[ii++]; + if (quoteChar == '\'') + HandleError("Singlequoted strings are only allowed in JSON5", inp, ii, ParserState.JSON5); + StringBuilder sb = new StringBuilder(); + while (true) + { + if (ii >= inp.Length) + { + HandleError($"Unterminated string literal starting at position {startUtf8Pos}", inp, ii - 1, ParserState.BAD); + break; + } + char c = inp[ii]; + if (c == quoteChar) + { + break; + } + else if (c == '\\') + { + if (ii >= inp.Length - 2) + { + HandleError($"Unterminated string literal starting at position {startUtf8Pos}", inp, inp.Length - 1, ParserState.BAD); + ii++; + continue; + } + char nextChar = inp[ii + 1]; + if (nextChar == quoteChar) + { + sb.Append(quoteChar); + ii += 1; + } + else if (ESCAPE_MAP.TryGetValue(nextChar, out char escapedChar)) + { + sb.Append(escapedChar); + ii += 1; + } + else if (nextChar == 'u') + { + // 2-byte unicode of the form \uxxxx + ii += 2; + int nextHex = ParseHexChar(inp, 4); + if (HandleCharErrors(nextHex, inp, ii)) + break; + sb.Append((char)nextHex); + } + else if (nextChar == '\n' || nextChar == '\r') + { + HandleError("Escaped newline characters are only allowed in JSON5", inp, ii + 1, ParserState.JSON5); + ii++; + if (nextChar == '\r' + && ii < inp.Length - 1 && inp[ii + 1] == '\n') + ii++; + } + else if (nextChar == 'x') + { + // 1-byte unicode (allowed only in JSON5) + ii += 2; + int nextHex = ParseHexChar(inp, 2); + if (HandleCharErrors(nextHex, inp, ii)) + break; + HandleError("\\x escapes are only allowed in JSON5", inp, ii, ParserState.JSON5); + sb.Append((char)nextHex); + } + else HandleError($"Escaped char '{nextChar}' is only valid in JSON5", inp, ii + 1, ParserState.JSON5); + } + else + { + if (HandleCharErrors(c, inp, ii)) + break; + utf8ExtraBytes += ExtraUTF8Bytes(c); + sb.Append(c); + } + ii++; + } + ii++; + //if (parseDatetimes) + //{ + // return TryParseDateOrDateTime(sb.ToString(), startUtf8Pos); + //} + return new JNode(sb.ToString(), Dtype.STR, startUtf8Pos); + } + + public string ParseKey(string inp) + { + char quoteChar = inp[ii]; + if (quoteChar == '\'') + HandleError("Singlequoted strings are only allowed in JSON5", inp, ii, ParserState.JSON5); + if (quoteChar != '\'' && quoteChar != '"') + { + return ParseUnquotedKey(inp); + } + ii++; + var sb = new StringBuilder(); + while (true) + { + if (ii >= inp.Length) + { + HandleError($"Unterminated object key", inp, ii - 1, ParserState.FATAL); + return null; + } + char c = inp[ii]; + if (c == quoteChar) + { + break; + } + else if (c == '\\') + { + if (ii >= inp.Length - 2) + { + HandleError($"Unterminated object key", inp, inp.Length - 1, ParserState.FATAL); + return null; + } + char nextChar = inp[ii + 1]; + if (nextChar == quoteChar) + { + sb.Append(quoteChar); + ii++; + } + else if (ESCAPE_MAP.TryGetValue(nextChar, out char escapedChar)) + { + sb.Append(escapedChar); + ii++; + } + else if (nextChar == 'u') + { + // 2-byte unicode of the form \uxxxx + // \x and \U escapes are not part of the JSON standard + ii += 2; + int nextHex = ParseHexChar(inp, 4); + if (HandleCharErrors(nextHex, inp, ii)) + break; + sb.Append((char)nextHex); + } + else if (nextChar == '\n' || nextChar == '\r') + { + HandleError($"Escaped newline characters are only allowed in JSON5", inp, ii + 1, ParserState.JSON5); + ii++; + if (nextChar == '\r' + && ii < inp.Length - 1 && inp[ii + 1] == '\n') + ii++; // skip \r\n as one + } + else if (nextChar == 'x') + { + ii += 2; + int nextHex = ParseHexChar(inp, 2); + if (HandleCharErrors(nextHex, inp, ii)) + break; + HandleError("\\x escapes are only allowed in JSON5", inp, ii, ParserState.JSON5); + sb.Append((char)nextHex); + } + else HandleError($"Escaped char '{nextChar}' is only valid in JSON5", inp, ii + 1, ParserState.JSON5); + } + else if (c < 0x20) // control characters + { + if (c == '\n') + HandleError($"Object key contains newline", inp, ii, ParserState.BAD); + else + HandleError("Control characters (ASCII code less than 0x20) are disallowed inside strings under the strict JSON specification", inp, ii, ParserState.OK); + sb.Append(c); + } + else + { + utf8ExtraBytes += ExtraUTF8Bytes(c); + sb.Append(c); + } + ii++; + } + ii++; + return sb.ToString(); + } + + public const string UNQUOTED_START = @"(?:[_\$\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}]|\\u[\da-f]{4})"; + + private static Regex UNICODE_ESCAPES = new Regex(@"(?<=\\u)[\da-f]{4}", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static Regex UNQUOTED_KEY_REGEX = new Regex($@"{UNQUOTED_START}(?:[\p{{Mn}}\p{{Mc}}\p{{Nd}}\p{{Pc}}\u200c\u200d]|{UNQUOTED_START})*", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public string ParseUnquotedKey(string inp) + { + var match = UNQUOTED_KEY_REGEX.Match(inp, ii); + if (!match.Success || match.Index != ii) + { + HandleError($"No valid unquoted key beginning at {ii}", inp, ii, ParserState.BAD); + return null; + } + HandleError("Unquoted keys are only supported in JSON5", inp, ii, ParserState.JSON5); + var result = match.Value; + ii += result.Length; + utf8ExtraBytes += ExtraUTF8BytesBetween(result, 0, result.Length); + return ParseUnquotedKeyHelper(inp, result); + } + + public string ParseUnquotedKeyHelper(string inp, string result) + { + if (result.Contains("\\u")) // fix unicode escapes + { + StringBuilder sb = new StringBuilder(); + Match m = UNICODE_ESCAPES.Match(result); + int start = 0; + while (m.Success) + { + if (m.Index > start + 2) + { + sb.Append(result, start, m.Index - start - 2); + } + char hexval = (char)int.Parse(m.Value, NumberStyles.HexNumber); + if (HandleCharErrors(hexval, inp, ii)) + return null; + sb.Append(hexval); + start = m.Index + 4; + m = m.NextMatch(); + } + if (start < result.Length) + sb.Append(result, start, result.Length - start); + result = sb.ToString(); + } + return result; + } + + //private static Regex DATE_TIME_REGEX = new Regex(@"^\d{4}-\d\d-\d\d # date + // (?:[T ](?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d # hours, minutes, seconds + // (?:\.\d{1,3})?Z?)?$ # milliseconds", + // RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); + //private JNode TryParseDateOrDateTime(string maybeDatetime, int startUtf8Pos) + //{ + // Match mtch = DATE_TIME_REGEX.Match(maybeDatetime); + // int len = maybeDatetime.Length; + // if (mtch.Success) + // { + // try + // { + // if (len == 10) + // { + // // yyyy-mm-dd dates have length 10 + // return new JNode(DateTime.Parse(maybeDatetime), Dtype.DATE, startUtf8Pos); + // } + // if (len >= 19 && len <= 24) + // { + // // yyyy-mm-dd hh:mm:ss has length 19, and yyyy-mm-dd hh:mm:ss.sssZ has length 24 + // return new JNode(DateTime.Parse(maybeDatetime), Dtype.DATETIME, startUtf8Pos); + // } + // } + // catch { } // it was an invalid date, i guess + // } + // // it didn't match, so it's just a normal string + // return new JNode(maybeDatetime, Dtype.STR, startUtf8Pos); + //} + + /// + /// Parse a number in a JSON string, including NaN or Infinity

+ /// Also parses null and the Python literals None (which we parse as null), nan, inf + ///
+ /// the JSON string + /// a JNode with type = Dtype.INT or Dtype.FLOAT, and the position of the end of the number. + /// + public JNode ParseNumber(string inp) + { + // parsed tracks which portions of a number have been parsed. + // So if the int part has been parsed, it will be 1. + // If the int and decimal point parts have been parsed, it will be 3. + // If the int, decimal point, and scientific notation parts have been parsed, it will be 7 + int parsed = 1; + int start = ii; + int startUtf8Pos = start + utf8ExtraBytes; + char c = inp[ii]; + bool negative = false; + if (c < '0' || c > '9') + { + if (c == 'n') + { + // try null + if (ii <= inp.Length - 4 && inp[ii + 1] == 'u' && inp[ii + 2] == 'l' && inp[ii + 3] == 'l') + { + ii += 4; + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + if (ii <= inp.Length - 3 && inp[ii + 1] == 'a' && inp[ii + 2] == 'n') + { + HandleError("nan is not a valid representation of Not a Number in JSON", inp, ii, ParserState.BAD); + ii += 3; + return new JNode(NanInf.nan, Dtype.FLOAT, startUtf8Pos); + } + HandleError("Expected literal starting with 'n' to be null or nan", inp, ii + 1, ParserState.FATAL); + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + if (c == '-' || c == '+') + { + if (c == '+') + HandleError("Leading + signs in numbers are not allowed except in JSON5", inp, ii, ParserState.JSON5); + else negative = true; + ii++; + if (ii >= inp.Length) + { + HandleError($"'{c}' sign at end of document", inp, inp.Length - 1, ParserState.FATAL); + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + } + c = inp[ii]; + if (c == 'I') + { + // try Infinity + if (ii <= inp.Length - 8 && inp[ii + 1] == 'n' && inp.Substring(ii + 2, 6) == "finity") + { + HandleError("Infinity is not part of the original JSON specification", inp, ii, ParserState.NAN_INF); + ii += 8; + double infty = negative ? NanInf.neginf : NanInf.inf; + return new JNode(infty, Dtype.FLOAT, startUtf8Pos); + } + HandleError("Expected literal starting with 'I' to be Infinity", + inp, ii + 1, ParserState.FATAL); + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + else if (c == 'N') + { + // try NaN + if (ii <= inp.Length - 3 && inp[ii + 1] == 'a' && inp[ii + 2] == 'N') + { + HandleError("NaN is not part of the original JSON specification", inp, ii, ParserState.NAN_INF); + ii += 3; + return new JNode(NanInf.nan, Dtype.FLOAT, startUtf8Pos); + } + // try None + if (ii <= inp.Length - 4 && inp[ii + 1] == 'o' && inp[ii + 2] == 'n' && inp[ii + 3] == 'e') + { + ii += 4; + HandleError("None is not an accepted part of any JSON specification", inp, ii, ParserState.BAD); + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + HandleError("Expected literal starting with 'N' to be NaN or None", inp, ii + 1, ParserState.FATAL); + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + else if (c == 'i') + { + if (ii <= inp.Length - 3 && inp[ii + 1] == 'n' && inp[ii + 2] == 'f') + { + HandleError("inf is not the correct representation of Infinity in JSON", inp, ii, ParserState.BAD); + ii += 3; + return new JNode(negative ? NanInf.neginf : NanInf.inf, Dtype.FLOAT, startUtf8Pos); + } + HandleError("Expected literal starting with 'i' to be inf", inp, ii, ParserState.FATAL); + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + } + if (c == '0' && ii < inp.Length - 1) + { + char nextChar = inp[ii + 1]; + if (nextChar == 'x') + { + HandleError("Hexadecimal numbers are only part of JSON5", inp, ii, ParserState.JSON5); + ii += 2; + start = ii; + while (ii < inp.Length) + { + c = inp[ii]; + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) + break; + ii++; + } + try + { + var hexnum = long.Parse(inp.Substring(start, ii - start), NumberStyles.HexNumber); + return new JNode(negative ? -hexnum : hexnum, Dtype.INT, startUtf8Pos); + } + catch + { + HandleError("Hex number too large for a 64-bit signed integer type", inp, start, ParserState.FATAL); + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + } + else if (nextChar >= '0' && nextChar <= '9') + HandleError("Numbers with an unnecessary leading 0 (like \"01\") are not part of any JSON specification", inp, ii, ParserState.BAD); + } + while (ii < inp.Length) + { + c = inp[ii]; + if (c >= '0' && c <= '9') + { + ii++; + } + else if (c == '.') + { + if (parsed != 1) + { + HandleError("Number with a decimal point in the wrong place", inp, ii, ParserState.FATAL); + break; + } + if (ii == start) + HandleError("Numbers with a leading decimal point are only part of JSON5", inp, startUtf8Pos, ParserState.JSON5); + parsed = 3; + ii++; + } + else if (c == 'e' || c == 'E') + { + if ((parsed & 4) != 0) + { + break; + } + if (ii >= 1 && inp[ii - 1] == '.') + HandleError("Numbers with a trailing decimal point are only part of JSON5", inp, startUtf8Pos, ParserState.JSON5); + parsed += 4; + ii++; + if (ii < inp.Length) + { + c = inp[ii]; + if (c == '+' || c == '-') + { + ii++; + } + } + else + { + HandleError("Scientific notation 'e' with no number following", inp, inp.Length - 1, ParserState.FATAL); + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + } + else if (c == '/' && ii < inp.Length - 1) + { + char nextC = inp[ii + 1]; + // make sure prospective denominator is also a number (we won't allow NaN or Infinity as denominator) + if (!((nextC >= '0' && nextC <= '9') + || nextC == '-' || nextC == '.' || nextC == '+')) + { + break; + } + HandleError("Fractions of the form 1/3 are not part of any JSON specification", inp, startUtf8Pos, ParserState.BAD); + double numer = double.Parse(inp.Substring(start, ii - start), JNode.DOT_DECIMAL_SEP); + JNode denomNode; + ii++; + denomNode = ParseNumber(inp); + if (fatal) + { + return new JNode(numer, Dtype.FLOAT, startUtf8Pos); + } + double denom = Convert.ToDouble(denomNode.value); + return new JNode(numer / denom, Dtype.FLOAT, startUtf8Pos); + } + else + { + break; + } + } + string numstr = inp.Substring(start, ii - start); + if (parsed == 1) + { + try + { + return new JNode(long.Parse(numstr), Dtype.INT, startUtf8Pos); + } + catch (Exception ex) + { + if (!(ex is OverflowException)) + { + HandleError($"Number string {JNode.StrToString(numstr, true)} had bad format", inp, startUtf8Pos, ParserState.BAD); + return new JNode(NanInf.nan, startUtf8Pos); + } + // overflow exceptions are OK, + // because doubles can represent much larger numbers than 64-bit ints, + // albeit with loss of precision + } + } + double num; + try + { + num = double.Parse(numstr, JNode.DOT_DECIMAL_SEP); + } + catch + { + HandleError($"Number string {JNode.StrToString(numstr, true)} had bad format", inp, startUtf8Pos, ParserState.BAD); + num = NanInf.nan; + } + if (numstr[numstr.Length - 1] == '.') + HandleError("Numbers with a trailing decimal point are only part of JSON5", inp, startUtf8Pos, ParserState.JSON5); + return new JNode(num, Dtype.FLOAT, startUtf8Pos); + } + + /// + /// Parse an array in a JSON string.

+ /// Parsing may fail for any of the following reasons:

+ /// 1. The array is not terminated by ']'.

+ /// 2. The array is terminated with '}' instead of ']'.

+ /// 3. Two commas with nothing but whitespace in between.

+ /// 4. A comma before the first value.

+ /// 5. A comma after the last value.

+ /// 6. Two values with no comma in between. + ///
+ /// the JSON string + /// a JArray, and the position of the end of the array. + public JArray ParseArray(string inp, int recursionDepth) + { + var children = new List(); + JArray arr = new JArray(ii + utf8ExtraBytes, children); + bool alreadySeenComma = false; + ii++; + char curC; + if (recursionDepth == MAX_RECURSION_DEPTH) + { + // Need to do this to avoid stack overflow when presented with unreasonably deep nesting. + // Stack overflow causes an unrecoverable panic, and we would rather fail gracefully. + HandleError($"Maximum recursion depth ({MAX_RECURSION_DEPTH}) reached", inp, ii, ParserState.FATAL); + return arr; + } + while (ii < inp.Length) + { + if (!ConsumeInsignificantChars(inp)) + { + return arr; + } + if (ii >= inp.Length) + { + break; + } + curC = inp[ii]; + if (curC == ',') + { + if (alreadySeenComma) + HandleError($"Two consecutive commas after element {children.Count - 1} of array", inp, ii, ParserState.BAD); + alreadySeenComma = true; + if (children.Count == 0) + HandleError("Comma before first value in array", inp, ii, ParserState.BAD); + ii++; + continue; + } + else if (curC == ']') + { + if (alreadySeenComma) + { + HandleError("Comma after last element of array", inp, ii, ParserState.JSON5); + } + ii++; + return arr; + } + else if (curC == '}') + { + HandleError("Tried to terminate an array with '}'", inp, ii, ParserState.BAD); + if (alreadySeenComma) + { + HandleError("Comma after last element of array", inp, ii, ParserState.JSON5); + } + ii++; + return arr; + } + else + { + if (children.Count > 0 && !alreadySeenComma) + HandleError("No comma between array members", inp, ii, ParserState.BAD); + // a new array member of some sort + alreadySeenComma = false; + JNode newObj; + int iiBeforeParse = ii; + int utf8ExtraBeforeParse = utf8ExtraBytes; + newObj = ParseSomething(inp, recursionDepth); + if (newObj.type == Dtype.STR && ii < inp.Length && inp[ii] == ':') + { + // maybe the user forgot the closing ']' of an array that's the child of an object. + HandleError("':' (key-value separator) where ',' between array members expected. Maybe you forgot to close the array?", inp, ii, ParserState.BAD); + ii = iiBeforeParse; + utf8ExtraBytes = utf8ExtraBeforeParse; + return arr; + } + //if (includeExtraProperties) + //{ + // newObj.extras = new ExtraJNodeProperties(arr, ii, children.Count); + //} + children.Add(newObj); + if (fatal) + return arr; + } + } + ii++; + HandleError("Unterminated array", inp, inp.Length - 1, ParserState.BAD); + return arr; + } + + /// + /// Parse an object in a JSON string.

+ /// Parsing may fail for any of the following reasons:

+ /// 1. The object is not terminated by '}'.

+ /// 2. The object is terminated with ']' instead of '}'.

+ /// 3. Two commas with nothing but whitespace in between.

+ /// 4. A comma before the first key-value pair.

+ /// 5. A comma after the last key-value pair.

+ /// 6. Two key-value pairs with no comma in between.

+ /// 7. No ':' between a key and a value.

+ /// 8. A key that's not a string. + ///
+ /// the JSON string + /// a JArray, and the position of the end of the array. + public JObject ParseObject(string inp, int recursionDepth) + { + var children = new Dictionary(); + JObject obj = new JObject(ii + utf8ExtraBytes, children); + bool alreadySeenComma = false; + ii++; + char curC; + if (recursionDepth == MAX_RECURSION_DEPTH) + { + HandleError($"Maximum recursion depth ({MAX_RECURSION_DEPTH}) reached", inp, ii, ParserState.FATAL); + return obj; + } + while (ii < inp.Length) + { + if (!ConsumeInsignificantChars(inp)) + { + return obj; + } + if (ii >= inp.Length) + { + break; + } + curC = inp[ii]; + if (curC == ',') + { + if (alreadySeenComma) + HandleError($"Two consecutive commas after key-value pair {children.Count - 1} of object", inp, ii, ParserState.BAD); + alreadySeenComma = true; + if (children.Count == 0) + HandleError("Comma before first value in object", inp, ii, ParserState.BAD); + ii++; + continue; + } + else if (curC == '}') + { + if (alreadySeenComma) + HandleError("Comma after last key-value pair of object", inp, ii, ParserState.JSON5); + ii++; + return obj; + } + else if (curC == ']') + { + HandleError("Tried to terminate object with ']'", inp, ii, ParserState.BAD); + if (alreadySeenComma) + HandleError("Comma after last key-value pair of object", inp, ii, ParserState.JSON5); + ii++; + return obj; + } + else // expecting a key + { + int childCount = children.Count; + if (childCount > 0 && !alreadySeenComma) + { + HandleError($"No comma after key-value pair {childCount - 1} in object", inp, ii, ParserState.BAD); + if (ii < inp.Length - 1 && curC == ':') + { + HandleError("':' found instead of comma after key-value pair", inp, ii, ParserState.BAD); + ii++; + ConsumeInsignificantChars(inp); + if (ii >= inp.Length) + break; + } + } + // a new key-value pair + int iiBeforeKey = ii; + int utf8ExtraBeforeKey = utf8ExtraBytes; + string key = ParseKey(inp); + if (fatal || key == null) + { + // key could be null if there's a valid JSON there that is not a valid key + // this covers the possibility that the user forgot to close the object before this (presumed) key, and in fact it's meant to be a value in a parent array + return obj; + } + if (ii >= inp.Length) + { + break; + } + if (inp[ii] == ':') + ii++; + else + { + if (!ConsumeInsignificantChars(inp)) + { + return obj; + } + if (ii >= inp.Length) + { + break; + } + char c = inp[ii]; + if (c == ':') + { + ii++; + } + else if (c == ',' || c == ']') + { + // comma or ']' after key instead of value could mean that this is supposed to be a value in a parent array, + // so we'll try bailing out here and reinterpreting the key as such + HandleError($"Found '{c}' after key {childCount} when colon expected", inp, ii, ParserState.BAD); + ii = iiBeforeKey; + utf8ExtraBytes = utf8ExtraBeforeKey; + return obj; + } + else HandleError($"No ':' between key {childCount} and value {childCount} of object", inp, ii, ParserState.BAD); + } + if (!ConsumeInsignificantChars(inp)) + { + return obj; + } + if (ii >= inp.Length) + { + break; + } + JNode val = ParseSomething(inp, recursionDepth); + //if (includeExtraProperties) + //{ + // val.extras = new ExtraJNodeProperties(obj, ii, key); + //} + children[key] = val; + if (fatal) + { + return obj; + } + if (children.Count == childCount) + { + HandleError($"Object has multiple of key \"{key}\"", inp, ii, ParserState.BAD); + } + alreadySeenComma = false; + } + } + ii++; + HandleError("Unterminated object", inp, inp.Length - 1, ParserState.BAD); + return obj; + } + + /// + /// Parse anything (a scalar, null, an object, or an array) in a JSON string.

+ /// Parsing may fail (causing this to return a null JNode) for any of the following reasons:

+ /// 1. Whatever reasons ParseObject, ParseArray, or ParseString might throw an error.

+ /// 2. An unquoted string other than true, false, null, NaN, Infinity, -Infinity.

+ /// 3. The JSON string contains only blankspace or is empty. + ///
+ /// the JSON string + /// a JNode. + public JNode ParseSomething(string inp, int recursionDepth) + { + int startUtf8Pos = ii + utf8ExtraBytes; + if (ii >= inp.Length) + { + HandleError("Unexpected end of file", inp, inp.Length - 1, ParserState.FATAL); + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + char curC = inp[ii]; + if (curC == '"' || curC == '\'') + { + return ParseString(inp); + } + if (curC >= '0' && curC <= '9' + || curC == '-' || curC == '+' + || curC == 'n' // null and nan + || curC == 'I' || curC == 'N' // Infinity, NaN and None + || curC == '.' // leading decimal point JSON5 numbers + || curC == 'i') // inf + { + return ParseNumber(inp); + } + if (curC == '[') + { + return ParseArray(inp, recursionDepth + 1); + } + if (curC == '{') + { + return ParseObject(inp, recursionDepth + 1); + } + char nextC; + if (ii > inp.Length - 4) + { + HandleError("No valid literal possible", inp, ii, ParserState.FATAL); + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + // misc literals. In strict JSON, only true or false + nextC = inp[ii + 1]; + if (curC == 't') + { + // try true + if (nextC == 'r' && inp[ii + 2] == 'u' && inp[ii + 3] == 'e') + { + ii += 4; + return new JNode(true, Dtype.BOOL, startUtf8Pos); + } + HandleError("Expected literal starting with 't' to be true", inp, ii+1, ParserState.FATAL); + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + if (curC == 'f') + { + // try false + if (ii <= inp.Length - 5 && nextC == 'a' && inp.Substring(ii + 2, 3) == "lse") + { + ii += 5; + return new JNode(false, Dtype.BOOL, startUtf8Pos); + } + HandleError("Expected literal starting with 'f' to be false", inp, ii+1, ParserState.FATAL); + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + if (curC == 'T') + { + // try True from Python + if (nextC == 'r' && inp[ii + 2] == 'u' && inp[ii + 3] == 'e') + { + ii += 4; + HandleError("True is not an accepted part of any JSON specification", inp, ii, ParserState.BAD); + return new JNode(true, Dtype.BOOL, startUtf8Pos); + } + HandleError("Expected literal starting with 'T' to be True", inp, ii + 1, ParserState.FATAL); + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + if (curC == 'F') + { + // try False from Python + if (ii <= inp.Length - 5 && nextC == 'a' && inp.Substring(ii + 2, 3) == "lse") + { + ii += 5; + HandleError("False is not an accepted part of any JSON specification", inp, ii, ParserState.BAD); + return new JNode(false, Dtype.BOOL, startUtf8Pos); + } + HandleError("Expected literal starting with 'F' to be False", inp, ii + 1, ParserState.FATAL); + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + if (curC == 'u') + { + // try undefined, because apparently some people want that? + // https://github.com/kapilratnani/JSON-Viewer/pull/146 + // it will be parsed as null + if (ii <= inp.Length - 9 && nextC == 'n' && inp.Substring(ii + 2, 7) == "defined") + { + ii += 9; + HandleError("undefined is not part of any JSON specification", inp, startUtf8Pos - utf8ExtraBytes, ParserState.BAD); + } + else HandleError("Expected literal starting with 'u' to be undefined", + inp, ii + 1, ParserState.FATAL); + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + HandleError("Badly located character " + (ii >= inp.Length ? "\"\\x00\"" : JNode.StrToString(inp.Substring(ii, 1), true)), inp, ii, ParserState.FATAL); + return new JNode(null, Dtype.NULL, startUtf8Pos); + } + + /// + /// Parse a JSON string and return a JNode representing the document. + /// + /// the JSON string + /// + public JNode Parse(string inp) + { + Reset(); + if (inp.Length == 0) + { + HandleError("No input", inp, 0, ParserState.FATAL); + return new JNode(); + } + if (!ConsumeInsignificantChars(inp)) + { + return new JNode(); + } + if (ii >= inp.Length) + { + HandleError("Json string is only whitespace and maybe comments", inp, inp.Length - 1, ParserState.FATAL); + return new JNode(); + } + JNode json = ParseSomething(inp, 0); + //if (includeExtraProperties) + //{ + // json.extras = new ExtraJNodeProperties(null, ii, null); + //} + if (fatal) + { + return json; + } + if (!ConsumeInsignificantChars(inp)) + { + return json; + } + if (ii < inp.Length) + { + HandleError($"At end of valid JSON document, got {inp[ii]} instead of EOF", inp, ii, ParserState.BAD); + } + return json; + } + + /// + /// Parse a JSON Lines document (a text file containing one or more \n-delimited lines + /// where each line contains its own valid JSON document) + /// as an array where the i^th element is the document on the i^th line.

+ /// See https://jsonlines.org/ + ///
+ /// + /// + public JNode ParseJsonLines(string inp) + { + Reset(); + if (inp.Length == 0) + { + HandleError("No input", inp, 0, ParserState.FATAL); + return new JNode(); + } + if (!ConsumeInsignificantChars(inp)) + { + return new JNode(); + } + if (ii >= inp.Length) + { + HandleError("Json string is only whitespace and maybe comments", inp, inp.Length - 1, ParserState.FATAL); + return new JNode(); + } + int lastII = 0; + JNode json; + List children = new List(); + JArray arr = new JArray(0, children); + int lineNum = 0; + while (ii < inp.Length) + { + json = ParseSomething(inp, 0); + ConsumeInsignificantChars(inp); + children.Add(json); + if (fatal) + { + return arr; + } + int maxLastII = ii > inp.Length ? inp.Length : ii; + for (; lastII < maxLastII; lastII++) + { + if (inp[lastII] == '\n') + lineNum++; + } + // make sure this document was all in one line + if (!(lineNum == arr.Length + || (ii >= inp.Length && lineNum == arr.Length - 1))) + { + if (ii >= inp.Length) + ii = inp.Length - 1; + HandleError( + "JSON Lines document does not contain exactly one JSON document per line", + inp, ii, ParserState.FATAL + ); + return arr; + } + if (!ConsumeInsignificantChars(inp)) + { + return arr; + } + } + return arr; + } + + /// + /// reset the lint, position, and utf8_extraBytes of this parser + /// + public void Reset() + { + lint.Clear(); + comments?.Clear(); + state = ParserState.STRICT; + utf8ExtraBytes = 0; + ii = 0; + } + + /// + /// create a new JsonParser with all the same settings as this one + /// + /// + public JsonParser Copy() + { + return new JsonParser(loggerLevel, throwIfLogged, throwIfFatal);//, includeExtraProperties); + } + #endregion + #region MISC_OTHER_FUNCTIONS + /// + /// returns inp if start == 0 and end == inp.Length (to avoid wasteful copying), otherwise returns inp.Substring(start, end - start) + /// + /// + /// + /// + /// + public static string SubstringUnlessAll(string inp, int start, int end) + { + return (start == 0 && end == inp.Length) ? inp : inp.Substring(start, end - start); + } + + /// + /// Try to parse inp[start:end] (start inclusive, end exclusive) as a number within the JSON5 specification, then return that number as a JNode

+ /// If inp[start:end] can't be parsed as a JSON5 number, return inp[start:end] as a JNode. + ///
+ /// the JSON string + /// the position to start parsing from + /// the position to end parsing at + /// the value to assign to the position attribute of the JNode returned + /// a JNode with type = Dtype.INT or Dtype.FLOAT, and the position of the end of the number. + /// + public static JNode TryParseNumber(string inp, int start, int end, int jnodePosition) + { + end = inp.Length < end ? inp.Length : end; + if (start >= end) + return new JNode(""); + // parsed tracks which portions of a number have been parsed. + // So if the int part has been parsed, it will be 1. + // If the int and decimal point parts have been parsed, it will be 3. + // If the int, decimal point, and scientific notation parts have been parsed, it will be 7 + int parsed = 1; + int ogStart = start; + char c = inp[start]; + bool negative = false; + if (c < '0' || c > '9') + { + if (c == '-' || c == '+') + { + negative = c == '-'; + start++; + if (start >= end) + return new JNode(SubstringUnlessAll(inp, start, end), jnodePosition); + c = inp[start]; + } + if (start == end - 8 && c == 'I' && inp[start + 1] == 'n' && inp.Substring(start + 2, 6) == "finity") + { + // try Infinity + return new JNode(negative ? NanInf.neginf : NanInf.inf, jnodePosition); + } + else if (start == end - 3 && c == 'N' && inp[start + 1] == 'a' && inp[start + 2] == 'N') + { + // try NaN + return new JNode(NanInf.nan, jnodePosition); + } + } + int ii = start; + if (c == '0' && ii < end - 1 && inp[ii + 1] == 'x') + { + ii += 2; + start = ii; + while (ii < end) + { + c = inp[ii]; + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) + return new JNode(SubstringUnlessAll(inp, ogStart, end), jnodePosition); + ii++; + } + try + { + var hexnum = long.Parse(inp.Substring(start, end - start), NumberStyles.HexNumber); + return new JNode(negative ? -hexnum : hexnum, jnodePosition); + } + catch + { + // probably overflow error + return new JNode(SubstringUnlessAll(inp, ogStart, end), jnodePosition); + } + } + string numstr = SubstringUnlessAll(inp, ogStart, end); + while (ii < end) + { + c = inp[ii]; + if (c >= '0' && c <= '9') + { + ii++; + } + else if (c == '.') + { + if (parsed != 1) + { + // two decimal places in the number + goto notANumber; + } + parsed = 3; + ii++; + } + else if (c == 'e' || c == 'E') + { + if ((parsed & 4) != 0) + { + break; + } + parsed += 4; + ii++; + if (ii < end) + { + c = inp[ii]; + if (c == '+' || c == '-') + { + ii++; + } + } + else + { + // Scientific notation 'e' with no number following + goto notANumber; + } + } + else + goto notANumber; + } + if (parsed == 1) + { + try + { + long l = long.Parse(numstr); + return new JNode(l, jnodePosition); + } + catch (OverflowException) + { + // doubles can represent much larger numbers than 64-bit ints, + // albeit with loss of precision + } + } + try + { + double d = double.Parse(numstr, JNode.DOT_DECIMAL_SEP); + return new JNode(d, jnodePosition); + } + catch + { + return new JNode(numstr); + } + notANumber: + return new JNode(numstr, jnodePosition); + } + #endregion + } +} \ No newline at end of file diff --git a/nppRandomStringGenerator/Main.cs b/nppRandomStringGenerator/Main.cs index 60ed475..b92acf6 100644 --- a/nppRandomStringGenerator/Main.cs +++ b/nppRandomStringGenerator/Main.cs @@ -1,16 +1,21 @@ using System.Drawing; +using System.IO; using System.Windows.Forms; using Kbg.NppPluginNET.PluginInfrastructure; +using NppPluginNET.Utils; using nppRandomStringGenerator.Storage; namespace Kbg.NppPluginNET { class Main { + public static readonly string PluginConfigDirectory = Path.Combine(Npp.notepad.GetConfigDirectory(), PluginName); internal const string PluginName = "nppRandomStringGenerator"; static ConfigAndGenerate ConfigAndGenerate = null; static About About = null; static Settings MySettings = null; + public static bool isShuttingDown = false; + public static void OnNotification(ScNotification notification) { @@ -31,7 +36,7 @@ internal static void CommandMenuInit() PluginBase.SetCommand(1, "&About", AboutnppRandomStringGenerator); } - internal static void SetToolBarIcon() + internal static void SetToolBarIcons() { } diff --git a/nppRandomStringGenerator/PluginInfrastructure/DllExport/DllExportAttribute.cs b/nppRandomStringGenerator/PluginInfrastructure/DllExport/DllExportAttribute.cs index 60fbcbe..3afc0dd 100644 --- a/nppRandomStringGenerator/PluginInfrastructure/DllExport/DllExportAttribute.cs +++ b/nppRandomStringGenerator/PluginInfrastructure/DllExport/DllExportAttribute.cs @@ -2,8 +2,15 @@ using System; using System.Runtime.InteropServices; -namespace NppPlugin.DllExport +namespace RGiesecke.DllExport { + /// + /// The fully qualified type name must be RGiesecke.DllExport.DllExportAttribute in order to work with the pre-configured task in UnmanagedExports.Repack.Upgrade.targets. + /// This implementation could be avoided if we referenced the RGiesecke.DllExport.Metadata assembly, but then it will look like a runtime dependency, and be copied to the build output directory. + /// + /// See + /// + /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] partial class DllExportAttribute : Attribute { diff --git a/nppRandomStringGenerator/PluginInfrastructure/DllExport/Mono.Cecil.dll b/nppRandomStringGenerator/PluginInfrastructure/DllExport/Mono.Cecil.dll deleted file mode 100644 index ffe9b57debe72e460a2fa851d2870557e6030ffd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 275968 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 z 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¥Oz$yjIj1vGqg;92UeJF8d4qlid5a1}CB#i4 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 diff --git a/nppRandomStringGenerator/PluginInfrastructure/DllExport/NppPlugin.DllExport.MSBuild.dll b/nppRandomStringGenerator/PluginInfrastructure/DllExport/NppPlugin.DllExport.MSBuild.dll deleted file mode 100644 index c3c9de86a2cb42776089e8ee157ca5223bfd643a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41472 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 P@*Kfp{O|Su1r7Xv!L!b_ diff --git a/nppRandomStringGenerator/PluginInfrastructure/DllExport/NppPlugin.DllExport.dll b/nppRandomStringGenerator/PluginInfrastructure/DllExport/NppPlugin.DllExport.dll deleted file mode 100644 index f673ed5ce1d567e6337dea83858dd141922daf8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90112 zcmd44d0-Sp7C&CyJ<~JOIXja~4vtKKgeEfy1d#i_Z$S{j0|+5V6w)}8fD#jfAYRCd zBHpXwee160uIq}sBI>Gm>xy`@UdtZ3>U!Ys^Ila?CYast{=R>G1F5dhdsVMqU9VnO zS9fL~f3Yxx5I+3=^pg;e;7UK^IQ;KHJJ{K!zsVL4gq|;ZM4R<|*@9IK$>`dIdvc<7 zO|-7IvC(acF0YR!nj51HjnPR*&yTKgSJYQ$Wm$cc=(&@Hn57xwtB+ca@k)D7=w%sN zh7gN1Axuf#wjS{);_bK!k;`#i>P-aFpMRbJAN1lT4fPs#4ke}k9ajZqYB3ISXHpz; zAEAag^5=R@WP%o4r-|-I^8Oo+B2OAW2s|wfS2xw4-UPhkIOq!=*A;rF|3rmYR-H&D z>Of@Opd(lGcwEzd#-Y2a6ZNZIBqUu0bfz{COMaGa06CThji10Ux>?~_xnP4 zIj3zz*cZh$|JvvCM;rI|U-x?Xu0xl< zHGA#blVW?@hVOjs)Sf%;e(=%2AAZOPUVTd5i~XOw;lf2vjNWnk_wTGbZE)^Ck3Jm# z@ztm?KobutO%tfZT5hw1O))fyFHuG@bhIx~LotjSUt&DPPp-^e2H3$1t@kJ z#n6|HsRD7Kwq7R7F$7{;nEaX-b-{=UR+ie*vkC5mNJ?0t&mQ0yNR!;thPGz~GE zVm8I{5UV_1-v^z#IUrlEhTvv`m2EjK(rZ3vy+7&Tc*)kgloD?^RQVAQnCbr65I{ZPJ%H( zIthkMl067lw7s3|0Y1s@iR%%vOF?4Up4L$i^vZ#0peat4QRqmg>zRhTd`?YI#^EYD6zMtXHDVL*jgh#3;9uKWV}Y z=S8eYMkF(mW#{RxgCYwTw^Fu9*jEtt8->+IK};(Qn}~(%JeC;KBH;jVX&Xj_ugcCp zY@)HvWIyC?Clb?Rka*Zc?@C5ESP%|H!d8Ab!w!TqhrLTZZ)dud$P~_sWZGG!eFxQ< z(A6@lX4?LOnkjasZ7vRH+u63?iBYl~j>ajPYy0i&L7_x{bbUA@)TfWCu#c4pXbR#l^xfp`$bpEO91WZ>FwP(}!9 zWQGbCWyNwpa0Z1!g^RP8=!4F7mYrb+Lct`IGTr_tr^;U#_QiTYV#v-4Tcr4+aHbtZ z{RV(NBK*@&Kl$Skzix+YpURA3V!Acp()h%blEaZBAYejC0!Ki=bLP{P#x4oWihY@Y*pxcVK;OStN4gY&K}sSp za99LH$s8Pk2sErDa0CQQBuU_ivKyE&7%Zd`I*{a{E6mCfo*`&ODL$$sUomeqfssv1 ztSxEHFk3WKwMx^4g|sS%NV>t6nWrXIO44wM_xs@0frcky1~TR zhC37`y2Gegz2(;s;7kI?Fx{27WWY?_51P+C2Iznlh?@a!(EaFOp2; zW*7ph2&}%gg91q6gUD_8+z~*gI})K$?#_WM(;Wq{$UPcyl4T(8OcH`0!^ONZtHv8H zjFyS1Kh$v1zh=C@-tsGw$H+y`SykQBNiyz^0mB`OpsK`$#my(gxqZ5Zu#M&qz3C6F1CyHR%%w=Sqiol9XBCI zB*#Iy47=C}B=>?CF0>0Hty7T5E^MXd=Elp^9n=A0(Q2l9AqHP!9)^-L9(f&%H>)@~ z5n)z{I?PH=MgTLH6|j9SL|N`s1c*<=CH~ogOxqVH7R`qJ2dq%s49U1%eAv`!9Y8Bt zVZToD&A5~Fk@lu-+QqF8kY=XsZ>7$(;ZXQH(%Z$!8OYd5{ep#O*h^?@yO;-!9dMt6 ztN^a)St}g0gN1fcRSuPCkc3%8EwYR9oiNhb!N^6RXwGw}DonK$QrL=0`k)1iY@tTV zv)tLpSv6GWp<2|I1)_5aL=@73tcAPV-6QE4w)Vm}-DP(tKiiCDU~#hS5D!T&6_pVW zE95@U+Q7z0j8A?zKWS167T5)7!G|E< zOG(mM8jk`S&KsGjwM>~V&q!sQF8=Kd|nNQIyJByu!;(>8H zE3p`Z#a#e;yr-R2Xy>Eh3l5usRKw@n`Lf}W{a7TcdgMTMUXR0(0&-4ym~>KOA(hZG z*4OUo9!Jq^yMX6~olUE#DQ9B{i)*&K2z(@4OhWfz#0O$y2pbY8Pgp-g$4Ko zN}B1Oh)cM1*yk8Ld8N)tgmNO`+yZzDIWEi{JfYY;G*ljsBsospC6p{|hZ85!w7~4L z;3>e*2uT_oMp&T6=3fsW;+`thY~oo;5f?>M0h0ffP>ny4(#|)Wfo&4z)-YM)fKpm z2w_yA?Mn|wu(qqV53;fJG)mM{Wv)QpB00pPb`(nt>An>MM%!ga3xjM?w_>Ca-7At8aZV=5a@zDN1YQM9#hFTeFP0B! z4PaE=4(-bhM_{$Rk&_WFLC1GP$9t5LN-_Q}S}%mS5WjF=#Lf6cQ}Yke+wgl7eqk=< zf`Tj45zIpTSzNPmr5~byjsR=0C^b6EnFf3q^eMj1>2C+dfD=C0?Q}V?KEe-L5q>>= z;2A))>0p?~XnmqJvkKP`+57Sg=M<=@xAcRs@~YelU3Xxa%<{}uq6Do2fq-)=Qtaoo zO|Fwc(Y+5Xjq)cd;$kGESt5(?wWxj;uO)2muZ9gQuQDrmkh!bD^E{7Ivpbu~@_moc zC|v_qq1%YiL2JZf{x)dqORfb0K8!9}m0T8N8Pa>HHkES^cdZd-2x)lpVk~uI|mZ`V=4wTl%5RhZ!1VQDxVdPVP!RMnamN}UQ z1^t%eH#Sr+U{SF$U2mbxyezgpikM!=9NaC+B#O^V<;xLrEGboGNfNDRZD4PV2^&%l zDC^i!k5;M%>tv&7s$=P4Py1HVo)yoBG^#+S>kI?8n=MZujp1Sf(9Nk8ArI9(i_lfN z(N+T{-^gK*n8|jK+lj<<2V*@}Q8`C%d6Gmpr~|s{Y|6S&Z`lo6%j3jg*>taYNqS2C zuon^dmBxEOTm;!!s7o>zX~@!MTdgE4JD6`sKc~PBv-tRcq|JS0hg*M- z6sDrLQoZajT!uWa7?u$0V~5+|+RJLqplXo{?L=Zar!t&#K+FixZ4SH{Gc(|9Awm+S z8NN<3Olpx854Tc9vxB9FaRY?9dsZ@HM<)&_3lytV;e1J0xd+zDdKXX4WtaClLu}=VJ@Q=Uxv9h2iW2 zL3jzd;hY1)&W`tYZUEJRR^G^hTTfLeF3b+81-RJRg)~wznH;*9#d5hWu}dgRG31z1H3l>3xVwsbt~0R4QX%k}DOT!X+T!!{!-(A(m5MAl&b z!anj3(YxWd4z#v3I^DCun9+J@m*(?*tYt@;vK$r zOMJ@L?g^BO^gR-=0=V`}^yX`6C~=gGSc%y(l98yBk<3J^jASLY%Sd+O1{ujo+$$rw ziRWa*PP`!_d5M3>NI2m)sHk={2<<33i_zM28_W0c42GwJN$FrT0a$r>x$=IQBOna& z2pk!r=NmphjL%7wp4PCSn2ol|L0cK|VTSuO=;c}JT$CD~QJh!tCHxQ)mhUoNk_J;45aRi2{sKnu{yYsJUt zzT!44J3i+E$diLEBM_14b_EIzcLPMZ%Mk1e6d7&{N4Q=nNiURZH2F9%I09>!oy63& zGJ}Q5yCBfL6(CrYyeAd!k-RSzFG*qmsWjb_F#jsvEeWfv;*sQ2sW@8YHpp{tM}R%N zJj1yIF_?LG9?}5b30U0 zNf6yS*y~n~V0j&)l8|3)R!;FUps(2+RW3=R$W1cYbu0SA`<(T}?c{tye>lZ*oN@#N>=H== zM?e_r5jf&C4D=+w3k8Wh=-Y<PM^ZUHKw{}T=sY~jQT~bH-yVBv| zE~%e(Ni8+Ik}$hV>L*=Ny9c_Gu&PVy)-I`gyQF^CCAC+utJ0=)NjMdPTAM27j zG1OIQCwED`s7vaLT~hs4S8{iCN&T!#YR`A#fupU^LOLE6@lfF0d7kparH-ZDd0j zK5mvuUn0?w=D4)xj-RRg6>>z}02br4*3T2}??~t(qh%~)x(AR(Mt$zf6p!$hs8Pa2 zqL8yY3hATdJPrXB=&gWvTGd*B9OTY;GunTWX86h5DvPWAcL*00HgLj?{MyeocF&C2p zH)g}XM7v4f5nGgKabF@2YB@Wp6629X?SA`hKi%~D*^^17H&W@hACVq={E@WMcG)K9 zl*;g(Od^7E)W#$B!m;t#iiTD9J%Pdi`>DI^0N=;(wz({}pv{*mwaqJ4$-%>b&T@h& zIYBNz7VrjocRA1%OpRWekEXaAjUwkG?EMHDD;aiD zfK<^%fL?FMw|G5Z!&zifI;N$F0L*rsuv_qc4ik zHmW~@vOlWy!b-1424!P^j=G|CFA66*Y$&EtNB3A}X*BhK_~_Kb-^^fQE=}5{q{A=> zp?rA8q_>cuz+&08&m!Ih4{czzKEnm_S~mfD7E>Q~Lu*-P0V`wbb2*Kf-^ya<7Gd|T zd*|lV%@z_f&oEmk&Ae3ohhUk04iw%k=5aC|Wt31EJ$Rf%nB6uKY4o|=0qCrrBptnA z(G9U>1zJc%JV%ErE~;fEQJ95}A~Kdy)}7{UH)>(ob(BLXuJZO+vP;-=F22I&&yh|R&bJoRZQg<=}?B<|ItIB6) z#EOH3i>#PwM*8bP3-L@PXW&jez%TV1>VZ8AnddVCJa{E4tn`%ghKYAY9q76B$+*&w z=Z*~Gk%FEvtHvl=b+zZz#H+ng)W15axTC5ubowEE>6mKIsVS?bgH0_jqRkXj&hNs? zofsOukhov~HWKsnmgi*>BFW+Nr&K%p#Rllr9qpW-nJh&&Wrg%sN}q3q@>?lgvW)vR z)cOKIAjHDbCMOjyi8L#FQAd-X6s*b$9X6u`Aq4vb`~)|^+DgC42(^&-dD3t4iL;R9wLfF4L58kD^mtK=k z`gfLZZvJ5nmBfiHD<8+8#`)&D%ON#B6pDop*tY*IN=c&6@W>uZ zJ-P|I8fLuPf6R$OGR=5XuGf8J&$ zISe7S=9`6~w5=uEEDFiJCEMi9rC~pysRQLz4qgoQeUg;*F=gd=HgRn=Jh@Fe{JuCC&p&IZOd{XP9C`$B=*r z!SlqIxThoOYR|%A7MXG_UF>;kJ=j@F`J6#&*6WZ=`h1vNZFho%edV;8%bg*YimKmwQ3ohu+m3SST|dGH{9tQ`W4gTxTRoAYZ2g z9BC)OWVcWy@=<2x*UA$alv-i>sS{CUdJ86!cOq&k5=#Pmu!S`p_r>sZ1Xc(;iJf`V zk_(z#NfYCs0Gs@Q*#-kyI{9>F62r}EAw>>@Exw-(eu z?&Wd>1jy(6K?h9f4b8Zh1b}QN-s4 z>1>}oIru3u;fx!f@x)W&SYBR8o+k_1^hgz2SfCt>OV!7CC=*IQB8JD40X&{mheCl& zAo={G(^jY`j9Ky=3>;!`Re`?eD_NP8G*ze z%J2~4WCy|nG6gn3MPKwoKE^Ir`chO{Dg?XULBae^9fPV>=ZhAr1ed3>;At#pVG7OK zc=t3)y1WTeUJ{oo+qoKTAy4onzXD~0k0(DL`^cc-ixtelR55Wbm*wNhzL}V0i?#Xr zyh9&sgAeO#!vsa&K_(WKCWLMCLE!0(A*H4>k(lnl=EH(iVNvr1$h4Rhp-Mh`vjJ`6 zu&w99uJx)%ueuKi%AXi_?zZWhPW5k zBIxLiRUCvd`GgLenZzoT#Us?~Rvao&-3kwvtHl(ythklMt2HU2*T_`w1l|H$8_Z9f zjZ5Mx1b$;Hrmb!||3V$Oo_NcubF1fb%Vr#~E95xALs_oZ;Sx2T$s=sJM%Yv@S_5p1 z!G2Vi67(z15YtT#4h`W51o@|k6v;vnD^9!zB@l7GM)_P<%aleHHsTx5l4Me`yZsp{ z>wXIma{ovax?l7WwJOdLN z;B7_AOq8H)ilq%j^K60gxgr+S`4CvH;P;~f0&X4Oldwmg=!GoZxx-+5=*FVc;zzc# zV?5#<2bs%VW|4oFS**s|xk!Seyk|pB%%9ka%PzA7Z%=hk+=RGMTpdU}AbC&V!UB-4 zzdZed4a7mz#0800!OZrT_S!lNt>qjBG1gy|u$`2kWN{4flEeo{nZ`-{lf({Tz4*og zw`^a7q;A>Bf~NU4sCyXm?qXbHXiM1!m0>g+w*lI-Q~hc2=i!MHAgWAi5}W|(T>E8s zY=TYoajU4rS)P&DLqQ>&f!AZ!FI&<&@ zMD6R1(e(2(2b8Xj0-QNmLKmDl;OomNx4Ir=-PgK;F8-g$#UCvjtbs4Ue zW2D8-_7^%|!UU2VP3h=L>BvLIgft7C*HOdDXVVeAFo+px7CCQ#SEUzK7FSAs5vQ)w zOW-*|5UE2~(uc035M5b;lD4G;r$i!={QO9glxSxP+J)B77F^@#!{kqBF_@2iXVJ)k zr((H6L!coryX|Z5f?B+9V;9tteP?$;E$lLF`%2P{eeg-pgMRd+juKm84soAjP;?bX z@n)3O+Q3miMNgrqyt~j_C`l6Do7H+MVpMnUx|%Q!v9@yL6y1Szv&w9{gXj{uH&D}e zuM}q@crDxa1;*m%;WW$AIc$ zm9kpcA-9n&9&2rgtAQl08ykbh3 z8LP$%9wihF>Mb;qa8HpsjfOZ?*FkIJ1t1sRJG~CFnCkHi?j|zfh-S*=2_@s55o^Q- zsbt3C$ncbsvh=n!5YCCGckgkkj2XukkD$$?pz-6d27QtY?x^<|)xpzvwbqrOrH)0iLBA(7AA^ak^$DP%| ztt(a?HzzJjONjhW#K(Krw{T<`6id*;@-=O7djP8C&?x&>Gnkw8;LAu1mvJ` z?C6HU|6XN9`yG4i&F1lGqFlT!VJ%8AI%K*1m|>DdNLG?Wv;mxBX(BiRLbFHU2ng^7 zWeSdfu-+qZ1ccK)0!KhN!y|A61o+WX21l?$#i;mRdVVWzK4Al?4~kJQjClAaQS_l@ z<~w;X*WADS(ztBL|DG-bIizjT===%2myh=nVC8*o93^HPFf4OJ%J;;5ZniMU_Z*@7 zjARxvC!rWWpD@S|r89fTPdZvS#)8*jA*9Jb_hss55;YibuoTBEGcf2~ub;ch2Uq1k z=Ua*QR;bG5Rl$mUoz=iQ>nS~`9+)mEJ>o$+REYzSm`Jp*S_=MA#5a8TdE#(EFQ{kc zX)+R^gR>YtWXGjjfQQkqLi246QeU6gj{I|_;;Qf4qa&`SIyra4uajG7tnk?Fx( zYpf?(O*7ypqhkheRucvWS3j8zGhmrPmLEK9R+Evdfu2qfZ4}RUZouTqR(y7|Vt7@W ztSL4MVQ1A{NdUJLm>4F$wU8W$+^y7oN_h;WHXvTpI0D_fVeG&N32Xyl?Bt^iys(Vn zZDQSPrK~L!@*uEG_0AAp&KLyIsTYK(E#&h7M}%tR_5_{7UF^y2N(K)p-NCFfd8o$A z$jwDvXsAiL{MrG09d#t#bO(c1(M7rWV)Q&&?i@Y3_g*1d;fmn#GdZoi2ovOba|8|1 z?h!Zw0{jt~f+Ki1wv1ZQyUsCm2hC}^=OZX~v73YOL;Ei{Y3ZDRcMWM3%nBx!L$rKs*0#x$Iw~cVNK9v`A(sTYB(MS!Ff6R92T|_~ z)H_42jHnHfM^wXniWDZC5(a^GS&q=jcrt=k;*n5IYF#;tz z2(W!H0Yj8ty@({%LGxXBn>5ibBX)ovMvg`@-l!zMz_HL6w2>K~=9REHRYJCstVYCT z$jI9)iSrVw2wGp+X~31}AK~|C{9>YaqOS(+ef-Wof_^+` z7^32YBj{fMlTD}R%jxoAcK9ZH#1Zt9fGPc!Bk0>92QIzXaRmJW(BQ9$U1@YU*)aJX z?>*A`I1Vyl=WHE^)pw!U)M0hr8cSl}!f$ag1h74hU2t40CFHnCQ%G)-;J(lhPeK;_ z2-r)^M>AI#@~RoU!5+`Xfq3V7D3Bb7Y#SuC3hN*C#<1-!kA5t&^u%4zqtF?TL}7m< zY{vT04(L@G22_BXCJV%F47#FY*IsRkVzU&RuP^F}`&b673H;eeIEa_hU`wvbGD2xG z!f=~?K4&5l;j!`}dWqc*Ap5H@-1LqdZi>jiLVb8LB_4rHwp7@`VxxBss#!Ab*QoZ9 zLooGzXQ#o5*=S{$QgPck4^5#qB_~mX$YKiHU~qiSRba}emiYWhT5-HPwksr$@tKKd zp%PE60EdZ}soL%b$XsEViTy`#KLRe2+*mKqa??EJU5~Ui;LPki#7+z0X& z{|)$(#OqMnJp<(;{u)()jr02;(0X8qQ_x7G(3bfb$^PsS7_F2gsJBvTuZ0gmLAe&- zxf-`|@?D6a2YJ;{(`^PY165h@12A^YzHd-+r_!ii^5|ci!G!-f7^Ga!Nj~EUt3*S=D!WDPh z>Xw?2dmXW`R>M({Yc)rX(G46XSqxrsBLe3bN=0+N1Z{vj4;xL)*=gg*S%|=blQf4? z(Y=Mv_eGq}=**>doDUgeA;WN|LD}SV1eW2>z$G*01l^g4;8Q&=vJkgaJ0~Kr0n%avOw0!yZBdVRhz);I4kW?*X}4xUW2DcK=4B{xZiwnDMYCOS zXxyJCCO+rgcfT(N7kKyGtHd)k^1hqah=pjD@n{*JJR0vFPnyV6@l|7uj2OPgfu)k% zH}r&66%4p1(e+b%X{S@~g{Nr!7} zxwQzw<}kRmBF}V|6QW~Wb;Mgi0adv?9EhJ_2NK<3e)zyB9?u05i4-b}jYgh&q>Ruu zj_tQXL+28^8*>ryvZt zry_J$BS2<)S_bQzvZaH?FJG2@+|zK@APukk6{zYYv=bTy^AhQ4ug=7ajv2AsB=*Gc zlpXWJMWiqTx5?Z`Cfar~je~6LxwvN|{qV0L#-MZk=zZJ(o`#5<1cN|=Kr;a1pjqm| zK0~)fda$2R063^0o=OA? zseYzEz*8~RzH7y>+hqH8Y%{@x(Kgc=xR8w}NLR+vP9KYgOW$XDHKiN&nJ9gyeWp*+ zxg_O(-e;OZbD)y z=lPk5XHziUCeO{`2T_)6T&Jc{kCTlxI{doGih`1~ai!Qx$ zkFgDVfDKxA6JpZf`B_T`mVh>BH-ldFTZ}R6&K{QEr=U8zgEhr;FGgXygFz{uDQ96!8tetA1SW4asFTK3 zct%LMB$1ty@(g!7M8JNrYhXy%fW{==(~hUc zozQrjR~3$c@C%Q?5fJd8Q`Vj%AZ+&t9QmaY?~SwYSnSLyqc{c+oT3gHO^~r(@q$9_ zC^sN+fRN@VcIPf3IUvIxa5F31a;YbuBPb9{s?>lZQ*?)nc6n8xLU&+rcthJEn_am< z=Vx23mm!1Bj(6)$igs+HUEzu12xMRB5jX+@?&oA-90B2KkH8TSuJH&Q0pVJYz!4BI zQKSrxfN-5h;0OrUdjyVvaDzwS2nf480!Ki&(IapK1k7ex3P(Voy7D;X2nf^;OyCH< ziGzEE!5EkHqsvj*ayimS*Mn{|?n5)uHm21|^aJplc@*iKOuhSWifDx#KP0bEiu2?d zoilp(2&M?-$UJnvG7x!Y(AaJeiE+>jjr$a^O+P5Ir8m@WU9kD)SYK&6fwhOy}&NO!;1>B_2z< zX1d2Eg}bwEoRXPZu4a zr)U1Hw_xbN5xSX1A4VZTx-#C9qcK5}2jv11U2^Fwyp?yTuL4(<%Fkl%8%l!}3xqWY zp9H>{(SGXi2tD^B;p!8>EU31!A_x0foM50AqfahDfm=xtdLGUZ5N`7b90B2WkH8VM zuaBQg&7uCIqvL3&Pf#>Hl?s}H1Yf$C0Lv$7qjesq>CD^Ua#5P?}z3x2+nd5kWCs;aS(1f35hbMw3v*hRRBF#;jyO*B+EGcVJ*mWEr< zIE|VJ<15qoB?ue2!Qj!$bXj>>DxJ?um(`JuZqZ<8tq8q%g%@$yE1Bn4ukm=_yoSmD z0A*qk|A;Pr=OA^Jw~soP`qOzIB!fQ|!ylxM$8_gIkl>mc{P7qbMDYUM`54rUJk8+m z$jG7fUp`(v3{~TgS5w6KjLOBMTb`@)(QWfmWvj1MlN$WBYRmM#Wld_Jz{3%080OV> zFiD$C%<@i5vNiIV=P@LoHkzy^tQ`3Mu6sS=w72KudwS?#x7`3z;&r%65qB4&_(-oW zKl8vSGf`BClTk(PjUYPX(aOBv=-xz-+$U_|v3c_aG7n@UN7@D(y<{WzgKRN;hTIRL zdC>=ThQDY#H$#X#w@XLP)?FLmL%YdaxP0djq+^aIXVA5mUPv$A70J1!ixrSzal3Kp z@Bp4iG87Jthe2ccCMS*rseRU^NFK-ge`;e@!Xm&a9f^x89ju#JdTObfbHPa73RO1H z^TZw~4|h*CEHxG*OScZ>!m6kbw`1|L4wvw}r~1-QMux~e8P$j0v`$%-yPSVP!M3|# zPI$BGYoO#E;ItEo>B&1OAq+OkI}tJ#qP%>|xeHXUm9sk^2Mu(bg_Yl66k~a!tC^KG6v1ejMwbydP9NGj63M0beVpH>2?N&XrE zzDyXPRu8Zo=K+u)=U;h3yzuPb9*181JbLx~Gm=g9YpRc_PrA*Cy82`)c1(Tkiuy!6 zx+YoYCRR5rk4G2QCz1_rb=tE*EiA4QeXVi&)o5o=>Nw3 z?#3TpymZ@&8{gZxV6c1TS;OzV{_4-dH+27J`%SkV`ta7x7v$V_`w8w{>zB;A|JLg# zKd^c9FMc=b@D-15US72O`H?R@{r>o4p7*_d*UR09|M``^lm7k6C0|*u&VK6;uYG*} z8SmI{)VzP(h41}!;*hhxtSTKIc}QIPv1V}7vFqxT&4c$ zqT81bIdR?feG-?SGdS^4e01}G`LWY~1(yV$@e-F?_?DH4znFmdr?@Ty9b47nIlmBB zgk*Jwg0}+cCZO>j$8{HYVZ^5+{XryIhx9F=J&pLO=(|ne#USTnqcurUg z+Ip~^iIBaP$tzJ0Dw8NwQ>tH+n4^?E2Z<}a#1p_=$*R@=hvb(c`KXDIR*(E7YXN@O zprlFAlBji{Qrn&J9|Y`zfDIELH8! z8kq+Fyn>Rs0?F!GyGE{s92KN*ljx4|8lME^)^g`ie^Q&tj7dj7U(Yoo)z-6WoyW8%k(#ul!DM}@*{JboAd$qS(44fU zmRbny29k0#U24Ik$TLTvSC^Vq-UqGDW~Nc2&CSX zV~hkZ#qghne>ip|D!R@@BkMF|l(S@on2w~q=76{!d1)9>GgGTlQ%l>yBOS>`(Da~U zsKGs^oTf`rJIY4&qG>~FrH?WCBHd<9=9ekSM9CDCi}I5#S_io_a;Q0}B&r-$mfDQs z)Vwqxgnq`8zc(uGA?ik+}o2x)k`_$rmO1#7tS-$BUWBZYKsXA=4ItWhj1S7~#()0&jY zT!WO-Oi`XX)Ii!uPdQpVsD%=^O7*-xq;z#?VKRGj;rvmr$XO>-NXcZ%XsA(NdwnUb zNfP5~Icq`9%eEf+O_}?%u00h@TIM>&vQ)Jeih0&c8UQL-&aW#VYTz-*L`i94QN5bL zOdyLKF()HVHK4_V3^6T7Pa@k0FB>hlG_<@j(nV0Eyd|omZ$3cw#Q%ME5-N>@TqMXc zdP{_-gsdoyZ(1qI7O%hua4`IiL39m>G$}g9)kI!OvsYrOt0yQhB%}I{z-;Oy- z)uyU;tcj%8YNpkqH0lbn%yO}y*|-E^cWR#8&W4SO9f)5!#!f~~YJVC{WTi z>M%>`6_SlahD#1v>SmHbHd~r7GH6{lA~`M|j~bV1AS+J`Dj7lQVmS?u>;O;e$?#XJ zX+1s_zqI~RhB{QJ8P}iBYw90-hf?$+9glSOyi#RLyh_PPCaqjuFBxRLzd*8lnXF?N z5aTB>w4U{+uXPhm4UH#9Ym?EIn$~D-;|lzZzZLbTM_UH8YO(QamN%T->^3K(GG(-} zc1?Y>FL-^UZX+T!t$nNewhTn5IpZ6n^@)U=z+W4vYfdEUS47vZYFJ&5zaY4JH5Gwe zBd8E9GwRf?ZlDqewD!|7C%S9Ti1r=7vZ+4NSId%ULj6iNQQub!PfXO;Hq|evU$b^n zL!!Q}$;IC&7(J21H#SA*xJ?Z!8|o9$iEbnPCBA4wQYsgZHZ(<(tK8<*E267v*VW_C z7uI{~YJDcvude4xH#asXp=P3fWj*B8)km9L(g`|NYdurtsrs&P>yuIXQ;7AaHzb?1 zqDjqbaW@C$C`&ZCw!V&ZSy8PeCv&U*=e8m>k8NzI!yi$MN7tjd-SwRsI4O08GWe^D z%WIPjb`RX&Gohw|e6-)j~>eDsNN&cIVwb4nl zW>stXX{<#3Y0c;=G(>)5{rc#N`nuJ%38?Qja#32(Ic^k+0!Puu(Y3XSBziE}q*_v& z2tJ6lYd}aO>F-dkYgkdgA|9=6imtA&MY&C@>M=+=>B{30&DvOBkF+S-MbH1N=-^x` zEINIb)`wzhP)0F2(Y5YMFfan#Q+Xz=L8F#=sg%BQ;CD(ge=fQP^;kolik6H=>uAK1 z=;aNKwTT8urfSyL)~$+8oO^6^Z7MzbGn%ZYs$vW!o0qSI&h?4vYD}fMs?yO@>(7XC zgUg~j{ut*<>OzcC8hbnqF^aL<(1_uTKigSXzp{Dt>NBb}9-Au~R`9@;DWbJB(^7RD zPZIu13TYCWqIEo()Sm)PHo0rpqMfrW%zdrknEEy7Bss;SD--S-p62})pr2OMC(#(( z{U}Av;Arh?G}4MQ}*1s~g4SkPq}K>D^gpPlH5D?OXT11R|b6Df(0T}*L}?=qh$ zZshP@4jxj6&=TL8Z=2boZ{RXtwwQ*{6yI`Kno05F zv&Lp+i&liD_~O;OutLLXC1aEQLyA<7fZLB#)zc(%yycNva+74)O@V-T9+ z*f^EDoI`uyPc_+M@WAc^O)-vj&OeH{=O0BfHxB!$#uQg@ct3~Fa`^EuO7s0NN|Q63 z!XCpZPsQj(FQN#f}lgy>|pjdyT_o zBdHCuMiHfP)K4{*IAhd&qdNE`)mAGOwwbD7G z6QfP>5a_0O4B>pmeUiDKjeZN<)5lQh=Z>K?7jbwW!ff%{m}kaV;-@j6j=?kXu?Mp( z5g$uqV8K|D*=s!MGh#eRIC(s!e`W&JXynBC!!0q9!;dCDHPIBOP9n+IadPw z#Ely1X^F2U?Vn_djL9U;nM^gEHu+POTg&05$uC3Z%?Q!^Q%L?7Q*Q5JiEdLV95RhY z$Z69^{`nm4o_6yzQ@qJxd^)w2%i%@SDb39s{}qQrXApPc466HS9G=bLB^}4oVnsnl;X!%JPL4?c*yTMNDRIUO`3B-_IPrd|k+~j>dmoZ=KT8>my&OW%vXrAlf@}1e_AW{ohP{0% z{V-55p8wp;D4;(e#^EiDI~es*=x#^P-5)N?63&c{5`sfQz zxeZ@W(xSQITjp&OjV#mf4HZv_O9W13LQWn~Kl~x`jhwG95dB&5b6DnJpus@5YBUBX z0r}B;&oY{>(Cb8JtBfSJ(rpa zR3#n-qLjnO63;`e;|V=3a+$ZC(eLp%oKk+sXtxM6dSf!B{DUY=Lr;lfMoXp;Z?EXa z=ov=Oi5`q*O(ou+L@A?78NDFN7)hD?1dhX@JSpWRfv*z)eLaKny)61N?+Y9p@{3nR z6{DwT5qeGZPs{g)s9_{q=`Dd*4ZFfyTM4!)ix{+?qa6!v0-Nt@(L36$3s585krTYO-jf^bTYi}f1u zLXVpeH>X)S6|f#(>J{Qp&z}(fyQgWC;iG95!r`Sk2xpZ>46OF0Z^4^5v9uWBn$rCU z*Oz{&mx-@R=O9gSbRkUo>?oBxp^UU$%Ast@jU2z0!v_)8i0{g7(q-*z@xwH#(Kplg zWO!|LZm%!(h3zLo{=)Wc9N)p=ogDs-!+-ZA?hA;g*KUaTsMj0N=c`_Ci85jKrZBfR zwQx@kr}zFC+>?8MY77#|KD~Wq;@mz}2zT}witwI3;}GuaGXvqreHQp+87w^vYW{DI?darkA$JK*N^eFySa_5By(%X5E3 z{K>xAelPEz`cf|0@6>}-`YB;s^qzG}xJ-0&YW!uQuQLMaJ?>rkwm5Y3+K4S~n0snr znb_Qq(p=QkEV2nU<^Y|AK zJ~X}~{>}JTK>2JkjsI^Z_sTZK{-Y=i4xzbl#!(cWHie{}IfddEakzg9rC&OgC^F?O zQ{P3dho(~AiPLCK-8f@`Stf?e+#M(rOJ<&!2S4tpp<${^LB1)D8Y=VNq7mg{P=?ID z4B?d7SLK(9%a5j9w;WBm#t)+Q88PV6qJ2?XTecVZ%TL1S+K!2gZGZ8Y*&Q{$O`&~e zANXKYqug}z=gWp!XUJiBEnG##GZb&R$Y9T#Z8{qSRq4r(U_T>J&cXCyCJA4med z#Asu%F|Z!!&x*G`a3;=`exP`p0_{MbOCDsNgO9#xy{Ytr+AjhZ!d8uCv`73RupK`0 z8ijTScEYdR#b{@6SKun3I~BSIs6f0bksu9vEOb-|~Au2H-u@Jhu^3T+0jRNSr5 zj^Iua6%VR>w+5dDdR*~-gM4LTkK#R#e6T%?Hltl$gg@MqZsno%!O%{4z!i)(2EPeC z3)Elng4S;GhZV2HdJ1TqN9{+ZWnK7wSc#yMxr0H&emp&WjMDd@D=L3?5)}yv0w2mkURsjX8DNa#%K*l3I7Un84^@h)Q2BW++6ZFBkI_qSEWc z2?|l^btD2md$@EQykg#Fu`TCA4NrYj=nL%>L5HoOnYb+H8_g99aDkq z{X%iNLS&&Y5jQe=KXei5aEZ8Ip{IZ@6~AXhbw5epA>NTFNV@D4pE24JJi*?Hw)&?+ zC)>{geXkJBoXdrQJ0Kx825IJ8E;1Qy3DWGiLgXt%v*QZUgV82#>nnxBXlL-BcB6i! zpt~ycO3=(p0yQd>mv=hQI)!@Xwdq%htqP4|v`e97j2=M8XRQgAZDijKYsk~7NO=onBLMs`qQs^8;^v6p`%JqzPDD)3TcPNyTPcolSXgH(S z6*`*H7Yd!mNXK0x$=t@MNTFL8RV(xuqe%)KV064fUol#%kWoOTZ&7FlqiYme#^_fH zt!K1Xp=%f&Q0TXezEUVFLNYDvaFL!zF^VcQpAqeT5wDriEQNl}Xt_eqGCEzMZy9Y< zXj&naaFar(Gy07}?=jk^(83-&kn#;iTY}4bB*j(YQ-w}rWaCa7>k5y-tHk%rlm60G zqC4~U2(o8aiG19Jlij_h$1nA3#3_tqzH3DjBboAAF=`y8l)UT2Bu0{Vo%n7-2k!>q zn@C9VZV;oe|B0C+-tBRlek0D+5ZW%j>G80ByI7%6-=4qM?-Y+Q+9;;=+@s$m`b?84 zYkR(^-z)A?DB1IM{eE%YbmDCk=k+|G|5`kTdvZcMdVZomD2Cyl+|%w!io^e6D15t9^pAG~M8Y=y{AepVc-5c$c^iX{qt zjeO6El?wUG)≻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 bKqj?-f5*Rm3!Er*6J9a<|Ns8alfeHC8XNyQ diff --git a/nppRandomStringGenerator/PluginInfrastructure/DllExport/NppPlugin.DllExport.targets b/nppRandomStringGenerator/PluginInfrastructure/DllExport/NppPlugin.DllExport.targets deleted file mode 100644 index b7bdd41..0000000 --- a/nppRandomStringGenerator/PluginInfrastructure/DllExport/NppPlugin.DllExport.targets +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - $(DevEnvDir)\..\..\VC\bin - - - - - - - - - - diff --git a/nppRandomStringGenerator/PluginInfrastructure/GatewayDomain.cs b/nppRandomStringGenerator/PluginInfrastructure/GatewayDomain.cs index af8fd5b..93d7165 100644 --- a/nppRandomStringGenerator/PluginInfrastructure/GatewayDomain.cs +++ b/nppRandomStringGenerator/PluginInfrastructure/GatewayDomain.cs @@ -1,7 +1,6 @@ // NPP plugin platform for .Net v0.94.00 by Kasper B. Graversen etc. using System; using System.Runtime.InteropServices; -using System.Text; namespace Kbg.NppPluginNET.PluginInfrastructure { @@ -16,11 +15,11 @@ public class Colour public readonly int Red, Green, Blue; public Colour(int rgb) - { - Red = rgb & 0xFF; - Green = (rgb >> 8) & 0xFF; - Blue = (rgb >> 16) & 0xFF; - } + { + Red = rgb & 0xFF; + Green = (rgb >> 8) & 0xFF; + Blue = (rgb >> 16) & 0xFF; + } /// /// @@ -30,11 +29,11 @@ public Colour(int rgb) /// a number 0-255 public Colour(int red, int green, int blue) { - if(red > 255 || red < 0) + if (red > 255 || red < 0) throw new ArgumentOutOfRangeException("red", "must be 0-255"); - if(green > 255 || green < 0) + if (green > 255 || green < 0) throw new ArgumentOutOfRangeException("green", "must be 0-255"); - if(blue > 255 || blue < 0) + if (blue > 255 || blue < 0) throw new ArgumentOutOfRangeException("blue", "must be 0-255"); Red = red; Green = green; @@ -63,14 +62,17 @@ public int Value /// public class Position : IEquatable { - private readonly int pos; + private readonly Int64 pos; + + public Position(IntPtr ptr) : this(ptr.ToInt64()) + { } - public Position(int pos) + public Position(Int64 pos) { this.pos = pos; } - public int Value + public Int64 Value { get { return pos; } } @@ -87,13 +89,13 @@ public int Value public static bool operator ==(Position a, Position b) { - if (ReferenceEquals(a, b)) - return true; - if (ReferenceEquals(a, null)) - return false; - if (ReferenceEquals(b, null)) - return false; - return a.pos == b.pos; + if (ReferenceEquals(a, b)) + return true; + if (ReferenceEquals(a, null)) + return false; + if (ReferenceEquals(b, null)) + return false; + return a.pos == b.pos; } public static bool operator !=(Position a, Position b) @@ -118,14 +120,14 @@ public static Position Min(Position a, Position b) return b; } - public static Position Max(Position a, Position b) - { - if (a > b) - return a; - return b; - } + public static Position Max(Position a, Position b) + { + if (a > b) + return a; + return b; + } - public override string ToString() + public override string ToString() { return "Postion: " + pos; } @@ -147,7 +149,7 @@ public override bool Equals(object obj) public override int GetHashCode() { - return pos; + return pos.GetHashCode(); } } @@ -177,7 +179,7 @@ public class KeyModifier /// public KeyModifier(SciMsg SCK_KeyCode, SciMsg SCMOD_modifier) { - value = (int) SCK_KeyCode | ((int) SCMOD_modifier << 16); + value = (int)SCK_KeyCode | ((int)SCMOD_modifier << 16); } public int Value @@ -189,9 +191,9 @@ public int Value [StructLayout(LayoutKind.Sequential)] public struct CharacterRange { - public CharacterRange(int cpmin, int cpmax) { cpMin = cpmin; cpMax = cpmax; } - public int cpMin; - public int cpMax; + public CharacterRange(int cpmin, int cpmax) { cpMin = new IntPtr(cpmin); cpMax = new IntPtr(cpmax); } + public IntPtr cpMin; + public IntPtr cpMax; } public class Cells @@ -218,8 +220,8 @@ public TextRange(CharacterRange chrRange, int stringCapacity) } public TextRange(int cpmin, int cpmax, int stringCapacity) { - _sciTextRange.chrg.cpMin = cpmin; - _sciTextRange.chrg.cpMax = cpmax; + _sciTextRange.chrg.cpMin = new IntPtr(cpmin); + _sciTextRange.chrg.cpMax = new IntPtr(cpmax); _sciTextRange.lpstrText = Marshal.AllocHGlobal(stringCapacity); } @@ -264,728 +266,728 @@ public void Dispose() /* ++Autogenerated -- start of section automatically generated from Scintilla.iface */ - /// Is undo history being collected? (Scintilla feature SCWS_) - public enum WhiteSpace - { - INVISIBLE = 0, - VISIBLEALWAYS = 1, - VISIBLEAFTERINDENT = 2, - VISIBLEONLYININDENT = 3 - } - /// Make white space characters invisible, always visible or visible outside indentation. (Scintilla feature SCTD_) - public enum TabDrawMode - { - LONGARROW = 0, - STRIKEOUT = 1 - } - /// Retrieve the position of the last correctly styled character. (Scintilla feature SC_EOL_) - public enum EndOfLine - { - CRLF = 0, - CR = 1, - LF = 2 - } - /// - /// Set the code page used to interpret the bytes of the document as characters. - /// The SC_CP_UTF8 value can be used to enter Unicode mode. - /// (Scintilla feature SC_IME_) - /// - public enum IMEInteraction - { - WINDOWED = 0, - INLINE = 1 - } - /// Choose to display the the IME in a winow or inline. (Scintilla feature SC_MARK_) - public enum MarkerSymbol - { - CIRCLE = 0, - ROUNDRECT = 1, - ARROW = 2, - SMALLRECT = 3, - SHORTARROW = 4, - EMPTY = 5, - ARROWDOWN = 6, - MINUS = 7, - PLUS = 8, - VLINE = 9, - LCORNER = 10, - TCORNER = 11, - BOXPLUS = 12, - BOXPLUSCONNECTED = 13, - BOXMINUS = 14, - BOXMINUSCONNECTED = 15, - LCORNERCURVE = 16, - TCORNERCURVE = 17, - CIRCLEPLUS = 18, - CIRCLEPLUSCONNECTED = 19, - CIRCLEMINUS = 20, - CIRCLEMINUSCONNECTED = 21, - BACKGROUND = 22, - DOTDOTDOT = 23, - ARROWS = 24, - PIXMAP = 25, - FULLRECT = 26, - LEFTRECT = 27, - AVAILABLE = 28, - UNDERLINE = 29, - RGBAIMAGE = 30, - BOOKMARK = 31, - VERTICALBOOKMARK = 32, - CHARACTER = 10000 - } - /// Invisible mark that only sets the line background colour. (Scintilla feature SC_MARKNUM_) - public enum MarkerOutline - { - FOLDEREND = 25, - FOLDEROPENMID = 26, - FOLDERMIDTAIL = 27, - FOLDERTAIL = 28, - FOLDERSUB = 29, - FOLDER = 30, - FOLDEROPEN = 31 - } - /// Set the alpha used for a marker that is drawn in the text area, not the margin. (Scintilla feature SC_MARGIN_) - public enum MarginType - { - SYMBOL = 0, - NUMBER = 1, - BACK = 2, - FORE = 3, - TEXT = 4, - RTEXT = 5, - COLOUR = 6 - } - /// Styles in range 32..39 are predefined for parts of the UI and are not used as normal styles. (Scintilla feature STYLE_) - public enum StylesCommon - { - DEFAULT = 32, - LINENUMBER = 33, - BRACELIGHT = 34, - BRACEBAD = 35, - CONTROLCHAR = 36, - INDENTGUIDE = 37, - CALLTIP = 38, - FOLDDISPLAYTEXT = 39, - LASTPREDEFINED = 39, - MAX = 255 - } - /// - /// Character set identifiers are used in StyleSetCharacterSet. - /// The values are the same as the Windows *_CHARSET values. - /// (Scintilla feature SC_CHARSET_) - /// - public enum CharacterSet - { - ANSI = 0, - DEFAULT = 1, - BALTIC = 186, - CHINESEBIG5 = 136, - EASTEUROPE = 238, - GB2312 = 134, - GREEK = 161, - HANGUL = 129, - MAC = 77, - OEM = 255, - RUSSIAN = 204, - OEM866 = 866, - CYRILLIC = 1251, - SHIFTJIS = 128, - SYMBOL = 2, - TURKISH = 162, - JOHAB = 130, - HEBREW = 177, - ARABIC = 178, - VIETNAMESE = 163, - THAI = 222, - _8859_15 = 1000 - } - /// Set a style to be underlined or not. (Scintilla feature SC_CASE_) - public enum CaseVisible - { - MIXED = 0, - UPPER = 1, - LOWER = 2, - CAMEL = 3 - } - /// Get the size of characters of a style in points multiplied by 100 (Scintilla feature SC_WEIGHT_) - public enum FontWeight - { - NORMAL = 400, - SEMIBOLD = 600, - BOLD = 700 - } - /// Indicator style enumeration and some constants (Scintilla feature INDIC_) - public enum IndicatorStyle - { - PLAIN = 0, - SQUIGGLE = 1, - TT = 2, - DIAGONAL = 3, - STRIKE = 4, - HIDDEN = 5, - BOX = 6, - ROUNDBOX = 7, - STRAIGHTBOX = 8, - DASH = 9, - DOTS = 10, - SQUIGGLELOW = 11, - DOTBOX = 12, - SQUIGGLEPIXMAP = 13, - COMPOSITIONTHICK = 14, - COMPOSITIONTHIN = 15, - FULLBOX = 16, - TEXTFORE = 17, - POINT = 18, - POINTCHARACTER = 19, - GRADIENT = 20, - GRADIENTCENTRE = 21, - CONTAINER = 8, - IME = 32, - IME_MAX = 35, - MAX = 35 - } - /// - /// INDIC_CONTAINER, INDIC_IME, INDIC_IME_MAX, and INDIC_MAX are indicator numbers, - /// not IndicatorStyles so should not really be in the INDIC_ enumeration. - /// They are redeclared in IndicatorNumbers INDICATOR_. - /// (Scintilla feature INDICATOR_) - /// - public enum IndicatorNumbers - { - CONTAINER = 8, - IME = 32, - IME_MAX = 35, - MAX = 35 - } - /// Retrieve the foreground hover colour of an indicator. (Scintilla feature SC_INDICVALUE) - public enum IndicValue - { - BIT = 0x1000000, - MASK = 0xFFFFFF - } - /// Retrieve the foreground hover colour of an indicator. (Scintilla feature SC_INDICFLAG_) - public enum IndicFlag - { - VALUEFORE = 1 - } - /// Is the horizontal scroll bar visible? (Scintilla feature SC_IV_) - public enum IndentView - { - NONE = 0, - REAL = 1, - LOOKFORWARD = 2, - LOOKBOTH = 3 - } - /// Returns the print magnification. (Scintilla feature SC_PRINT_) - public enum PrintOption - { - NORMAL = 0, - INVERTLIGHT = 1, - BLACKONWHITE = 2, - COLOURONWHITE = 3, - COLOURONWHITEDEFAULTBG = 4, - SCREENCOLOURS = 5 - } - /// Returns the print colour mode. (Scintilla feature SCFIND_) - public enum FindOption - { - NONE = 0x0, - WHOLEWORD = 0x2, - MATCHCASE = 0x4, - WORDSTART = 0x00100000, - REGEXP = 0x00200000, - POSIX = 0x00400000, - CXX11REGEX = 0x00800000 - } - /// The number of display lines needed to wrap a document line (Scintilla feature SC_FOLDLEVEL) - public enum FoldLevel - { - BASE = 0x400, - WHITEFLAG = 0x1000, - HEADERFLAG = 0x2000, - NUMBERMASK = 0x0FFF - } - /// Switch a header line between expanded and contracted and show some text after the line. (Scintilla feature SC_FOLDDISPLAYTEXT_) - public enum FoldDisplayTextStyle - { - HIDDEN = 0, - STANDARD = 1, - BOXED = 2 - } - /// Get the default fold display text. (Scintilla feature SC_FOLDACTION_) - public enum FoldAction - { - CONTRACT = 0, - EXPAND = 1, - TOGGLE = 2 - } - /// Ensure a particular line is visible by expanding any header line hiding it. (Scintilla feature SC_AUTOMATICFOLD_) - public enum AutomaticFold - { - SHOW = 0x0001, - CLICK = 0x0002, - CHANGE = 0x0004 - } - /// Get automatic folding behaviours. (Scintilla feature SC_FOLDFLAG_) - public enum FoldFlag - { - LINEBEFORE_EXPANDED = 0x0002, - LINEBEFORE_CONTRACTED = 0x0004, - LINEAFTER_EXPANDED = 0x0008, - LINEAFTER_CONTRACTED = 0x0010, - LEVELNUMBERS = 0x0040, - LINESTATE = 0x0080 - } - /// Is the range start..end considered a word? (Scintilla feature SC_IDLESTYLING_) - public enum IdleStyling - { - NONE = 0, - TOVISIBLE = 1, - AFTERVISIBLE = 2, - ALL = 3 - } - /// Retrieve the limits to idle styling. (Scintilla feature SC_WRAP_) - public enum Wrap - { - NONE = 0, - WORD = 1, - CHAR = 2, - WHITESPACE = 3 - } - /// Retrieve whether text is word wrapped. (Scintilla feature SC_WRAPVISUALFLAG_) - public enum WrapVisualFlag - { - NONE = 0x0000, - END = 0x0001, - START = 0x0002, - MARGIN = 0x0004 - } - /// Retrive the display mode of visual flags for wrapped lines. (Scintilla feature SC_WRAPVISUALFLAGLOC_) - public enum WrapVisualLocation - { - DEFAULT = 0x0000, - END_BY_TEXT = 0x0001, - START_BY_TEXT = 0x0002 - } - /// Retrive the start indent for wrapped lines. (Scintilla feature SC_WRAPINDENT_) - public enum WrapIndentMode - { - FIXED = 0, - SAME = 1, - INDENT = 2, - DEEPINDENT = 3 - } - /// Retrieve how wrapped sublines are placed. Default is fixed. (Scintilla feature SC_CACHE_) - public enum LineCache - { - NONE = 0, - CARET = 1, - PAGE = 2, - DOCUMENT = 3 - } - /// Append a string to the end of the document without changing the selection. (Scintilla feature SC_PHASES_) - public enum PhasesDraw - { - ONE = 0, - TWO = 1, - MULTIPLE = 2 - } - /// Control font anti-aliasing. (Scintilla feature SC_EFF_) - public enum FontQuality - { - QUALITY_MASK = 0xF, - QUALITY_DEFAULT = 0, - QUALITY_NON_ANTIALIASED = 1, - QUALITY_ANTIALIASED = 2, - QUALITY_LCD_OPTIMIZED = 3 - } - /// Scroll so that a display line is at the top of the display. (Scintilla feature SC_MULTIPASTE_) - public enum MultiPaste - { - ONCE = 0, - EACH = 1 - } - /// Set the other colour used as a chequerboard pattern in the fold margin (Scintilla feature SC_ACCESSIBILITY_) - public enum Accessibility - { - DISABLED = 0, - ENABLED = 1 - } - /// Set which document modification events are sent to the container. (Scintilla feature EDGE_) - public enum EdgeVisualStyle - { - NONE = 0, - LINE = 1, - BACKGROUND = 2, - MULTILINE = 3 - } - /// Retrieves the number of lines completely visible. (Scintilla feature SC_POPUP_) - public enum PopUp - { - NEVER = 0, - ALL = 1, - TEXT = 2 - } - /// Retrieve the zoom level. (Scintilla feature SC_DOCUMENTOPTION_) - public enum DocumentOption - { - DEFAULT = 0, - STYLES_NONE = 0x1, - TEXT_LARGE = 0x100 - } - /// Get internal focus flag. (Scintilla feature SC_STATUS_) - public enum Status - { - OK = 0, - FAILURE = 1, - BADALLOC = 2, - WARN_START = 1000, - WARN_REGEX = 1001 - } - /// Get whether mouse wheel can be active outside the window. (Scintilla feature SC_CURSOR) - public enum CursorShape - { - NORMAL = -1, - ARROW = 2, - WAIT = 4, - REVERSEARROW = 7 - } - /// Constants for use with SetVisiblePolicy, similar to SetCaretPolicy. (Scintilla feature VISIBLE_) - public enum VisiblePolicy - { - SLOP = 0x01, - STRICT = 0x04 - } - /// Set the focus to this Scintilla widget. (Scintilla feature CARET_) - public enum CaretPolicy - { - SLOP = 0x01, - STRICT = 0x04, - JUMPS = 0x10, - EVEN = 0x08 - } - /// Copy argument text to the clipboard. (Scintilla feature SC_SEL_) - public enum SelectionMode - { - STREAM = 0, - RECTANGLE = 1, - LINES = 2, - THIN = 3 - } - /// - /// Get currently selected item text in the auto-completion list - /// Returns the length of the item text - /// Result is NUL-terminated. - /// (Scintilla feature SC_CASEINSENSITIVEBEHAVIOUR_) - /// - public enum CaseInsensitiveBehaviour - { - RESPECTCASE = 0, - IGNORECASE = 1 - } - /// Get auto-completion case insensitive behaviour. (Scintilla feature SC_MULTIAUTOC_) - public enum MultiAutoComplete - { - ONCE = 0, - EACH = 1 - } - /// Retrieve the effect of autocompleting when there are multiple selections. (Scintilla feature SC_ORDER_) - public enum Ordering - { - PRESORTED = 0, - PERFORMSORT = 1, - CUSTOM = 2 - } - /// Stop the caret preferred x position changing when the user types. (Scintilla feature SC_CARETSTICKY_) - public enum CaretSticky - { - OFF = 0, - ON = 1, - WHITESPACE = 2 - } - /// Duplicate the selection. If selection empty duplicate the line containing the caret. (Scintilla feature SC_ALPHA_) - public enum Alpha - { - TRANSPARENT = 0, - OPAQUE = 255, - NOALPHA = 256 - } - /// Get the background alpha of the caret line. (Scintilla feature CARETSTYLE_) - public enum CaretStyle - { - INVISIBLE = 0, - LINE = 1, - BLOCK = 2, - OVERSTRIKE_BAR = 0, - OVERSTRIKE_BLOCK = 0x10, - INS_MASK = 0xF, - BLOCK_AFTER = 0x100 - } - /// Get the start of the range of style numbers used for margin text (Scintilla feature SC_MARGINOPTION_) - public enum MarginOption - { - NONE = 0, - SUBLINESELECT = 1 - } - /// Clear the annotations from all lines (Scintilla feature ANNOTATION_) - public enum AnnotationVisible - { - HIDDEN = 0, - STANDARD = 1, - BOXED = 2, - INDENTED = 3 - } - /// Allocate some extended (>255) style numbers and return the start of the range (Scintilla feature UNDO_) - public enum UndoFlags - { - NONE = 0, - MAY_COALESCE = 1 - } - /// Return the virtual space of the anchor of the rectangular selection. (Scintilla feature SCVS_) - public enum VirtualSpace - { - NONE = 0, - RECTANGULARSELECTION = 1, - USERACCESSIBLE = 2, - NOWRAPLINESTART = 4 - } - /// Scroll to end of document. (Scintilla feature SC_TECHNOLOGY_) - public enum Technology - { - DEFAULT = 0, - DIRECTWRITE = 1, - DIRECTWRITERETAIN = 2, - DIRECTWRITEDC = 3 - } - /// - /// Line end types which may be used in addition to LF, CR, and CRLF - /// SC_LINE_END_TYPE_UNICODE includes U+2028 Line Separator, - /// U+2029 Paragraph Separator, and U+0085 Next Line - /// (Scintilla feature SC_LINE_END_TYPE_) - /// - public enum LineEndType - { - DEFAULT = 0, - UNICODE = 1 - } - /// - /// Retrieve a '\n' separated list of properties understood by the current lexer. - /// Result is NUL-terminated. - /// (Scintilla feature SC_TYPE_) - /// - public enum TypeProperty - { - BOOLEAN = 0, - INTEGER = 1, - STRING = 2 - } - /// - /// Notifications - /// Type of modification and the action which caused the modification. - /// These are defined as a bit mask to make it easy to specify which notifications are wanted. - /// One bit is set from each of SC_MOD_* and SC_PERFORMED_*. - /// (Scintilla feature SC_MOD_ SC_PERFORMED_ SC_MULTISTEPUNDOREDO SC_LASTSTEPINUNDOREDO SC_MULTILINEUNDOREDO SC_STARTACTION SC_MODEVENTMASKALL) - /// - public enum ModificationFlags - { - } - /// - /// Notifications - /// Type of modification and the action which caused the modification. - /// These are defined as a bit mask to make it easy to specify which notifications are wanted. - /// One bit is set from each of SC_MOD_* and SC_PERFORMED_*. - /// (Scintilla feature SC_UPDATE_) - /// - public enum Update - { - CONTENT = 0x1, - SELECTION = 0x2, - V_SCROLL = 0x4, - H_SCROLL = 0x8 - } - /// - /// Symbolic key codes and modifier flags. - /// ASCII and other printable characters below 256. - /// Extended keys above 300. - /// (Scintilla feature SCMOD_) - /// - public enum KeyMod - { - NORM = 0, - SHIFT = 1, - CTRL = 2, - ALT = 4, - SUPER = 8, - META = 16 - } - /// - /// Symbolic key codes and modifier flags. - /// ASCII and other printable characters below 256. - /// Extended keys above 300. - /// (Scintilla feature SC_AC_) - /// - public enum CompletionMethods - { - FILLUP = 1, - DOUBLECLICK = 2, - TAB = 3, - NEWLINE = 4, - COMMAND = 5 - } - /// characterSource for SCN_CHARADDED (Scintilla feature SC_CHARACTERSOURCE_) - public enum CharacterSource - { - DIRECT_INPUT = 0, - TENTATIVE_INPUT = 1, - IME_RESULT = 2 - } - /// For SciLexer.h (Scintilla feature SCLEX_) - public enum Lexer - { - CONTAINER = 0, - NULL = 1, - PYTHON = 2, - CPP = 3, - HTML = 4, - XML = 5, - PERL = 6, - SQL = 7, - VB = 8, - PROPERTIES = 9, - ERRORLIST = 10, - MAKEFILE = 11, - BATCH = 12, - XCODE = 13, - LATEX = 14, - LUA = 15, - DIFF = 16, - CONF = 17, - PASCAL = 18, - AVE = 19, - ADA = 20, - LISP = 21, - RUBY = 22, - EIFFEL = 23, - EIFFELKW = 24, - TCL = 25, - NNCRONTAB = 26, - BULLANT = 27, - VBSCRIPT = 28, - BAAN = 31, - MATLAB = 32, - SCRIPTOL = 33, - ASM = 34, - CPPNOCASE = 35, - FORTRAN = 36, - F77 = 37, - CSS = 38, - POV = 39, - LOUT = 40, - ESCRIPT = 41, - PS = 42, - NSIS = 43, - MMIXAL = 44, - CLW = 45, - CLWNOCASE = 46, - LOT = 47, - YAML = 48, - TEX = 49, - METAPOST = 50, - POWERBASIC = 51, - FORTH = 52, - ERLANG = 53, - OCTAVE = 54, - MSSQL = 55, - VERILOG = 56, - KIX = 57, - GUI4CLI = 58, - SPECMAN = 59, - AU3 = 60, - APDL = 61, - BASH = 62, - ASN1 = 63, - VHDL = 64, - CAML = 65, - BLITZBASIC = 66, - PUREBASIC = 67, - HASKELL = 68, - PHPSCRIPT = 69, - TADS3 = 70, - REBOL = 71, - SMALLTALK = 72, - FLAGSHIP = 73, - CSOUND = 74, - FREEBASIC = 75, - INNOSETUP = 76, - OPAL = 77, - SPICE = 78, - D = 79, - CMAKE = 80, - GAP = 81, - PLM = 82, - PROGRESS = 83, - ABAQUS = 84, - ASYMPTOTE = 85, - R = 86, - MAGIK = 87, - POWERSHELL = 88, - MYSQL = 89, - PO = 90, - TAL = 91, - COBOL = 92, - TACL = 93, - SORCUS = 94, - POWERPRO = 95, - NIMROD = 96, - SML = 97, - MARKDOWN = 98, - TXT2TAGS = 99, - A68K = 100, - MODULA = 101, - COFFEESCRIPT = 102, - TCMD = 103, - AVS = 104, - ECL = 105, - OSCRIPT = 106, - VISUALPROLOG = 107, - LITERATEHASKELL = 108, - STTXT = 109, - KVIRC = 110, - RUST = 111, - DMAP = 112, - AS = 113, - DMIS = 114, - REGISTRY = 115, - BIBTEX = 116, - SREC = 117, - IHEX = 118, - TEHEX = 119, - JSON = 120, - EDIFACT = 121, - INDENT = 122, - MAXIMA = 123, - STATA = 124, - SAS = 125, - NIM = 126, - CIL = 127, - X12 = 128, - DATAFLEX = 129, - AUTOMATIC = 1000 - } - /// GTK Specific to work around focus and accelerator problems: (Scintilla feature SC_BIDIRECTIONAL_) - public enum Bidirectional - { - DISABLED = 0, - L2R = 1, - R2L = 2 - } - /// Set bidirectional text display state. (Scintilla feature SC_LINECHARACTERINDEX_) - public enum LineCharacterIndexType - { - NONE = 0, - UTF32 = 1, - UTF16 = 2 - } + /// Is undo history being collected? (Scintilla feature SCWS_) + public enum WhiteSpace + { + INVISIBLE = 0, + VISIBLEALWAYS = 1, + VISIBLEAFTERINDENT = 2, + VISIBLEONLYININDENT = 3 + } + /// Make white space characters invisible, always visible or visible outside indentation. (Scintilla feature SCTD_) + public enum TabDrawMode + { + LONGARROW = 0, + STRIKEOUT = 1 + } + /// Retrieve the position of the last correctly styled character. (Scintilla feature SC_EOL_) + public enum EndOfLine + { + CRLF = 0, + CR = 1, + LF = 2 + } + /// + /// Set the code page used to interpret the bytes of the document as characters. + /// The SC_CP_UTF8 value can be used to enter Unicode mode. + /// (Scintilla feature SC_IME_) + /// + public enum IMEInteraction + { + WINDOWED = 0, + INLINE = 1 + } + /// Choose to display the the IME in a winow or inline. (Scintilla feature SC_MARK_) + public enum MarkerSymbol + { + CIRCLE = 0, + ROUNDRECT = 1, + ARROW = 2, + SMALLRECT = 3, + SHORTARROW = 4, + EMPTY = 5, + ARROWDOWN = 6, + MINUS = 7, + PLUS = 8, + VLINE = 9, + LCORNER = 10, + TCORNER = 11, + BOXPLUS = 12, + BOXPLUSCONNECTED = 13, + BOXMINUS = 14, + BOXMINUSCONNECTED = 15, + LCORNERCURVE = 16, + TCORNERCURVE = 17, + CIRCLEPLUS = 18, + CIRCLEPLUSCONNECTED = 19, + CIRCLEMINUS = 20, + CIRCLEMINUSCONNECTED = 21, + BACKGROUND = 22, + DOTDOTDOT = 23, + ARROWS = 24, + PIXMAP = 25, + FULLRECT = 26, + LEFTRECT = 27, + AVAILABLE = 28, + UNDERLINE = 29, + RGBAIMAGE = 30, + BOOKMARK = 31, + VERTICALBOOKMARK = 32, + CHARACTER = 10000 + } + /// Invisible mark that only sets the line background colour. (Scintilla feature SC_MARKNUM_) + public enum MarkerOutline + { + FOLDEREND = 25, + FOLDEROPENMID = 26, + FOLDERMIDTAIL = 27, + FOLDERTAIL = 28, + FOLDERSUB = 29, + FOLDER = 30, + FOLDEROPEN = 31 + } + /// Set the alpha used for a marker that is drawn in the text area, not the margin. (Scintilla feature SC_MARGIN_) + public enum MarginType + { + SYMBOL = 0, + NUMBER = 1, + BACK = 2, + FORE = 3, + TEXT = 4, + RTEXT = 5, + COLOUR = 6 + } + /// Styles in range 32..39 are predefined for parts of the UI and are not used as normal styles. (Scintilla feature STYLE_) + public enum StylesCommon + { + DEFAULT = 32, + LINENUMBER = 33, + BRACELIGHT = 34, + BRACEBAD = 35, + CONTROLCHAR = 36, + INDENTGUIDE = 37, + CALLTIP = 38, + FOLDDISPLAYTEXT = 39, + LASTPREDEFINED = 39, + MAX = 255 + } + /// + /// Character set identifiers are used in StyleSetCharacterSet. + /// The values are the same as the Windows *_CHARSET values. + /// (Scintilla feature SC_CHARSET_) + /// + public enum CharacterSet + { + ANSI = 0, + DEFAULT = 1, + BALTIC = 186, + CHINESEBIG5 = 136, + EASTEUROPE = 238, + GB2312 = 134, + GREEK = 161, + HANGUL = 129, + MAC = 77, + OEM = 255, + RUSSIAN = 204, + OEM866 = 866, + CYRILLIC = 1251, + SHIFTJIS = 128, + SYMBOL = 2, + TURKISH = 162, + JOHAB = 130, + HEBREW = 177, + ARABIC = 178, + VIETNAMESE = 163, + THAI = 222, + _8859_15 = 1000 + } + /// Set a style to be underlined or not. (Scintilla feature SC_CASE_) + public enum CaseVisible + { + MIXED = 0, + UPPER = 1, + LOWER = 2, + CAMEL = 3 + } + /// Get the size of characters of a style in points multiplied by 100 (Scintilla feature SC_WEIGHT_) + public enum FontWeight + { + NORMAL = 400, + SEMIBOLD = 600, + BOLD = 700 + } + /// Indicator style enumeration and some constants (Scintilla feature INDIC_) + public enum IndicatorStyle + { + PLAIN = 0, + SQUIGGLE = 1, + TT = 2, + DIAGONAL = 3, + STRIKE = 4, + HIDDEN = 5, + BOX = 6, + ROUNDBOX = 7, + STRAIGHTBOX = 8, + DASH = 9, + DOTS = 10, + SQUIGGLELOW = 11, + DOTBOX = 12, + SQUIGGLEPIXMAP = 13, + COMPOSITIONTHICK = 14, + COMPOSITIONTHIN = 15, + FULLBOX = 16, + TEXTFORE = 17, + POINT = 18, + POINTCHARACTER = 19, + GRADIENT = 20, + GRADIENTCENTRE = 21, + CONTAINER = 8, + IME = 32, + IME_MAX = 35, + MAX = 35 + } + /// + /// INDIC_CONTAINER, INDIC_IME, INDIC_IME_MAX, and INDIC_MAX are indicator numbers, + /// not IndicatorStyles so should not really be in the INDIC_ enumeration. + /// They are redeclared in IndicatorNumbers INDICATOR_. + /// (Scintilla feature INDICATOR_) + /// + public enum IndicatorNumbers + { + CONTAINER = 8, + IME = 32, + IME_MAX = 35, + MAX = 35 + } + /// Retrieve the foreground hover colour of an indicator. (Scintilla feature SC_INDICVALUE) + public enum IndicValue + { + BIT = 0x1000000, + MASK = 0xFFFFFF + } + /// Retrieve the foreground hover colour of an indicator. (Scintilla feature SC_INDICFLAG_) + public enum IndicFlag + { + VALUEFORE = 1 + } + /// Is the horizontal scroll bar visible? (Scintilla feature SC_IV_) + public enum IndentView + { + NONE = 0, + REAL = 1, + LOOKFORWARD = 2, + LOOKBOTH = 3 + } + /// Returns the print magnification. (Scintilla feature SC_PRINT_) + public enum PrintOption + { + NORMAL = 0, + INVERTLIGHT = 1, + BLACKONWHITE = 2, + COLOURONWHITE = 3, + COLOURONWHITEDEFAULTBG = 4, + SCREENCOLOURS = 5 + } + /// Returns the print colour mode. (Scintilla feature SCFIND_) + public enum FindOption + { + NONE = 0x0, + WHOLEWORD = 0x2, + MATCHCASE = 0x4, + WORDSTART = 0x00100000, + REGEXP = 0x00200000, + POSIX = 0x00400000, + CXX11REGEX = 0x00800000 + } + /// The number of display lines needed to wrap a document line (Scintilla feature SC_FOLDLEVEL) + public enum FoldLevel + { + BASE = 0x400, + WHITEFLAG = 0x1000, + HEADERFLAG = 0x2000, + NUMBERMASK = 0x0FFF + } + /// Switch a header line between expanded and contracted and show some text after the line. (Scintilla feature SC_FOLDDISPLAYTEXT_) + public enum FoldDisplayTextStyle + { + HIDDEN = 0, + STANDARD = 1, + BOXED = 2 + } + /// Get the default fold display text. (Scintilla feature SC_FOLDACTION_) + public enum FoldAction + { + CONTRACT = 0, + EXPAND = 1, + TOGGLE = 2 + } + /// Ensure a particular line is visible by expanding any header line hiding it. (Scintilla feature SC_AUTOMATICFOLD_) + public enum AutomaticFold + { + SHOW = 0x0001, + CLICK = 0x0002, + CHANGE = 0x0004 + } + /// Get automatic folding behaviours. (Scintilla feature SC_FOLDFLAG_) + public enum FoldFlag + { + LINEBEFORE_EXPANDED = 0x0002, + LINEBEFORE_CONTRACTED = 0x0004, + LINEAFTER_EXPANDED = 0x0008, + LINEAFTER_CONTRACTED = 0x0010, + LEVELNUMBERS = 0x0040, + LINESTATE = 0x0080 + } + /// Is the range start..end considered a word? (Scintilla feature SC_IDLESTYLING_) + public enum IdleStyling + { + NONE = 0, + TOVISIBLE = 1, + AFTERVISIBLE = 2, + ALL = 3 + } + /// Retrieve the limits to idle styling. (Scintilla feature SC_WRAP_) + public enum Wrap + { + NONE = 0, + WORD = 1, + CHAR = 2, + WHITESPACE = 3 + } + /// Retrieve whether text is word wrapped. (Scintilla feature SC_WRAPVISUALFLAG_) + public enum WrapVisualFlag + { + NONE = 0x0000, + END = 0x0001, + START = 0x0002, + MARGIN = 0x0004 + } + /// Retrive the display mode of visual flags for wrapped lines. (Scintilla feature SC_WRAPVISUALFLAGLOC_) + public enum WrapVisualLocation + { + DEFAULT = 0x0000, + END_BY_TEXT = 0x0001, + START_BY_TEXT = 0x0002 + } + /// Retrive the start indent for wrapped lines. (Scintilla feature SC_WRAPINDENT_) + public enum WrapIndentMode + { + FIXED = 0, + SAME = 1, + INDENT = 2, + DEEPINDENT = 3 + } + /// Retrieve how wrapped sublines are placed. Default is fixed. (Scintilla feature SC_CACHE_) + public enum LineCache + { + NONE = 0, + CARET = 1, + PAGE = 2, + DOCUMENT = 3 + } + /// Append a string to the end of the document without changing the selection. (Scintilla feature SC_PHASES_) + public enum PhasesDraw + { + ONE = 0, + TWO = 1, + MULTIPLE = 2 + } + /// Control font anti-aliasing. (Scintilla feature SC_EFF_) + public enum FontQuality + { + QUALITY_MASK = 0xF, + QUALITY_DEFAULT = 0, + QUALITY_NON_ANTIALIASED = 1, + QUALITY_ANTIALIASED = 2, + QUALITY_LCD_OPTIMIZED = 3 + } + /// Scroll so that a display line is at the top of the display. (Scintilla feature SC_MULTIPASTE_) + public enum MultiPaste + { + ONCE = 0, + EACH = 1 + } + /// Set the other colour used as a chequerboard pattern in the fold margin (Scintilla feature SC_ACCESSIBILITY_) + public enum Accessibility + { + DISABLED = 0, + ENABLED = 1 + } + /// Set which document modification events are sent to the container. (Scintilla feature EDGE_) + public enum EdgeVisualStyle + { + NONE = 0, + LINE = 1, + BACKGROUND = 2, + MULTILINE = 3 + } + /// Retrieves the number of lines completely visible. (Scintilla feature SC_POPUP_) + public enum PopUp + { + NEVER = 0, + ALL = 1, + TEXT = 2 + } + /// Retrieve the zoom level. (Scintilla feature SC_DOCUMENTOPTION_) + public enum DocumentOption + { + DEFAULT = 0, + STYLES_NONE = 0x1, + TEXT_LARGE = 0x100 + } + /// Get internal focus flag. (Scintilla feature SC_STATUS_) + public enum Status + { + OK = 0, + FAILURE = 1, + BADALLOC = 2, + WARN_START = 1000, + WARN_REGEX = 1001 + } + /// Get whether mouse wheel can be active outside the window. (Scintilla feature SC_CURSOR) + public enum CursorShape + { + NORMAL = -1, + ARROW = 2, + WAIT = 4, + REVERSEARROW = 7 + } + /// Constants for use with SetVisiblePolicy, similar to SetCaretPolicy. (Scintilla feature VISIBLE_) + public enum VisiblePolicy + { + SLOP = 0x01, + STRICT = 0x04 + } + /// Set the focus to this Scintilla widget. (Scintilla feature CARET_) + public enum CaretPolicy + { + SLOP = 0x01, + STRICT = 0x04, + JUMPS = 0x10, + EVEN = 0x08 + } + /// Copy argument text to the clipboard. (Scintilla feature SC_SEL_) + public enum SelectionMode + { + STREAM = 0, + RECTANGLE = 1, + LINES = 2, + THIN = 3 + } + /// + /// Get currently selected item text in the auto-completion list + /// Returns the length of the item text + /// Result is NUL-terminated. + /// (Scintilla feature SC_CASEINSENSITIVEBEHAVIOUR_) + /// + public enum CaseInsensitiveBehaviour + { + RESPECTCASE = 0, + IGNORECASE = 1 + } + /// Get auto-completion case insensitive behaviour. (Scintilla feature SC_MULTIAUTOC_) + public enum MultiAutoComplete + { + ONCE = 0, + EACH = 1 + } + /// Retrieve the effect of autocompleting when there are multiple selections. (Scintilla feature SC_ORDER_) + public enum Ordering + { + PRESORTED = 0, + PERFORMSORT = 1, + CUSTOM = 2 + } + /// Stop the caret preferred x position changing when the user types. (Scintilla feature SC_CARETSTICKY_) + public enum CaretSticky + { + OFF = 0, + ON = 1, + WHITESPACE = 2 + } + /// Duplicate the selection. If selection empty duplicate the line containing the caret. (Scintilla feature SC_ALPHA_) + public enum Alpha + { + TRANSPARENT = 0, + OPAQUE = 255, + NOALPHA = 256 + } + /// Get the background alpha of the caret line. (Scintilla feature CARETSTYLE_) + public enum CaretStyle + { + INVISIBLE = 0, + LINE = 1, + BLOCK = 2, + OVERSTRIKE_BAR = 0, + OVERSTRIKE_BLOCK = 0x10, + INS_MASK = 0xF, + BLOCK_AFTER = 0x100 + } + /// Get the start of the range of style numbers used for margin text (Scintilla feature SC_MARGINOPTION_) + public enum MarginOption + { + NONE = 0, + SUBLINESELECT = 1 + } + /// Clear the annotations from all lines (Scintilla feature ANNOTATION_) + public enum AnnotationVisible + { + HIDDEN = 0, + STANDARD = 1, + BOXED = 2, + INDENTED = 3 + } + /// Allocate some extended (>255) style numbers and return the start of the range (Scintilla feature UNDO_) + public enum UndoFlags + { + NONE = 0, + MAY_COALESCE = 1 + } + /// Return the virtual space of the anchor of the rectangular selection. (Scintilla feature SCVS_) + public enum VirtualSpace + { + NONE = 0, + RECTANGULARSELECTION = 1, + USERACCESSIBLE = 2, + NOWRAPLINESTART = 4 + } + /// Scroll to end of document. (Scintilla feature SC_TECHNOLOGY_) + public enum Technology + { + DEFAULT = 0, + DIRECTWRITE = 1, + DIRECTWRITERETAIN = 2, + DIRECTWRITEDC = 3 + } + /// + /// Line end types which may be used in addition to LF, CR, and CRLF + /// SC_LINE_END_TYPE_UNICODE includes U+2028 Line Separator, + /// U+2029 Paragraph Separator, and U+0085 Next Line + /// (Scintilla feature SC_LINE_END_TYPE_) + /// + public enum LineEndType + { + DEFAULT = 0, + UNICODE = 1 + } + /// + /// Retrieve a '\n' separated list of properties understood by the current lexer. + /// Result is NUL-terminated. + /// (Scintilla feature SC_TYPE_) + /// + public enum TypeProperty + { + BOOLEAN = 0, + INTEGER = 1, + STRING = 2 + } + /// + /// Notifications + /// Type of modification and the action which caused the modification. + /// These are defined as a bit mask to make it easy to specify which notifications are wanted. + /// One bit is set from each of SC_MOD_* and SC_PERFORMED_*. + /// (Scintilla feature SC_MOD_ SC_PERFORMED_ SC_MULTISTEPUNDOREDO SC_LASTSTEPINUNDOREDO SC_MULTILINEUNDOREDO SC_STARTACTION SC_MODEVENTMASKALL) + /// + public enum ModificationFlags + { + } + /// + /// Notifications + /// Type of modification and the action which caused the modification. + /// These are defined as a bit mask to make it easy to specify which notifications are wanted. + /// One bit is set from each of SC_MOD_* and SC_PERFORMED_*. + /// (Scintilla feature SC_UPDATE_) + /// + public enum Update + { + CONTENT = 0x1, + SELECTION = 0x2, + V_SCROLL = 0x4, + H_SCROLL = 0x8 + } + /// + /// Symbolic key codes and modifier flags. + /// ASCII and other printable characters below 256. + /// Extended keys above 300. + /// (Scintilla feature SCMOD_) + /// + public enum KeyMod + { + NORM = 0, + SHIFT = 1, + CTRL = 2, + ALT = 4, + SUPER = 8, + META = 16 + } + /// + /// Symbolic key codes and modifier flags. + /// ASCII and other printable characters below 256. + /// Extended keys above 300. + /// (Scintilla feature SC_AC_) + /// + public enum CompletionMethods + { + FILLUP = 1, + DOUBLECLICK = 2, + TAB = 3, + NEWLINE = 4, + COMMAND = 5 + } + /// characterSource for SCN_CHARADDED (Scintilla feature SC_CHARACTERSOURCE_) + public enum CharacterSource + { + DIRECT_INPUT = 0, + TENTATIVE_INPUT = 1, + IME_RESULT = 2 + } + /// For SciLexer.h (Scintilla feature SCLEX_) + public enum Lexer + { + CONTAINER = 0, + NULL = 1, + PYTHON = 2, + CPP = 3, + HTML = 4, + XML = 5, + PERL = 6, + SQL = 7, + VB = 8, + PROPERTIES = 9, + ERRORLIST = 10, + MAKEFILE = 11, + BATCH = 12, + XCODE = 13, + LATEX = 14, + LUA = 15, + DIFF = 16, + CONF = 17, + PASCAL = 18, + AVE = 19, + ADA = 20, + LISP = 21, + RUBY = 22, + EIFFEL = 23, + EIFFELKW = 24, + TCL = 25, + NNCRONTAB = 26, + BULLANT = 27, + VBSCRIPT = 28, + BAAN = 31, + MATLAB = 32, + SCRIPTOL = 33, + ASM = 34, + CPPNOCASE = 35, + FORTRAN = 36, + F77 = 37, + CSS = 38, + POV = 39, + LOUT = 40, + ESCRIPT = 41, + PS = 42, + NSIS = 43, + MMIXAL = 44, + CLW = 45, + CLWNOCASE = 46, + LOT = 47, + YAML = 48, + TEX = 49, + METAPOST = 50, + POWERBASIC = 51, + FORTH = 52, + ERLANG = 53, + OCTAVE = 54, + MSSQL = 55, + VERILOG = 56, + KIX = 57, + GUI4CLI = 58, + SPECMAN = 59, + AU3 = 60, + APDL = 61, + BASH = 62, + ASN1 = 63, + VHDL = 64, + CAML = 65, + BLITZBASIC = 66, + PUREBASIC = 67, + HASKELL = 68, + PHPSCRIPT = 69, + TADS3 = 70, + REBOL = 71, + SMALLTALK = 72, + FLAGSHIP = 73, + CSOUND = 74, + FREEBASIC = 75, + INNOSETUP = 76, + OPAL = 77, + SPICE = 78, + D = 79, + CMAKE = 80, + GAP = 81, + PLM = 82, + PROGRESS = 83, + ABAQUS = 84, + ASYMPTOTE = 85, + R = 86, + MAGIK = 87, + POWERSHELL = 88, + MYSQL = 89, + PO = 90, + TAL = 91, + COBOL = 92, + TACL = 93, + SORCUS = 94, + POWERPRO = 95, + NIMROD = 96, + SML = 97, + MARKDOWN = 98, + TXT2TAGS = 99, + A68K = 100, + MODULA = 101, + COFFEESCRIPT = 102, + TCMD = 103, + AVS = 104, + ECL = 105, + OSCRIPT = 106, + VISUALPROLOG = 107, + LITERATEHASKELL = 108, + STTXT = 109, + KVIRC = 110, + RUST = 111, + DMAP = 112, + AS = 113, + DMIS = 114, + REGISTRY = 115, + BIBTEX = 116, + SREC = 117, + IHEX = 118, + TEHEX = 119, + JSON = 120, + EDIFACT = 121, + INDENT = 122, + MAXIMA = 123, + STATA = 124, + SAS = 125, + NIM = 126, + CIL = 127, + X12 = 128, + DATAFLEX = 129, + AUTOMATIC = 1000 + } + /// GTK Specific to work around focus and accelerator problems: (Scintilla feature SC_BIDIRECTIONAL_) + public enum Bidirectional + { + DISABLED = 0, + L2R = 1, + R2L = 2 + } + /// Set bidirectional text display state. (Scintilla feature SC_LINECHARACTERINDEX_) + public enum LineCharacterIndexType + { + NONE = 0, + UTF32 = 1, + UTF16 = 2 + } /* --Autogenerated -- end of section automatically generated from Scintilla.iface */ } diff --git a/nppRandomStringGenerator/PluginInfrastructure/IScintillaGateway.cs b/nppRandomStringGenerator/PluginInfrastructure/IScintillaGateway.cs index 981e1b7..0468a48 100644 --- a/nppRandomStringGenerator/PluginInfrastructure/IScintillaGateway.cs +++ b/nppRandomStringGenerator/PluginInfrastructure/IScintillaGateway.cs @@ -55,7 +55,15 @@ public interface IScintillaGateway void ClearDocumentStyle(); /// Returns the number of bytes in the document. (Scintilla feature 2006) - int GetLength(); + long GetLength(); + + /// + /// If would return a value greater than , return false and set result to -1.

+ /// Otherwise, return true and set result to the number of bytes in the document. + ///
+ /// + /// + bool TryGetLengthAsInt(out int result); /// Returns the character byte at the position. (Scintilla feature 2007) int GetCharAt(int pos); @@ -157,7 +165,7 @@ public interface IScintillaGateway /// Result is NUL-terminated. /// (Scintilla feature 2027) /// - unsafe string GetCurLine(int length); + unsafe string GetCurLine(); /// Retrieve the position of the last correctly styled character. (Scintilla feature 2028) int GetEndStyled(); @@ -909,12 +917,12 @@ public interface IScintillaGateway unsafe void SetText(string text); /// - /// Retrieve all the text in the document. - /// Returns number of characters retrieved. - /// Result is NUL-terminated. + /// You may wish to use Npp.TryGetLengthAsInt instead, as it indicates when the length of the document is too great.

+ /// If length = -1 and the document has more than characters, return "".

+ /// Otherwise, returns all the text in the document if length = -1 (or the first length chars of the document).

/// (Scintilla feature 2182) ///
- unsafe string GetText(int length); + unsafe string GetText(int length = -1); /// Retrieve the number of characters in the document. (Scintilla feature 2183) int GetTextLength(); @@ -2516,7 +2524,10 @@ public interface IScintillaGateway /// Set up the key words used by the lexer. (Scintilla feature 4005) unsafe void SetKeyWords(int keyWordSet, string keyWords); - /// Set the lexing language of the document based on string name. (Scintilla feature 4006) + /// Set the lexing language of the document based on string name. (Scintilla feature 4006)

+ /// NOTE! This function does not seem to do anything as of 08/25/2022.

+ /// Use INotepadNPPGateWay.SetCurrentLanguage instead. + ///
unsafe void SetLexerLanguage(string language); /// Load a lexer library (dll / so). (Scintilla feature 4007) diff --git a/nppRandomStringGenerator/PluginInfrastructure/MenuCmdID_h.cs b/nppRandomStringGenerator/PluginInfrastructure/MenuCmdID_h.cs index b4e2a28..756c4e0 100644 --- a/nppRandomStringGenerator/PluginInfrastructure/MenuCmdID_h.cs +++ b/nppRandomStringGenerator/PluginInfrastructure/MenuCmdID_h.cs @@ -20,7 +20,7 @@ public enum NppMenuCmd : uint IDM_FILE_SAVE = (IDM_FILE + 6), IDM_FILE_SAVEALL = (IDM_FILE + 7), IDM_FILE_SAVEAS = (IDM_FILE + 8), - //IDM_FILE_ASIAN_LANG = (IDM_FILE + 9), + //IDM_FILE_ASIAN_LANG = (IDM_FILE + 9), IDM_FILE_PRINT = (IDM_FILE + 10), IDM_FILE_PRINTNOW = 1001, IDM_FILE_EXIT = (IDM_FILE + 11), diff --git a/nppRandomStringGenerator/PluginInfrastructure/Msgs_h.cs b/nppRandomStringGenerator/PluginInfrastructure/Msgs_h.cs index a9efd7b..747feaf 100644 --- a/nppRandomStringGenerator/PluginInfrastructure/Msgs_h.cs +++ b/nppRandomStringGenerator/PluginInfrastructure/Msgs_h.cs @@ -24,7 +24,13 @@ public enum LangType L_ASM, L_DIFF, L_PROPS, L_PS, L_RUBY, L_SMALLTALK, L_VHDL, L_KIX, L_AU3, L_CAML, L_ADA, L_VERILOG, L_MATLAB, L_HASKELL, L_INNO, L_SEARCHRESULT, L_CMAKE, L_YAML, L_COBOL, L_GUI4CLI, L_D, L_POWERSHELL, L_R, L_JSP, - L_COFFEESCRIPT, L_JSON, L_JAVASCRIPT, L_FORTRAN_77, + L_COFFEESCRIPT, L_JSON, L_JAVASCRIPT, L_FORTRAN_77, L_BAANC, L_SREC, + L_IHEX, L_TEHEX, L_SWIFT, + L_ASN1, L_AVS, L_BLITZBASIC, L_PUREBASIC, L_FREEBASIC, + L_CSOUND, L_ERLANG, L_ESCRIPT, L_FORTH, L_LATEX, + L_MMIXAL, L_NIM, L_NNCRONTAB, L_OSCRIPT, L_REBOL, + L_REGISTRY, L_RUST, L_SPICE, L_TXT2TAGS, L_VISUALPROLOG, + L_TYPESCRIPT, L_JSON5, L_MSSQL, L_GDSCRIPT, L_HOLLYWOOD, // Don't use L_JS, use L_JAVASCRIPT instead // The end of enumated language type, so it should be always at the end L_EXTERNAL @@ -45,7 +51,6 @@ public enum NppMsg : uint ALL_OPEN_FILES = 0, PRIMARY_VIEW = 1, SECOND_VIEW = 2, - NPPM_GETOPENFILENAMES = Constants.NPPMSG + 8, NPPM_MODELESSDIALOG = Constants.NPPMSG + 12, @@ -76,12 +81,12 @@ public enum NppMsg : uint STATUSBAR_UNICODE_TYPE = 4, STATUSBAR_TYPING_MODE = 5, - NPPM_GETMENUHANDLE = Constants.NPPMSG + 25, - NPPPLUGINMENU = 0, /// - /// INT NPPM_GETMENUHANDLE(INT menuChoice, 0) - /// Return: menu handle (HMENU) of choice (plugin menu handle or Notepad++ main menu handle) + /// INT NPPM_GETMENUHANDLE(INT menuChoice, 0)

+ /// Return: menu handle (HMENU) of choice (plugin menu handle () or Notepad++ main menu handle ()) ///
+ NPPM_GETMENUHANDLE = Constants.NPPMSG + 25, + NPPPLUGINMENU = 0, NPPMAINMENU = 1, /// @@ -527,6 +532,66 @@ public enum NppMsg : uint /// NPPM_GETPLUGINHOMEPATH = Constants.NPPMSG + 97, + /// + /// INT NPPM_GETSETTINGSCLOUDPATH(size_t strLen, TCHAR *settingsOnCloudPath) + /// Get settings on cloud path. It's useful if plugins want to store its settings on Cloud, if this path is set. + /// Returns the number of TCHAR copied/to copy. If the return value is 0, then this path is not set, or the "strLen" is not enough to copy the path. + /// Users should call it with settingsCloudPath be NULL to get the required number of TCHAR (not including the terminating nul character), + /// allocate settingsCloudPath buffer with the return value + 1, then call it again to get the path. + /// + NPPM_GETSETTINGSONCLOUDPATH = Constants.NPPMSG + 98, + + /// + /// BOOL NPPM_SETLINENUMBERWIDTHMODE(0, INT widthMode) + /// Set line number margin width in dynamic width mode (LINENUMWIDTH_DYNAMIC) or constant width mode (LINENUMWIDTH_CONSTANT) + /// It may help some plugins to disable non-dynamic line number margins width to have a smoothly visual effect while vertical scrolling the content in Notepad++ + /// If calling is successful return TRUE, otherwise return FALSE. + /// + NPPM_SETLINENUMBERWIDTHMODE = Constants.NPPMSG + 99, + LINENUMWIDTH_DYNAMIC = 0, + LINENUMWIDTH_CONSTANT = 1, + + /// + /// INT NPPM_GETLINENUMBERWIDTHMODE(0, 0) + /// Get line number margin width in dynamic width mode (LINENUMWIDTH_DYNAMIC) or constant width mode (LINENUMWIDTH_CONSTANT) + /// + NPPM_GETLINENUMBERWIDTHMODE = Constants.NPPMSG + 100, + + /// + /// VOID NPPM_ADDTOOLBARICON_FORDARKMODE(UINT funcItem[X]._cmdID, toolbarIconsWithDarkMode iconHandles) + /// Use NPPM_ADDTOOLBARICON_FORDARKMODE instead obsolete NPPM_ADDTOOLBARICON which doesn't support the dark mode + /// 2 formats / 3 icons are needed: 1 * BMP + 2 * ICO + /// All 3 handles below should be set so the icon will be displayed correctly if toolbar icon sets are changed by users, also in dark mode + /// + NPPM_ADDTOOLBARICON_FORDARKMODE = Constants.NPPMSG + 101, + + // BOOL NPPM_ALLOCATEINDICATOR(int numberRequested, int* startNumber) + // Allocates an indicator number to a plugin: if a plugin needs to add an indicator, + // it has to use this message to get the indicator number, in order to prevent a conflict with the other plugins. + // wParam[in]: numberRequested is the number of ID you request for the reservation + // lParam[out]: startNumber will be set to the initial command ID if successful + // Return TRUE if successful, FALSE otherwise. startNumber will also be set to 0 if unsuccessful + // + // Example: If a plugin needs 1 indicator ID, the following code can be used : + // + // int idBegin; + // BOOL isAllocatedSuccessful = ::SendMessage(nppData._nppHandle, NPPM_ALLOCATEINDICATOR, 1, &idBegin); + // + // if isAllocatedSuccessful is TRUE, and value of idBegin is 7 + // then indicator ID 7 is preserved by Notepad++, and it is safe to be used by the plugin. + NPPM_ALLOCATEINDICATOR = Constants.NPPMSG + 113, + + /// + /// int NPPM_GETNATIVELANGFILENAME(size_t strLen, char* nativeLangFileName)

+ /// Get the Current native language file name string.

+ /// Users should call it with nativeLangFileName as NULL to get the required number of char (not including the terminating nul character),

+ /// allocate commandLineStr buffer with the return value + 1, then call it again to get the current native language file name string.

+ /// wParam[in]: strLen is "commandLineStr" buffer length

+ /// lParam[out]: commandLineStr recieves all copied native language file name string

+ /// Return the number of char copied/to copy + ///
+ NPPM_GETNATIVELANGFILENAME = Constants.NPPMSG + 116, + RUNCOMMAND_USER = Constants.WM_USER + 3000, NPPM_GETFULLCURRENTPATH = RUNCOMMAND_USER + FULL_CURRENT_PATH, NPPM_GETCURRENTDIRECTORY = RUNCOMMAND_USER + CURRENT_DIRECTORY, @@ -772,6 +837,62 @@ public enum NppMsg : uint /// NPPN_FILEDELETED = NPPN_FIRST + 26, + /// + /// To notify plugins that Dark Mode was enabled/disabled + /// scnNotification->nmhdr.code = NPPN_DARKMODECHANGED; + /// scnNotification->nmhdr.hwndFrom = hwndNpp; + /// scnNotification->nmhdr.idFrom = 0; + /// + NPPN_DARKMODECHANGED = NPPN_FIRST + 27, + + /// + /// To notify plugins that the new argument for plugins (via '-pluginMessage="YOUR_PLUGIN_ARGUMENT"' in command line) is available + /// scnNotification->nmhdr.code = NPPN_CMDLINEPLUGINMSG; + /// scnNotification->nmhdr.hwndFrom = hwndNpp; + /// scnNotification->nmhdr.idFrom = pluginMessage; //where pluginMessage is pointer of type wchar_t + /// + NPPN_CMDLINEPLUGINMSG = NPPN_FIRST + 28, + + /// + /// To notify lexer plugins that the buffer (in idFrom) is just applied to a external lexer + /// scnNotification->nmhdr.code = NPPN_EXTERNALLEXERBUFFER; + /// scnNotification->nmhdr.hwndFrom = hwndNpp; + /// scnNotification->nmhdr.idFrom = BufferID; //where pluginMessage is pointer of type wchar_t + /// + NPPN_EXTERNALLEXERBUFFER = NPPN_FIRST + 29, + + /// + /// To notify plugins that the current document is just modified by Replace All action.

+ /// For solving the performance issue (from v8.6.4), Notepad++ doesn't trigger SCN_MODIFIED during Replace All action anymore.

+ /// As a result, the plugins which monitor SCN_MODIFIED should also monitor NPPN_GLOBALMODIFIED.

+ /// This notification is implemented in Notepad++ v8.6.5.

+ /// scnNotification->nmhdr.code = NPPN_GLOBALMODIFIED;

+ /// scnNotification->nmhdr.hwndFrom = BufferID;

+ /// scnNotifiNATIVELANGCHANGEDcation->nmhdr.idFrom = 0; // preserved for the future use, must be zero + ///
+ NPPN_GLOBALMODIFIED = NPPN_FIRST + 30, + + + /// + /// To notify plugins that the current native language is just changed to another one.

+ /// Use NPPM_GETNATIVELANGFILENAME to get current native language file name.

+ /// Use NPPM_GETMENUHANDLE(NPPPLUGINMENU, 0) to get submenu "Plugins" handle (HMENU)

+ /// scnNotification->nmhdr.code = NPPN_NATIVELANGCHANGED;

+ /// scnNotification->nmhdr.hwndFrom = hwndNpp

+ /// scnNotification->nmhdr.idFrom = 0; // preserved for the future use, must be zero + ///
+ NPPN_NATIVELANGCHANGED = NPPN_FIRST + 31, + /* --Autogenerated -- end of section automatically generated from notepad-plus-plus\PowerEditor\src\MISC\PluginsManager\Notepad_plus_msgs.h * */ } + + public enum StatusBarSection + { + DocType, + DocSize, + CurPos, + EofFormat, + UnicodeType, + TypingMode, + } } diff --git a/nppRandomStringGenerator/PluginInfrastructure/NotepadPPGateway.cs b/nppRandomStringGenerator/PluginInfrastructure/NotepadPPGateway.cs index e1668df..58c7d4a 100644 --- a/nppRandomStringGenerator/PluginInfrastructure/NotepadPPGateway.cs +++ b/nppRandomStringGenerator/PluginInfrastructure/NotepadPPGateway.cs @@ -1,7 +1,11 @@ // NPP plugin platform for .Net v0.94.00 by Kasper B. Graversen etc. using System; +using System.Collections.Generic; +using System.Drawing; +using System.Runtime.InteropServices; using System.Text; using NppPluginNET.PluginInfrastructure; +using NppPluginNET.Utils; namespace Kbg.NppPluginNET.PluginInfrastructure { @@ -9,10 +13,61 @@ public interface INotepadPPGateway { void FileNew(); + void AddToolbarIcon(int funcItemsIndex, toolbarIcons icon); + void AddToolbarIcon(int funcItemsIndex, Bitmap icon); + string GetNppPath(); + string GetPluginConfigPath(); string GetCurrentFilePath(); - unsafe string GetFilePath(int bufferId); + unsafe string GetFilePath(IntPtr bufferId); void SetCurrentLanguage(LangType language); - } + bool OpenFile(string path); + bool SaveCurrentFile(); + void ShowDockingForm(System.Windows.Forms.Form form); + void HideDockingForm(System.Windows.Forms.Form form); + Color GetDefaultForegroundColor(); + Color GetDefaultBackgroundColor(); + string GetConfigDirectory(); + int[] GetNppVersion(); + string[] GetOpenFileNames(); + void SetStatusBarSection(string message, StatusBarSection section); + /// + /// Register a modeless form (i.e., a form that doesn't block the parent application until closed)

+ /// with Notepad++ using NPPM_MODELESSDIALOG

+ /// If you don't do this, Notepad++ may intercept some keystrokes in unintended ways. + ///
+ /// the Handle attribute of a Windows form + void AddModelessDialog(IntPtr formHandle); + /// + /// unregister a modelesss form that was registered with AddModelessDialog.

+ /// This MUST be called in the Dispose method of the form, BEFORE the components of the form are disposed. + ///
+ /// the Handle attribute of a Windows form + void RemoveModelessDialog(IntPtr formHandle); + /// + /// Introduced in Notepad++ 8.5.6.

+ /// NPPM_ALLOCATEINDICATOR: allocate one or more unused indicator IDs, + /// which can then be assigned styles and used to style regions of text.

+ /// returns false and sets indicators to null if numberOfIndicators is less than 1, or if the requested number of indicators could not be allocated.

+ /// Otherwise, returns true, and sets indicators to an array of numberOfIndicators indicator IDs.

+ /// See https://www.scintilla.org/ScintillaDoc.html#Indicators for more info on the indicator API. + ///
+ /// number of consecutive indicator IDs to allocate + /// + bool AllocateIndicators(int numberOfIndicators, out int[] indicators); + + /// + /// get the English name of the Notepad++ UI language.

+ /// a return value of false indicates that something went wrong and fname should not be used + ///
+ bool TryGetNativeLangName(out string langName); + + /// + /// returns [0] if only the mainView is open, [0, 1] if both the mainView and subView are open, and [1] if only the subView is open.

+ /// Thus, GetVisibleViews().Count will return 2 if the Notepad++ window is split into two views, and 1 otherwise. + ///
+ List GetVisibleViews(); + + } ///
public unsafe string StyleGetFont(int style) { - byte[] fontNameBuffer = new byte[10000]; - fixed (byte* fontNamePtr = fontNameBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_STYLEGETFONT, (IntPtr) style, (IntPtr) fontNamePtr); - return Encoding.UTF8.GetString(fontNameBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_STYLEGETFONT, (IntPtr)style); } /// Get is a style to have its end of line filled or not. (Scintilla feature 2487) @@ -911,12 +952,7 @@ public unsafe void SetWordChars(string characters) /// public unsafe string GetWordChars() { - byte[] charactersBuffer = new byte[10000]; - fixed (byte* charactersPtr = charactersBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_GETWORDCHARS, (IntPtr) Unused, (IntPtr) charactersPtr); - return Encoding.UTF8.GetString(charactersBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_GETWORDCHARS); } /// Set the number of characters to have directly indexed categories (Scintilla feature 2720) @@ -1551,12 +1587,7 @@ public int GetFirstVisibleLine() /// public unsafe string GetLine(int line) { - byte[] textBuffer = new byte[10000]; - fixed (byte* textPtr = textBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_GETLINE, (IntPtr) line, (IntPtr) textPtr); - return Encoding.UTF8.GetString(textBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_GETLINE, (IntPtr)line); } /// Returns the number of lines in the document. There is always at least one. (Scintilla feature 2154) @@ -1609,12 +1640,7 @@ public void SetSel(int anchor, int caret) /// public unsafe string GetSelText() { - byte[] textBuffer = new byte[10000]; - fixed (byte* textPtr = textBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_GETSELTEXT, (IntPtr) Unused, (IntPtr) textPtr); - return Encoding.UTF8.GetString(textBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_GETSELTEXT); } /// @@ -1758,19 +1784,15 @@ public unsafe void SetText(string text) } } - /// - /// Retrieve all the text in the document. - /// Returns number of characters retrieved. - /// Result is NUL-terminated. - /// (Scintilla feature 2182) - /// - public unsafe string GetText(int length) + public unsafe string GetText(int length = -1) { - byte[] textBuffer = new byte[10000]; + if (length < 1 && !TryGetLengthAsInt(out length)) + return ""; + byte[] textBuffer = new byte[length]; fixed (byte* textPtr = textBuffer) { - Win32.SendMessage(scintilla, SciMsg.SCI_GETTEXT, (IntPtr) length, (IntPtr) textPtr); - return Encoding.UTF8.GetString(textBuffer).TrimEnd('\0'); + Win32.SendMessage(scintilla, SciMsg.SCI_GETTEXT, length, (IntPtr)textPtr); + return Utf8BytesToNullStrippedString(textBuffer); } } @@ -1861,12 +1883,7 @@ public void SetTargetRange(int start, int end) /// Retrieve the text in the target. (Scintilla feature 2687) public unsafe string GetTargetText() { - byte[] textBuffer = new byte[10000]; - fixed (byte* textPtr = textBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_GETTARGETTEXT, (IntPtr) Unused, (IntPtr) textPtr); - return Encoding.UTF8.GetString(textBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_GETTARGETTEXT); } /// Make the target range start and end be the same as the selection range start and end. (Scintilla feature 2287) @@ -2129,12 +2146,7 @@ public unsafe void SetDefaultFoldDisplayText(string text) /// Get the default fold display text. (Scintilla feature 2723) public unsafe string GetDefaultFoldDisplayText() { - byte[] textBuffer = new byte[10000]; - fixed (byte* textPtr = textBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_GETDEFAULTFOLDDISPLAYTEXT, (IntPtr) Unused, (IntPtr) textPtr); - return Encoding.UTF8.GetString(textBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_GETDEFAULTFOLDDISPLAYTEXT); } /// Expand or contract a fold header. (Scintilla feature 2237) @@ -2474,12 +2486,11 @@ public MultiPaste GetMultiPaste() /// public unsafe string GetTag(int tagNumber) { - byte[] tagValueBuffer = new byte[10000]; - fixed (byte* tagValuePtr = tagValueBuffer) + if (tagNumber < 0) { - Win32.SendMessage(scintilla, SciMsg.SCI_GETTAG, (IntPtr) tagNumber, (IntPtr) tagValuePtr); - return Encoding.UTF8.GetString(tagValueBuffer).TrimEnd('\0'); + throw new ArgumentException("tagNumber must be non-negative integer"); } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_GETTAG, (IntPtr)tagNumber); } /// Join the lines in the target. (Scintilla feature 2288) @@ -3639,12 +3650,7 @@ public unsafe void SetWhitespaceChars(string characters) /// Get the set of characters making up whitespace for when moving or selecting by word. (Scintilla feature 2647) public unsafe string GetWhitespaceChars() { - byte[] charactersBuffer = new byte[10000]; - fixed (byte* charactersPtr = charactersBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_GETWHITESPACECHARS, (IntPtr) Unused, (IntPtr) charactersPtr); - return Encoding.UTF8.GetString(charactersBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_GETWHITESPACECHARS); } /// @@ -3663,12 +3669,7 @@ public unsafe void SetPunctuationChars(string characters) /// Get the set of characters making up punctuation characters (Scintilla feature 2649) public unsafe string GetPunctuationChars() { - byte[] charactersBuffer = new byte[10000]; - fixed (byte* charactersPtr = charactersBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_GETPUNCTUATIONCHARS, (IntPtr) Unused, (IntPtr) charactersPtr); - return Encoding.UTF8.GetString(charactersBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_GETPUNCTUATIONCHARS); } /// Reset the set of characters for whitespace and word characters to the defaults. (Scintilla feature 2444) @@ -3691,12 +3692,7 @@ public int AutoCGetCurrent() /// public unsafe string AutoCGetCurrentText() { - byte[] textBuffer = new byte[10000]; - fixed (byte* textPtr = textBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_AUTOCGETCURRENTTEXT, (IntPtr) Unused, (IntPtr) textPtr); - return Encoding.UTF8.GetString(textBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_AUTOCGETCURRENTTEXT); } /// Set auto-completion case insensitive behaviour to either prefer case-sensitive matches or have no preference. (Scintilla feature 2634) @@ -3748,12 +3744,7 @@ public void Allocate(int bytes) /// public unsafe string TargetAsUTF8() { - byte[] sBuffer = new byte[10000]; - fixed (byte* sPtr = sBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_TARGETASUTF8, (IntPtr) Unused, (IntPtr) sPtr); - return Encoding.UTF8.GetString(sBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_TARGETASUTF8); } /// @@ -3776,12 +3767,7 @@ public unsafe string EncodedFromUTF8(string utf8) { fixed (byte* utf8Ptr = Encoding.UTF8.GetBytes(utf8)) { - byte[] encodedBuffer = new byte[10000]; - fixed (byte* encodedPtr = encodedBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_ENCODEDFROMUTF8, (IntPtr) utf8Ptr, (IntPtr) encodedPtr); - return Encoding.UTF8.GetString(encodedBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_ENCODEDFROMUTF8, (IntPtr)utf8Ptr); } } @@ -4030,12 +4016,7 @@ public unsafe void MarginSetText(int line, string text) /// Get the text in the text margin for a line (Scintilla feature 2531) public unsafe string MarginGetText(int line) { - byte[] textBuffer = new byte[10000]; - fixed (byte* textPtr = textBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_MARGINGETTEXT, (IntPtr) line, (IntPtr) textPtr); - return Encoding.UTF8.GetString(textBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_MARGINGETTEXT, (IntPtr)line); } /// Set the style number for the text margin for a line (Scintilla feature 2532) @@ -4062,12 +4043,7 @@ public unsafe void MarginSetStyles(int line, string styles) /// Get the styles in the text margin for a line (Scintilla feature 2535) public unsafe string MarginGetStyles(int line) { - byte[] stylesBuffer = new byte[10000]; - fixed (byte* stylesPtr = stylesBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_MARGINGETSTYLES, (IntPtr) line, (IntPtr) stylesPtr); - return Encoding.UTF8.GetString(stylesBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_MARGINGETSTYLES, (IntPtr)line); } /// Clear the margin text on all lines (Scintilla feature 2536) @@ -4112,12 +4088,7 @@ public unsafe void AnnotationSetText(int line, string text) /// Get the annotation text for a line (Scintilla feature 2541) public unsafe string AnnotationGetText(int line) { - byte[] textBuffer = new byte[10000]; - fixed (byte* textPtr = textBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_ANNOTATIONGETTEXT, (IntPtr) line, (IntPtr) textPtr); - return Encoding.UTF8.GetString(textBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_ANNOTATIONGETTEXT, (IntPtr)line); } /// Set the style number for the annotations for a line (Scintilla feature 2542) @@ -4144,12 +4115,7 @@ public unsafe void AnnotationSetStyles(int line, string styles) /// Get the annotation styles for a line (Scintilla feature 2545) public unsafe string AnnotationGetStyles(int line) { - byte[] stylesBuffer = new byte[10000]; - fixed (byte* stylesPtr = stylesBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_ANNOTATIONGETSTYLES, (IntPtr) line, (IntPtr) stylesPtr); - return Encoding.UTF8.GetString(stylesBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_ANNOTATIONGETSTYLES, (IntPtr)line); } /// Get the number of annotation lines for a line (Scintilla feature 2546) @@ -4765,12 +4731,7 @@ public unsafe string GetRepresentation(string encodedCharacter) { fixed (byte* encodedCharacterPtr = Encoding.UTF8.GetBytes(encodedCharacter)) { - byte[] representationBuffer = new byte[10000]; - fixed (byte* representationPtr = representationBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_GETREPRESENTATION, (IntPtr) encodedCharacterPtr, (IntPtr) representationPtr); - return Encoding.UTF8.GetString(representationBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_GETREPRESENTATION, (IntPtr)encodedCharacterPtr); } } @@ -4861,12 +4822,7 @@ public unsafe string GetProperty(string key) { fixed (byte* keyPtr = Encoding.UTF8.GetBytes(key)) { - byte[] valueBuffer = new byte[10000]; - fixed (byte* valuePtr = valueBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_GETPROPERTY, (IntPtr) keyPtr, (IntPtr) valuePtr); - return Encoding.UTF8.GetString(valueBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_GETPROPERTY, (IntPtr)keyPtr); } } @@ -4880,12 +4836,7 @@ public unsafe string GetPropertyExpanded(string key) { fixed (byte* keyPtr = Encoding.UTF8.GetBytes(key)) { - byte[] valueBuffer = new byte[10000]; - fixed (byte* valuePtr = valueBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_GETPROPERTYEXPANDED, (IntPtr) keyPtr, (IntPtr) valuePtr); - return Encoding.UTF8.GetString(valueBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_GETPROPERTYEXPANDED, (IntPtr)keyPtr); } } @@ -4910,12 +4861,7 @@ public unsafe int GetPropertyInt(string key, int defaultValue) /// public unsafe string GetLexerLanguage() { - byte[] languageBuffer = new byte[10000]; - fixed (byte* languagePtr = languageBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_GETLEXERLANGUAGE, (IntPtr) Unused, (IntPtr) languagePtr); - return Encoding.UTF8.GetString(languageBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_GETLEXERLANGUAGE); } /// For private communication between an application and a known lexer. (Scintilla feature 4013) @@ -4931,12 +4877,7 @@ public IntPtr PrivateLexerCall(int operation, IntPtr pointer) /// public unsafe string PropertyNames() { - byte[] namesBuffer = new byte[10000]; - fixed (byte* namesPtr = namesBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_PROPERTYNAMES, (IntPtr) Unused, (IntPtr) namesPtr); - return Encoding.UTF8.GetString(namesBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_PROPERTYNAMES); } /// Retrieve the type of a property. (Scintilla feature 4015) @@ -4957,12 +4898,7 @@ public unsafe string DescribeProperty(string name) { fixed (byte* namePtr = Encoding.UTF8.GetBytes(name)) { - byte[] descriptionBuffer = new byte[10000]; - fixed (byte* descriptionPtr = descriptionBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_DESCRIBEPROPERTY, (IntPtr) namePtr, (IntPtr) descriptionPtr); - return Encoding.UTF8.GetString(descriptionBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_DESCRIBEPROPERTY, (IntPtr)namePtr); } } @@ -4973,12 +4909,7 @@ public unsafe string DescribeProperty(string name) /// public unsafe string DescribeKeyWordSets() { - byte[] descriptionsBuffer = new byte[10000]; - fixed (byte* descriptionsPtr = descriptionsBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_DESCRIBEKEYWORDSETS, (IntPtr) Unused, (IntPtr) descriptionsPtr); - return Encoding.UTF8.GetString(descriptionsBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_DESCRIBEKEYWORDSETS); } /// @@ -5053,12 +4984,7 @@ public int DistanceToSecondaryStyles() /// public unsafe string GetSubStyleBases() { - byte[] stylesBuffer = new byte[10000]; - fixed (byte* stylesPtr = stylesBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_GETSUBSTYLEBASES, (IntPtr) Unused, (IntPtr) stylesPtr); - return Encoding.UTF8.GetString(stylesBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_GETSUBSTYLEBASES); } /// Retrieve the number of named styles for the lexer. (Scintilla feature 4029) @@ -5074,12 +5000,7 @@ public int GetNamedStyles() /// public unsafe string NameOfStyle(int style) { - byte[] nameBuffer = new byte[10000]; - fixed (byte* namePtr = nameBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_NAMEOFSTYLE, (IntPtr) style, (IntPtr) namePtr); - return Encoding.UTF8.GetString(nameBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_NAMEOFSTYLE, (IntPtr)style); } /// @@ -5089,12 +5010,7 @@ public unsafe string NameOfStyle(int style) /// public unsafe string TagsOfStyle(int style) { - byte[] tagsBuffer = new byte[10000]; - fixed (byte* tagsPtr = tagsBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_TAGSOFSTYLE, (IntPtr) style, (IntPtr) tagsPtr); - return Encoding.UTF8.GetString(tagsBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_TAGSOFSTYLE, (IntPtr)style); } /// @@ -5104,12 +5020,7 @@ public unsafe string TagsOfStyle(int style) /// public unsafe string DescriptionOfStyle(int style) { - byte[] descriptionBuffer = new byte[10000]; - fixed (byte* descriptionPtr = descriptionBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_DESCRIPTIONOFSTYLE, (IntPtr) style, (IntPtr) descriptionPtr); - return Encoding.UTF8.GetString(descriptionBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_DESCRIPTIONOFSTYLE, (IntPtr)style); } /// Retrieve bidirectional text display state. (Scintilla feature 2708) diff --git a/nppRandomStringGenerator/PluginInfrastructure/Scintilla_iface.cs b/nppRandomStringGenerator/PluginInfrastructure/Scintilla_iface.cs index 945c0cf..db0d30e 100644 --- a/nppRandomStringGenerator/PluginInfrastructure/Scintilla_iface.cs +++ b/nppRandomStringGenerator/PluginInfrastructure/Scintilla_iface.cs @@ -38,13 +38,13 @@ public struct ScNotificationHeader public struct ScNotification { public ScNotificationHeader Header; - private int position; /* SCN_STYLENEEDED, SCN_DOUBLECLICK, SCN_MODIFIED, SCN_MARGINCLICK, SCN_NEEDSHOWN, SCN_DWELLSTART, SCN_DWELLEND, SCN_CALLTIPCLICK, SCN_HOTSPOTCLICK, SCN_HOTSPOTDOUBLECLICK, SCN_HOTSPOTRELEASECLICK, SCN_INDICATORCLICK, SCN_INDICATORRELEASE, SCN_USERLISTSELECTION, SCN_AUTOCSELECTION */ + private IntPtr position; /* SCN_STYLENEEDED, SCN_DOUBLECLICK, SCN_MODIFIED, SCN_MARGINCLICK, SCN_NEEDSHOWN, SCN_DWELLSTART, SCN_DWELLEND, SCN_CALLTIPCLICK, SCN_HOTSPOTCLICK, SCN_HOTSPOTDOUBLECLICK, SCN_HOTSPOTRELEASECLICK, SCN_INDICATORCLICK, SCN_INDICATORRELEASE, SCN_USERLISTSELECTION, SCN_AUTOCSELECTION */ public int character; /* SCN_CHARADDED, SCN_KEY, SCN_AUTOCCOMPLETE, SCN_AUTOCSELECTION, SCN_USERLISTSELECTION */ public int Mmodifiers; /* SCN_KEY, SCN_DOUBLECLICK, SCN_HOTSPOTCLICK, SCN_HOTSPOTDOUBLECLICK, SCN_HOTSPOTRELEASECLICK, SCN_INDICATORCLICK, SCN_INDICATORRELEASE */ public int ModificationType; /* SCN_MODIFIED - modification types are name "SC_MOD_*" */ public IntPtr TextPointer; /* SCN_MODIFIED, SCN_USERLISTSELECTION, SCN_AUTOCSELECTION, SCN_URIDROPPED */ - public int Length; /* SCN_MODIFIED */ - public int LinesAdded; /* SCN_MODIFIED */ + public IntPtr Length; /* SCN_MODIFIED */ + public IntPtr LinesAdded; /* SCN_MODIFIED */ public int Message; /* SCN_MACRORECORD */ public IntPtr wParam; /* SCN_MACRORECORD */ public IntPtr lParam; /* SCN_MACRORECORD */ @@ -52,7 +52,7 @@ public struct ScNotification /// /// 0-based index /// - public int LineNumber; /* SCN_MODIFIED */ + public IntPtr LineNumber; /* SCN_MODIFIED */ public int FoldLevelNow; /* SCN_MODIFIED */ public int FoldLevelPrev; /* SCN_MODIFIED */ public int Margin; /* SCN_MARGINCLICK */ @@ -60,9 +60,10 @@ public struct ScNotification public int X; /* SCN_DWELLSTART, SCN_DWELLEND */ public int Y; /* SCN_DWELLSTART, SCN_DWELLEND */ public int Token; /* SCN_MODIFIED with SC_MOD_CONTAINER */ - public int AnnotationLinesAdded; /* SC_MOD_CHANGEANNOTATION */ + public IntPtr AnnotationLinesAdded; /* SC_MOD_CHANGEANNOTATION */ public int Updated; /* SCN_UPDATEUI */ public int ListCompletionMethod; /* SCN_AUTOCSELECTION, SCN_AUTOCCOMPLETED, SCN_USERLISTSELECTION */ + public int CharacterSource; /* SCN_CHARADDED */ /// /// SCN_STYLENEEDED, SCN_DOUBLECLICK, SCN_MODIFIED, SCN_MARGINCLICK, SCN_NEEDSHOWN, SCN_DWELLSTART, SCN_DWELLEND, SCN_CALLTIPCLICK, SCN_HOTSPOTCLICK, SCN_HOTSPOTDOUBLECLICK, SCN_HOTSPOTRELEASECLICK, SCN_INDICATORCLICK, SCN_INDICATORRELEASE, SCN_USERLISTSELECTION, SCN_AUTOCSELECTION @@ -73,7 +74,7 @@ public struct ScNotification /// Character of the notification - eg keydown /// SCN_CHARADDED, SCN_KEY, SCN_AUTOCCOMPLETE, SCN_AUTOCSELECTION, SCN_USERLISTSELECTION /// - public char Character { get { return (char) character; } } + public char Character { get { return (char)character; } } } [Flags] @@ -3232,8 +3233,8 @@ public TextToFind(CharacterRange chrRange, string searchText) /// the search pattern public TextToFind(int cpmin, int cpmax, string searchText) { - _sciTextToFind.chrg.cpMin = cpmin; - _sciTextToFind.chrg.cpMax = cpmax; + _sciTextToFind.chrg.cpMin = new IntPtr(cpmin); + _sciTextToFind.chrg.cpMax = new IntPtr(cpmax); _sciTextToFind.lpstrText = Marshal.StringToHGlobalAnsi(searchText); } diff --git a/nppRandomStringGenerator/PluginInfrastructure/UnmanagedExports.cs b/nppRandomStringGenerator/PluginInfrastructure/UnmanagedExports.cs index 1a7bac6..50744ff 100644 --- a/nppRandomStringGenerator/PluginInfrastructure/UnmanagedExports.cs +++ b/nppRandomStringGenerator/PluginInfrastructure/UnmanagedExports.cs @@ -2,7 +2,7 @@ using System; using System.Runtime.InteropServices; using Kbg.NppPluginNET.PluginInfrastructure; -using NppPlugin.DllExport; +using RGiesecke.DllExport; namespace Kbg.NppPluginNET { @@ -50,7 +50,7 @@ static void beNotified(IntPtr notifyCode) if (notification.Header.Code == (uint)NppMsg.NPPN_TBMODIFICATION) { PluginBase._funcItems.RefreshItems(); - Main.SetToolBarIcon(); + Main.SetToolBarIcons(); } else if (notification.Header.Code == (uint)NppMsg.NPPN_SHUTDOWN) { diff --git a/nppRandomStringGenerator/PluginInfrastructure/Win32.cs b/nppRandomStringGenerator/PluginInfrastructure/Win32.cs index d1a8c74..581a050 100644 --- a/nppRandomStringGenerator/PluginInfrastructure/Win32.cs +++ b/nppRandomStringGenerator/PluginInfrastructure/Win32.cs @@ -48,6 +48,73 @@ public struct ScrollInfo public int nTrackPos; } + /// + /// @see https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-nmhdr + /// + [StructLayout(LayoutKind.Sequential)] + public struct TagNMHDR + { + public IntPtr hwndFrom; + public UIntPtr idFrom; + public uint code; + } + + /// + /// see https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-menuiteminfow + /// + [StructLayout(LayoutKind.Sequential)] + public struct MenuItemInfo + { + /// + /// this is always equal to the size of this struct + /// + public uint cbSize { get; private set; } + public uint fMask; + public uint fType; + public uint fState; + public uint wID; + public IntPtr hSubMenu; + public IntPtr hbmpChecked; + public IntPtr hbmpUnchecked; + public UIntPtr dwItemData; + public IntPtr dwTypeData; + public uint cch; + public IntPtr hbmpItem; + + public MenuItemInfo(MenuItemMask fMask) + { + cbSize = 0; + this.fMask = (uint)fMask; + fType = 0; + fState = 0; + wID = 0; + hSubMenu = IntPtr.Zero; + hbmpChecked = IntPtr.Zero; + hbmpUnchecked = IntPtr.Zero; + dwItemData = UIntPtr.Zero; + dwTypeData = IntPtr.Zero; + cch = 0; + hbmpItem = IntPtr.Zero; + cbSize = (uint)Marshal.SizeOf(this); + } + } + + /// + /// See the description of the fMask MenuItemInfo attribute in https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-menuiteminfow + /// + public enum MenuItemMask : uint + { + MIIM_BITMAP = 0X80, + MIIM_CHECKMARKS = 8, + MIIM_DATA = 0X20, + MIIM_FTYPE = 0X100, + MIIM_ID = 2, + MIIM_STATE = 1, + MIIM_STRING = 0X40, + MIIM_SUBMENU = 4, + // MIIM_TYPE = 0X10, // deprecated + } + /// /// Used for the ScrollInfo fMask /// SIF_ALL => Combination of SIF_PAGE, SIF_POS, SIF_RANGE, and SIF_TRACKPOS. @@ -266,7 +333,7 @@ public static IntPtr SendMessage(IntPtr hWnd, SciMsg Msg, IntPtr wParam, IntPtr /// If gateways are missing or incomplete, please help extend them and send your code to the project /// at https://github.com/kbilsted/NotepadPlusPlusPluginPack.Net /// - public static IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam, ref LangType lParam) + public static IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam, ref LangType lParam) { IntPtr outVal; IntPtr retval = SendMessage(hWnd, (UInt32)Msg, new IntPtr(wParam), out outVal); @@ -279,20 +346,94 @@ public static IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam, ref LangType [DllImport("kernel32")] public static extern int GetPrivateProfileInt(string lpAppName, string lpKeyName, int nDefault, string lpFileName); + [DllImport("kernel32")] + public static extern int GetPrivateProfileString(string lpAppName, string lpKeyName, string lpDefault, StringBuilder lpReturnedString, int nSize, string lpFileName); + [DllImport("kernel32")] public static extern bool WritePrivateProfileString(string lpAppName, string lpKeyName, string lpString, string lpFileName); + [DllImport("kernel32.dll")] + public static extern int GetPrivateProfileSection(string lpAppName, byte[] lpszReturnBuffer, int nSize, string lpFileName); + public const int MF_BYCOMMAND = 0; public const int MF_CHECKED = 8; public const int MF_UNCHECKED = 0; + public const int MF_ENABLED = 0; + public const int MF_GRAYED = 1; + public const int MF_DISABLED = 2; [DllImport("user32")] public static extern IntPtr GetMenu(IntPtr hWnd); [DllImport("user32")] - public static extern int CheckMenuItem(IntPtr hmenu, int uIDCheckItem, int uCheck); + public static extern IntPtr GetSubMenu(IntPtr hMenu, int index); + + [DllImport("user32")] + public static extern bool SetMenuItemInfo(IntPtr hMenu, uint item, bool fByPosition, IntPtr ptrMenuItemInfo); + + [DllImport("user32")] + public static extern bool GetMenuItemInfo(IntPtr hMenu, uint item, bool fByPosition, IntPtr ptrMenuItemInfo); + + [DllImport("user32")] + public static extern int GetMenuItemCount(IntPtr hMenu); + + [DllImport("user32")] + public static extern int CheckMenuItem(IntPtr hMenu, int uIDCheckItem, int uCheck); + + [DllImport("user32")] + public static extern bool EnableMenuItem(IntPtr hMenu, int uIDEnableItem, int uEnable); public const int WM_CREATE = 1; + public const int WM_NOTIFY = 0x004e; + public const int GWL_EXSTYLE = -20; + public const int GWLP_HINSTANCE = -6; + public const int GWLP_HWNDPARENT = -8; + public const int GWLP_ID = -12; + public const int GWL_STYLE = -16; + public const int GWLP_USERDATA = -21; + public const int GWLP_WNDPROC = -4; + public const long WS_EX_ACCEPTFILES = 0x00000010L; + public const long WS_EX_APPWINDOW = 0x00040000L; + public const long WS_EX_CLIENTEDGE = 0x00000200L; + public const long WS_EX_COMPOSITED = 0x02000000L; + public const long WS_EX_CONTEXTHELP = 0x00000400L; + public const long WS_EX_CONTROLPARENT = 0x00010000L; + public const long WS_EX_DLGMODALFRAME = 0x00000001L; + public const long WS_EX_LAYERED = 0x00080000L; + public const long WS_EX_LAYOUTRTL = 0x00400000L; + public const long WS_EX_LEFT = 0x00000000L; + public const long WS_EX_LEFTSCROLLBAR = 0x00004000L; + public const long WS_EX_LTRREADING = 0x00000000L; + public const long WS_EX_MDICHILD = 0x00000040L; + public const long WS_EX_NOACTIVATE = 0x08000000L; + public const long WS_EX_NOINHERITLAYOUT = 0x00100000L; + public const long WS_EX_NOPARENTNOTIFY = 0x00000004L; + public const long WS_EX_NOREDIRECTIONBITMAP = 0x00200000L; + public const long WS_EX_OVERLAPPEDWINDOW = (WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE); + public const long WS_EX_PALETTEWINDOW = (WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST); + public const long WS_EX_RIGHT = 0x00001000L; + public const long WS_EX_RIGHTSCROLLBAR = 0x00000000L; + public const long WS_EX_RTLREADING = 0x00002000L; + public const long WS_EX_STATICEDGE = 0x00020000L; + public const long WS_EX_TOOLWINDOW = 0x00000080L; + public const long WS_EX_TOPMOST = 0x00000008L; + public const long WS_EX_TRANSPARENT = 0x00000020L; + public const long WS_EX_WINDOWEDGE = 0x00000100L; + + public delegate IntPtr WindowLongGetter(IntPtr hWnd, int nIndex); + public delegate IntPtr WindowLongSetter(IntPtr hWnd, int nIndex, IntPtr dwNewLong); + + [DllImport("user32")] + public static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex); + + [DllImport("user32")] + public static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex); + + [DllImport("user32")] + public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong); + + [DllImport("user32")] + public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong); [DllImport("user32")] public static extern bool ClientToScreen(IntPtr hWnd, ref Point lpPoint); diff --git a/nppRandomStringGenerator/Properties/AssemblyInfo.cs b/nppRandomStringGenerator/Properties/AssemblyInfo.cs index 873ea09..1d744fc 100644 --- a/nppRandomStringGenerator/Properties/AssemblyInfo.cs +++ b/nppRandomStringGenerator/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("CMBSolutions")] [assembly: AssemblyProduct("nppRandomStringGenerator")] -[assembly: AssemblyCopyright("Copyright © CMBSolutions 2021 - 2024")] +[assembly: AssemblyCopyright("Copyright © CMBSolutions 2021 - 2025")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.9.3")] -[assembly: AssemblyFileVersion("1.9.3")] +[assembly: AssemblyVersion("1.9.4")] +[assembly: AssemblyFileVersion("1.9.4")] diff --git a/nppRandomStringGenerator/Utils/ArrayExtensions.cs b/nppRandomStringGenerator/Utils/ArrayExtensions.cs new file mode 100644 index 0000000..47072e9 --- /dev/null +++ b/nppRandomStringGenerator/Utils/ArrayExtensions.cs @@ -0,0 +1,399 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NppPluginNET.Utils +{ + public static class ArrayExtensions + { + /// + /// Allows the use of Python-style slices, where start, stop, and stride must be declared as individual paramters.

+ /// Thus e.g. arr.Slice(2, null, -1) is just like arr[slice(2, None, -1)] in Python.

+ /// This just yields the elements one by one.

+ /// If you want a function that yields a shallow copy of the sliced region of the iterable, use Slice<T> instead. + ///
+ /// + /// + /// + /// + /// + /// + public static IEnumerable LazySlice(this IList source, int? start, int? stop = null, int? stride = null) + { + int len = source.Count; + if (len <= 0) + { + yield break; + } + int istop, istart, istride; + if (stride is int istride_) + { + istride = istride_; + if (istride_ < 0) + { + if (start is int istart_) + { + if (istart_ < -len) + yield break; + istart = ClampWithinLen(len, istart_, true); + } + else + istart = len - 1; + + if (stop is int istop_) + istop = ClampWithinLen(len, istop_, false); + else + istop = -1; + } + else if (istride_ == 0) + { + throw new ArgumentException("The stride parameter of a slice must be a non-zero integer, or must be left empty"); + } + else // positive stride + { + if (start is int istart_) + { + if (istart_ >= len) + yield break; + istart = ClampWithinLen(len, istart_, true); + } + else + istart = 0; + + if (stop is int istop_) + istop = ClampWithinLen(len, istop_, false); + else + istop = len; + } + } + else // stride unspecified, assumed to be 1 + { + istride = 1; + + if (start is int istart_) + istart = ClampWithinLen(len, istart_, true); + else + istart = 0; + + if (stop is int istop_) + istop = ClampWithinLen(len, istop_, false); + else + istop = len; + } + + if (istride < 0) + { + for (int ii = istart; ii > istop; ii += istride) + { + yield return source[ii]; + } + } + else + { + for (int ii = istart; ii < istop; ii += istride) + { + yield return source[ii]; + } + } + } + + /// + /// If num is negative, use Python-style negative indices (e.g., -1 is the last element, -len is the first elememnt) + /// Otherwise, restrict num to between 0 and len (inclusive unless is_start_idx) + /// + /// + /// + /// + public static int ClampWithinLen(int len, int num, bool is_start_idx) + { + if (num >= len) + return is_start_idx ? len - 1: len; + if (num < 0) + { + num += len; + if (num < 0) + return is_start_idx ? 0 : -1; + return num; + } + return num; + } + + /// + /// Allows the use of Python-style slices, passed as strings (e.g., ":", "1::-2").

+ /// Because LazySlice is an extension method, all arrays in this namespace can use this method.

+ /// See https://stackoverflow.com/questions/509211/understanding-slicing + /// This just yields the elements one by one.

+ /// If you want a function that yields a shallow copy of the sliced region of the iterable, use Slice<T> instead. + ///
+ public static IEnumerable LazySlice(this IList source, string slicer) + // note the "this" before the type and type. + // that designates Slice as a new method that can be used by T[] objects + // (i.e., arrays of any object type) using the "." syntax. + // That's why later on we see a.Slice(":" + a.Length) even though a doesn't + // come with the Slice method built in. + { + string[] parts = slicer.Split(':'); + int? start = 0, stop = 0, stride = 1; + switch (parts.Length) + { + case 1: start = 0; stop = int.Parse(parts[0]); break; + case 2: + if (parts[0] == "") start = null; else start = int.Parse(parts[0]); + if (parts[1] == "") stop = null; else stop = int.Parse(parts[1]); + break; + case 3: + if (parts[0] == "") start = null; else start = int.Parse(parts[0]); + if (parts[1] == "") stop = null; else stop = int.Parse(parts[1]); + if (parts[2] == "") stride = null; else stride = int.Parse(parts[2]); + break; + } + foreach (T item in source.LazySlice(start, stop, stride)) { yield return item; } + } + + /// + /// This just yields the elements one by one.

+ /// If you want a function that yields a shallow copy of the sliced region of the iterable, use Slice<T> instead. + ///
+ /// + /// + /// + /// + public static IEnumerable LazySlice(this IList source, int?[] slicer) + { + int? start = 0, stop = 0, stride = 1; + switch (slicer.Length) + { + case 1: start = 0; stop = slicer[0]; break; + case 2: + start = slicer[0]; + stop = slicer[1]; + break; + case 3: + start = slicer[0]; + stop = slicer[1]; + stride = slicer[2]; + break; + } + foreach (T item in source.LazySlice(start, stop, stride)) { yield return item; } + } + + /// + /// Allows use of Python-style slices, except that these create a copy of the sliced object rather than a view.

+ /// For higher performance at the cost of only producing an iterator and not a new iterable, use LazySlice.

+ /// See the documentation for LazySlice with three int arguments. + ///
+ /// + /// + /// + /// + /// + /// + /// + public static IList Slice(this IList source, int? start, int? stop = null, int? stride = null) + { + if (source is T[]) + return source.LazySlice(start, stop, stride).ToArray(); + if (source is List) + return source.LazySlice(start, stop, stride).ToList(); + throw new NotImplementedException("Slice extension is only implemented for strings, Lists, and arrays"); + } + + /// + /// Allows use of Python-style slices, except that these create a copy of the sliced object rather than a view.

+ /// For higher performance at the cost of only producing an iterator and not a new iterable, use LazySlice.

+ /// See the documentation for LazySlice with a string argument + ///
+ /// + /// + /// + /// + /// + /// + /// + public static IList Slice(this IList source, string slicer) + { + string[] parts = slicer.Split(':'); + int? start = 0, stop = 0, stride = 1; + switch (parts.Length) + { + case 1: start = 0; stop = int.Parse(parts[0]); break; + case 2: + if (parts[0] == "") start = null; else start = int.Parse(parts[0]); + if (parts[1] == "") stop = null; else stop = int.Parse(parts[1]); + break; + case 3: + if (parts[0] == "") start = null; else start = int.Parse(parts[0]); + if (parts[1] == "") stop = null; else stop = int.Parse(parts[1]); + if (parts[2] == "") stride = null; else stride = int.Parse(parts[2]); + break; + } + return source.Slice(start, stop, stride); + } + + /// + /// Allows use of Python-style slices, except that these create a copy of the sliced object rather than a view.

+ /// For higher performance at the cost of only producing an iterator and not a new iterable, use LazySlice.

+ /// See the documentation for LazySlice with three int arguments. + ///
+ /// + /// + /// + /// + /// + /// + /// + public static IList Slice(this IList source, int?[] slicer) + { + int? start = 0, stop = 0, stride = 1; + switch (slicer.Length) + { + case 1: start = 0; stop = slicer[0]; break; + case 2: + start = slicer[0]; + stop = slicer[1]; + break; + case 3: + start = slicer[0]; + stop = slicer[1]; + stride = slicer[2]; + break; + } + return source.Slice(start, stop, stride); + } + + public static string Slice(this string source, string slicer) + { + return new string(source.ToCharArray().LazySlice(slicer).ToArray()); + } + + public static string Slice(this string source, int start, int stop, int stride) + { + return new string(source.ToCharArray().LazySlice(start, stop, stride).ToArray()); + } + + /// + /// s_slice(x: string, sli: integer | slicer) -> string

+ /// uses Python slicing syntax.

+ /// EXAMPLES:

+ /// * s_slice(abcde, 1:-2) returns "bc"

+ /// * s_slice(abcde, :2) returns "ab"

+ /// * s_slice(abcde, -2) returns "d"

+ ///
+ public static string Slice(this string source, int?[] slicer) + { + return new string(source.ToCharArray().LazySlice(slicer).ToArray()); + } + + /// + /// randomize the order of the elements in arr + /// + public static void Shuffle(this IList arr) + { + for (int ii = 1; ii < arr.Count; ii++) + { + int swapWith = Npp.random.Next(0, ii + 1); + if (swapWith < ii) + { + T temp = arr[ii]; + arr[ii] = arr[swapWith]; + arr[swapWith] = temp; + } + } + } + + /// + /// Removes and returns the last element of a list. + /// + /// + /// + /// + public static T Pop(this List list) + { + int lastIdx = list.Count - 1; + T last = list[lastIdx]; + list.RemoveAt(lastIdx); + return last; + } + + /// + /// e.g., int[]{1,2,3,4,5} -> + /// "[1, 2, 3, 4, 5]" + /// + /// + /// + /// + public static string ArrayToString(this IList list) + { + var sb = new StringBuilder(); + sb.Append('['); + for (int ii = 0; ii < list.Count; ii++) + { + T x = list[ii]; + sb.Append(x.ToString()); + if (ii < list.Count - 1) + sb.Append(", "); + } + sb.Append(']'); + return sb.ToString(); + } + + /// + /// If idx is between [0, source length) exclusive, atIdx = source[idx] and return true.

+ /// If idx is between [-(source length), -1] inclusive, atIdx = source[idx + source length] and return true.

+ /// else atIdx = default(T) and return false + ///
+ /// + /// + /// + /// + /// + public static bool WrappedIndex(this IList source, int idx, out T atIdx) + { + int count = source.Count; + if (idx >= count || idx < -count) + { + atIdx = default(T); + return false; + } + idx = idx >= 0 ? idx : idx + count; + atIdx = source[idx]; + return true; + } + + /// + /// see WrappedIndex docs for lists and arrays.

+ /// Returns null if idx is out of bounds.

+ /// EXAMPLES

+ /// 1. WrappedIndex("abc", 1) -> "b"

+ /// 2. WrappedIndex("abc", -3) -> "a"

+ /// 3. WrappedIndex("abc", 4) -> null

+ /// 4. WrappedIndex("abc", -5) -> null

+ ///
+ /// + /// + /// + /// + public static bool WrappedIndex(this string source, int idx, out string atIdx) + { + int count = source.Length; + if (idx >= count || idx < -count) + { + atIdx = null; + return false; + } + idx = idx >= 0 ? idx : idx + count; + atIdx = source.Substring(idx, 1); + return true; + } + + /// + /// Return the first element of list if idx is not a valid index for this IList.

+ /// Otherwise return list[idx] + ///
+ public static T FirstIfOutOfBounds(this IList list, int idx) + { + return list[idx >= list.Count || idx < 0 ? 0 : idx]; + } + } +} diff --git a/nppRandomStringGenerator/Utils/FormStyle.cs b/nppRandomStringGenerator/Utils/FormStyle.cs new file mode 100644 index 0000000..0a2ce65 --- /dev/null +++ b/nppRandomStringGenerator/Utils/FormStyle.cs @@ -0,0 +1,116 @@ +using System.Drawing; +using System.Windows.Forms; + +namespace NppPluginNET.Utils +{ + public static class FormStyle + { + public static Color SlightlyDarkControl = Color.FromArgb( + 3 * SystemColors.Control.R / 4 + SystemColors.ControlDark.R / 4, + 3 * SystemColors.Control.G / 4 + SystemColors.ControlDark.G / 4, + 3 * SystemColors.Control.B / 4 + SystemColors.ControlDark.B / 4 + ); + + + /// + /// Changes the background and text color of the form + /// and any child forms to match the editor window.

+ /// Fires when the form is first opened + /// and also whenever the style is changed.

+ /// Heavily based on CsvQuery (https://github.com/jokedst/CsvQuery) + ///
+ public static void ApplyStyle(Control ctrl, bool useNppStyle, bool darkMode = false) + { + if (ctrl == null || ctrl.IsDisposed) return; + if (!Npp.nppVersionAtLeast8) + useNppStyle = false; // trying to follow editor style looks weird for Notepad++ 7.3.3 + if (ctrl is Form form) + { + foreach (Form childForm in form.OwnedForms) + { + ApplyStyle(childForm, useNppStyle, darkMode); + } + } + Color backColor = Npp.notepad.GetDefaultBackgroundColor(); + if (!useNppStyle || ( + backColor.R > 240 && + backColor.G > 240 && + backColor.B > 240)) + { + // if the background is basically white, + // use the system defaults because they + // look best on a white or nearly white background + ctrl.BackColor = SystemColors.Control; + ctrl.ForeColor = SystemColors.ControlText; + foreach (Control child in ctrl.Controls) + { + if (child.HasChildren && !(child is Form)) + // recursively apply style to children of container controls (GroupBoxes, SplitContainers, etc.) + ApplyStyle(child, useNppStyle, darkMode); + // controls containing text + if (child is TextBox || child is ListBox || child is ComboBox || child is TreeView) + { + child.BackColor = SystemColors.Window; // white background + child.ForeColor = SystemColors.WindowText; + } + else if (child is DataGridView dgv) + { + dgv.EnableHeadersVisualStyles = true; + dgv.BackgroundColor = SystemColors.ControlDark; + dgv.ForeColor = SystemColors.ControlText; + dgv.GridColor = SystemColors.ControlLight; + dgv.RowsDefaultCellStyle.ForeColor = SystemColors.ControlText; + dgv.RowsDefaultCellStyle.BackColor = SystemColors.Window; + } + else + { + // buttons should be a bit darker but everything else is the same color as the background + child.BackColor = (child is Button) ? SlightlyDarkControl : SystemColors.Control; + child.ForeColor = SystemColors.ControlText; + if (child is LinkLabel llbl) + { + llbl.LinkColor = Color.Blue; + llbl.ActiveLinkColor = Color.Red; + llbl.VisitedLinkColor = Color.Purple; + } + } + } + return; + } + Color foreColor = Npp.notepad.GetDefaultForegroundColor(); + ctrl.BackColor = backColor; + Color InBetween = Color.FromArgb( + foreColor.R / 4 + 3 * backColor.R / 4, + foreColor.G / 4 + 3 * backColor.G / 4, + foreColor.B / 4 + 3 * backColor.B / 4 + ); + foreach (Control child in ctrl.Controls) + { + child.BackColor = backColor; + child.ForeColor = foreColor; + if (child.HasChildren && !(child is Form)) + // recursively apply style to children of container controls (GroupBoxes, SplitContainers, etc.) + ApplyStyle(child, useNppStyle, darkMode); + if (child is LinkLabel llbl) + { + llbl.LinkColor = foreColor; + llbl.ActiveLinkColor = foreColor; + llbl.VisitedLinkColor = foreColor; + } + else if (child is DataGridView dgv) + { + dgv.EnableHeadersVisualStyles = false; + dgv.BackgroundColor = InBetween; + dgv.ForeColor = foreColor; + dgv.GridColor = foreColor; + dgv.ColumnHeadersDefaultCellStyle.ForeColor = foreColor; + dgv.ColumnHeadersDefaultCellStyle.BackColor = backColor; + dgv.RowHeadersDefaultCellStyle.ForeColor = foreColor; + dgv.RowHeadersDefaultCellStyle.BackColor = backColor; + dgv.RowsDefaultCellStyle.ForeColor = foreColor; + dgv.RowsDefaultCellStyle.BackColor = backColor; + } + } + } + } +} diff --git a/nppRandomStringGenerator/Utils/NanInf.cs b/nppRandomStringGenerator/Utils/NanInf.cs new file mode 100644 index 0000000..512aa67 --- /dev/null +++ b/nppRandomStringGenerator/Utils/NanInf.cs @@ -0,0 +1,21 @@ +namespace NppPluginNET.Utils +{ + public class NanInf + { + /// + /// a/b

+ /// may be necessary to generate infinity or nan at runtime + /// to avoid the compiler pre-computing things

+ /// since if the compiler sees literal division by 0d in the code + /// it just pre-computes it at compile time + ///
+ /// + /// + /// + public static double Divide(double a, double b) { return a / b; } + + public static readonly double inf = Divide(1d, 0d); + public static readonly double neginf = Divide(-1d, 0d); + public static readonly double nan = Divide(0d, 0d); + } +} diff --git a/nppRandomStringGenerator/Utils/Npp.cs b/nppRandomStringGenerator/Utils/Npp.cs new file mode 100644 index 0000000..7688aba --- /dev/null +++ b/nppRandomStringGenerator/Utils/Npp.cs @@ -0,0 +1,352 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Windows.Forms; +using Kbg.NppPluginNET; +using Kbg.NppPluginNET.PluginInfrastructure; + +/// +/// miscellaneous useful things like a connector to Notepad++ +/// +namespace NppPluginNET.Utils +{ + /// + /// contains connectors to Scintilla (editor) and Notepad++ (notepad) + /// + public class Npp + { + /// + /// connector to Scintilla + /// + public static IScintillaGateway editor = new ScintillaGateway(PluginBase.GetCurrentScintilla()); + /// + /// connector to Notepad++ + /// + public static INotepadPPGateway notepad = new NotepadPPGateway(); + + /// + /// this should only be instantiated once in your entire project + /// + public static Random random = new Random(); + + public static readonly int[] nppVersion = notepad.GetNppVersion(); + + public static readonly string nppVersionStr = NppVersionString(true); + + public static readonly bool nppVersionAtLeast8 = nppVersion[0] >= 8; + + /// + /// the directory containing of the plugin DLL (i.e., the DLL that this code compiles into)

+ /// usually Path.Combine(notepad.GetNppPath(), "plugins", Main.PluginName) would work just as well,

+ /// but under some weird circumstances (see this GitHub issue comment: https://github.com/molsonkiko/NppCSharpPluginPack/issues/5#issuecomment-1982167513)

+ /// it can fail. + ///
+ public static readonly string pluginDllDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + /// + /// append text to current doc, then append newline and move cursor + /// + /// + public static void AddLine(string inp) + { + editor.AppendText(Encoding.UTF8.GetByteCount(inp), inp); + editor.AppendText(Environment.NewLine.Length, Environment.NewLine); + } + + public enum PathType + { + FULL_CURRENT_PATH, + FILE_NAME, + DIRECTORY + } + + /// + /// input is one of 'p', 'd', 'f'

+ /// if 'p', get full path to current file (default)

+ /// if 'd', get directory of current file

+ /// if 'f', get filename of current file + ///
+ /// + /// + public static string GetCurrentPath(PathType which = PathType.FULL_CURRENT_PATH) + { + NppMsg msg = NppMsg.NPPM_GETFULLCURRENTPATH; + switch (which) + { + case PathType.FULL_CURRENT_PATH: break; + case PathType.DIRECTORY: msg = NppMsg.NPPM_GETCURRENTDIRECTORY; break; + case PathType.FILE_NAME: msg = NppMsg.NPPM_GETFILENAME; break; + default: throw new ArgumentException("GetCurrentPath argument must be member of PathType enum"); + } + + StringBuilder path = new StringBuilder(Win32.MAX_PATH); + Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)msg, 0, path); + + return path.ToString(); + } + + /// + /// Get the file type for a file path (no period)

+ /// Default path is the currently open file. + ///
+ /// + /// + public static string FileExtension(string path = null) + { + if (path == null) + path = GetCurrentPath(Npp.PathType.FILE_NAME); + StringBuilder sb = new StringBuilder(); + for (int ii = path.Length - 1; ii >= 0; ii--) + { + char c = path[ii]; + if (c == '.') break; + sb.Append(c); + } + // the chars were added in the wrong direction, so reverse them + return sb.ToString().Slice("::-1"); + } + + /// + /// Trying to copy an empty string or null to the clipboard raises an error.

+ /// This shows a message box if the user tries to do that. + ///
+ /// + public static void TryCopyToClipboard(string text) + { + if (text == null || text.Length == 0) + { + Translator.ShowTranslatedMessageBox("Couldn't find anything to copy to the clipboard", + "Nothing to copy to clipboard", + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + return; + } + Clipboard.SetText(text); + } + + private static bool stopShowingFileTooLongNotifications = false; + + private static void WarnFileTooBig(bool showMessage) + { + if (showMessage && !stopShowingFileTooLongNotifications) + stopShowingFileTooLongNotifications = Translator.ShowTranslatedMessageBox( + $"{Main.PluginName} cannot perform this plugin command on a file with more than 2147483647 bytes.\r\nDo you want to stop showing notifications when a file is too long?", + $"File too long for {Main.PluginName}", + MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes; + } + + /// + /// if returns a number greater than , return false and set len to -1.

+ /// Otherwise, return true and set len to the length of the document.

+ /// If showMessageOnFail, show a message box warning the user that the command could not be executed. + ///
+ public static bool TryGetLengthAsInt(out int len, bool showMessageOnFail = true) + { + if (!editor.TryGetLengthAsInt(out len)) + { + WarnFileTooBig(showMessageOnFail); + return false; + } + return true; + } + + /// + /// if returns a number greater than , return false and set text to an empty string.

+ /// Otherwise, return true and set text with .

+ /// If showMessageOnFail, show a message box warning the user that the command could not be executed. + ///
+ public static bool TryGetText(out string text, bool showMessageOnFail = true, int length = -1) + { + if (length < 0 && !editor.TryGetLengthAsInt(out length)) + { + text = ""; + WarnFileTooBig(showMessageOnFail); + return false; + } + text = editor.GetText(length); + return true; + } + + public static string AssemblyVersionString() + { + string version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + while (version.EndsWith(".0")) + version = version.Substring(0, version.Length - 2); +#if DEBUG + return $"{version} Debug"; +#else + return version; +#endif // DEBUG + } + + public static void CreateConfigSubDirectoryIfNotExists() + { + var ConfigDirInfo = new DirectoryInfo(Main.PluginConfigDirectory); + if (!ConfigDirInfo.Exists) + ConfigDirInfo.Create(); + } + + /// + /// get all text starting at position start in the current document + /// and ending at position end in the current document + /// + /// + /// + /// + public static string GetSlice(int start, int end) + { + int len = end - start; + IntPtr rangePtr = editor.GetRangePointer(start, len); + string ansi = Marshal.PtrToStringAnsi(rangePtr, len); + // TODO: figure out a way to do this that involves less memcopy for non-ASCII + if (ansi.Any(c => c >= 128)) + return Encoding.UTF8.GetString(Encoding.Default.GetBytes(ansi)); + return ansi; + } + + private static readonly string[] newlines = new string[] { "\r\n", "\r", "\n" }; + + /// 0: CRLF, 1: CR, 2: LF

+ /// Anything less than 0 or greater than 2: LF
+ public static string GetEndOfLineString(int eolType) + { + if (eolType < 0 || eolType >= 3) + return "\n"; + return newlines[eolType]; + } + + private static string NppVersionString(bool include32bitVs64bit) + { + int[] nppVer = notepad.GetNppVersion(); + string nppVerStr = $"{nppVer[0]}.{nppVer[1]}.{nppVer[2]}"; + return include32bitVs64bit ? $"{nppVerStr} {IntPtr.Size * 8}bit" : nppVerStr; + } + + /// + /// appends the JSON representation of char c to a StringBuilder.

+ /// for most characters, this just means appending the character itself, but for example '\n' would become "\\n", '\t' would become "\\t",

+ /// and most other chars less than 32 would be appended as "\\u00{char value in hex}" (e.g., '\x14' becomes "\\u0014") + ///
+ public static void CharToSb(StringBuilder sb, char c) + { + switch (c) + { + case '\\': sb.Append("\\\\"); break; + case '"': sb.Append("\\\""); break; + case '\x01': sb.Append("\\u0001"); break; + case '\x02': sb.Append("\\u0002"); break; + case '\x03': sb.Append("\\u0003"); break; + case '\x04': sb.Append("\\u0004"); break; + case '\x05': sb.Append("\\u0005"); break; + case '\x06': sb.Append("\\u0006"); break; + case '\x07': sb.Append("\\u0007"); break; + case '\x08': sb.Append("\\b"); break; + case '\x09': sb.Append("\\t"); break; + case '\x0A': sb.Append("\\n"); break; + case '\x0B': sb.Append("\\v"); break; + case '\x0C': sb.Append("\\f"); break; + case '\x0D': sb.Append("\\r"); break; + case '\x0E': sb.Append("\\u000E"); break; + case '\x0F': sb.Append("\\u000F"); break; + case '\x10': sb.Append("\\u0010"); break; + case '\x11': sb.Append("\\u0011"); break; + case '\x12': sb.Append("\\u0012"); break; + case '\x13': sb.Append("\\u0013"); break; + case '\x14': sb.Append("\\u0014"); break; + case '\x15': sb.Append("\\u0015"); break; + case '\x16': sb.Append("\\u0016"); break; + case '\x17': sb.Append("\\u0017"); break; + case '\x18': sb.Append("\\u0018"); break; + case '\x19': sb.Append("\\u0019"); break; + case '\x1A': sb.Append("\\u001A"); break; + case '\x1B': sb.Append("\\u001B"); break; + case '\x1C': sb.Append("\\u001C"); break; + case '\x1D': sb.Append("\\u001D"); break; + case '\x1E': sb.Append("\\u001E"); break; + case '\x1F': sb.Append("\\u001F"); break; + default: sb.Append(c); break; + } + } + + /// + /// the string representation of a JSON string + /// if not quoted, this will not have the enclosing quotes a JSON string normally has + /// + public static string StrToString(string s, bool quoted) + { + int slen = s.Length; + int ii = 0; + for (; ii < slen; ii++) + { + char c = s[ii]; + if (c < 32 || c == '\\' || c == '"') + break; + } + if (ii == slen) + return quoted ? $"\"{s}\"" : s; + var sb = new StringBuilder(); + if (quoted) + sb.Append('"'); + if (ii > 0) + { + ii--; + sb.Append(s, 0, ii); + } + for (; ii < slen; ii++) + CharToSb(sb, s[ii]); + if (quoted) + sb.Append('"'); + return sb.ToString(); + } + + /// + /// Based on the value of askUser, do one of three things:

+ /// DONT_DO_DONT_ASK: return false (don't do the thing)

+ /// ASK_BEFORE_DOING:

+ /// 1. show a Yes/No message box with text messageBoxText and caption messageBoxCaption.

+ /// 2. if and only if the user clicks Yes, return true.

+ /// DO_WITHOUT_ASKING: return true (do the thing without asking) + ///
+ /// whether to ask user + /// text of message box (if and only if askUser = ASK_BEFORE_DOING + /// caption of message box (if and only if askUser = ASK_BEFORE_DOING + /// + public static bool AskBeforeDoingSomething(AskUserWhetherToDoThing askUser, string messageBoxText, string messageBoxCaption) + { + switch (askUser) + { + case AskUserWhetherToDoThing.DONT_DO_DONT_ASK: + return false; + case AskUserWhetherToDoThing.ASK_BEFORE_DOING: + return Translator.ShowTranslatedMessageBox(messageBoxText, + messageBoxCaption, + MessageBoxButtons.YesNo, MessageBoxIcon.Question + ) != DialogResult.No; + case AskUserWhetherToDoThing.DO_WITHOUT_ASKING: + default: + break; + } + return true; + } + } + + public enum AskUserWhetherToDoThing + { + /// + /// don't do the thing, and don't prompt the user either + /// + DONT_DO_DONT_ASK, + /// + /// prompt the user to ask whether to do it + /// + ASK_BEFORE_DOING, + /// + /// do it without prompting the user + /// + DO_WITHOUT_ASKING, + } +} diff --git a/nppRandomStringGenerator/Utils/NppListener.cs b/nppRandomStringGenerator/Utils/NppListener.cs new file mode 100644 index 0000000..9f8d7f2 --- /dev/null +++ b/nppRandomStringGenerator/Utils/NppListener.cs @@ -0,0 +1,36 @@ +using NppPluginNET.PluginInfrastructure; +using Kbg.NppPluginNET; +using System.Windows.Forms; + +namespace NppPluginNET.Utils +{ + /// + /// This class listens to messages that are not broadcast by the plugins manager (e.g., Notepad++ internal messages).

+ /// The original idea comes from Peter Frentrup's NppMenuSearch plugin (https://github.com/search?q=repo%3Apeter-frentrup%2FNppMenuSearch%20NppListener&type=code) + ///
+ public class NppListener : NativeWindow + { + /// + /// the NPPM_INTERNAL_RELOADNATIVELANG message is fired twice every time we change the native language preference. + /// I don't know why, but we should ignore every other one of those messages. + /// + private static bool reloadNativeLangToggle = false; + + protected override void WndProc(ref Message m) + { + if (!Main.isShuttingDown) + { + switch (m.Msg) + { + case (int)Resource.NPPM_INTERNAL_RELOADNATIVELANG: + base.WndProc(ref m); + if (reloadNativeLangToggle) + Translator.ResetTranslations(false); + reloadNativeLangToggle = !reloadNativeLangToggle; + return; + } + } + base.WndProc(ref m); + } + } +} diff --git a/nppRandomStringGenerator/Utils/Translator.cs b/nppRandomStringGenerator/Utils/Translator.cs new file mode 100644 index 0000000..e44c6e2 --- /dev/null +++ b/nppRandomStringGenerator/Utils/Translator.cs @@ -0,0 +1,599 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; +using System.IO; +using NppPluginNET.JSON_Tools; +using Kbg.NppPluginNET; +using Kbg.NppPluginNET.PluginInfrastructure; +using System.Reflection; +using System.ComponentModel; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace NppPluginNET.Utils +{ + public static class Translator + { + private static JObject translations = null; + public static bool HasTranslations => !(translations is null); + + public static bool HasLoadedAtStartup { get; private set; } = false; + + public static string languageName { get; private set; } = DEFAULT_LANG; + + public const string DEFAULT_LANG = "english"; + public const string DEFAULT_PLUGINS_MENU_NAME = "&Plugins"; + /// + /// whether to use the Windows culture (see below) to determine translation language in some cases

+ /// This is currently false because of issue 82 (https://github.com/molsonkiko/JsonToolsNppPlugin/issues/82) + ///
+ private const bool CHECK_WINDOWS_CULTURE = false; + public static readonly string translationDir = Path.Combine(Npp.pluginDllDirectory, "translation"); + + /// + /// Attempts to load translations from a config file. + /// + /// Is this method being called as the plugin is starting up? + /// If null, use the logic described in README.md to determine which language to load. Otherwise, do not attempt to load any language other than preferredLang. + private static void LoadTranslations(bool atStartup = true) + { + if (atStartup && languageName == DEFAULT_LANG) + { + //MessageBox.Show("Not loading translations, because english is the current culture language"); + return; + } + if (!TryGetTranslationFileName(languageName, out string translationFilename)) + { + //MessageBox.Show($"Could not find a translation file for language {languageFirstname} in directory {translationDir}"); + languageName = DEFAULT_LANG; + return; + } + FileInfo translationFile = new FileInfo(translationFilename); + try + { + var parser = new JsonParser(LoggerLevel.JSON5); + string translationFileText; + using (var fp = new StreamReader(translationFile.OpenRead(), Encoding.UTF8, true)) + { + translationFileText = fp.ReadToEnd(); + } + translations = (JObject)parser.Parse(translationFileText); + PropagateGlobalConstants(); + //MessageBox.Show($"Found and successfully parsed translation file at path {translationFilename}"); + } + catch (Exception ex) + { + languageName = DEFAULT_LANG; + MessageBox.Show($"While attempting to parse translation file {translationFilename}, got an exception:\r\n{ex}"); + } + } + + /// + /// Attempts to read the nativeLang.xml file that determines language of Notepad++ UI. If successful, returns true and sets nativeLangXml to text of that file.

+ /// On failure, nativeLangXml = null and returns false + ///
+ /// + /// + private static bool TryGetNppNativeLangXmlText(out string nativeLangXml) + { + string nativeLangXmlFname = Path.Combine(Npp.notepad.GetConfigDirectory(), "..", "..", "nativeLang.xml"); + if (File.Exists(nativeLangXmlFname)) + { + try + { + using (var reader = new StreamReader(File.OpenRead(nativeLangXmlFname), Encoding.UTF8, true)) + { + nativeLangXml = reader.ReadToEnd(); + } + return true; + } + catch //(Exception ex) + { + //MessageBox.Show($"While attempting to determine native language preference from Notepad++ config XML, got an error:\r\n{ex}"); + } + } + nativeLangXml = null; + return false; + } + + private static bool TryGetTranslationFileName(string langName, out string translationFilename) + { + translationFilename = Path.Combine(translationDir, langName + ".json5"); + if (!File.Exists(translationFilename)) + { + translationFilename = null; + return false; + } + return true; + } + + /// + /// Find the string at the end of a sequence of keys in translations, returning false if no such string was found.

+ /// For example:

+ /// + /// Suppose translations is + /// {"foo": {"bar": "1", "rnq": "2"}, "quz": "3"} + /// - TryGetTranslationAtPath(["foo", "bar"], out JNode result) would set result to "1" and return true

+ /// - TryGetTranslationAtPath(["foo", "rnq"], out JNode result) would set result to "2" and return true

+ /// - TryGetTranslationAtPath(["blah", "rnq"], out JNode result) would set result to null and return false

+ /// - TryGetTranslationAtPath(["foo", "rnq", "b"], out JNode result) would set result to null and return false

+ /// - TryGetTranslationAtPath(["foo", "b"], out JNode result) would set result to null and return false

+ /// - TryGetTranslationAtPath(["quz"], out JNode result) would set result to "3" and return true

+ /// - TryGetTranslationAtPath(["foo"], out JNode result) would set result to {"bar": "1", "rnq": "2"} and return true

+ ///
+ ///
+ /// + /// + /// + public static bool TryGetTranslationAtPath(string[] pathToTrans, out JNode result) + { + result = null; + if (!(translations is JObject trans) || pathToTrans.Length == 0) + return false; + int pathLen = pathToTrans.Length; + JNode child = new JNode(); + for (int ii = 0; ii < pathLen; ii++) + { + string key = pathToTrans[ii]; + if (!trans.TryGetValue(key, out child)) + return false; + if (ii < pathLen - 1) + { + if (child is JObject childObj) + trans = childObj; + else + return false; + } + } + result = child; + return true; + } + + private static readonly Regex constantNameRegex = new Regex(@"\A\$[a-zA-Z_]+\$\z", RegexOptions.Compiled); + + /// + /// Replaces all instances of constant names (see the "$constants$" field of the translation files) with their value in the translation file + /// + private static void PropagateGlobalConstants() + { + if (translations is JObject jobj + && translations.children.TryGetValue("$constants$", out JNode constsNode) && constsNode is JObject consts) + { + var constants = new Dictionary(); + var invalidConstantNames = new JArray(); + foreach (string key in consts.children.Keys) + { + if (constantNameRegex.IsMatch(key)) + constants[key] = consts[key].ValueOrToString(); + else + invalidConstantNames.children.Add(new JNode(key)); + } + if (invalidConstantNames.Length > 0) + { + MessageBox.Show($"The \"$constants$\" field for the {languageName}.json5 file contains constants where the key does not match the regular expression {JNode.StrToString(constantNameRegex.ToString(), true)}:\r\n{invalidConstantNames.ToString()}", + "Invalid \"$constants$\" field", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + if (constants.Count > 0) + { + foreach (KeyValuePair kv in jobj.children) + { + if (kv.Key != "$constants$") + PropagateGlobalConstantsHelper(kv.Value, constants); + } + } + } + } + + private static void PropagateGlobalConstantsHelper(JNode node, Dictionary constants) + { + if (node.value is string s && s.IndexOf('$') >= 0) + { + node.value = PropagateConstantsToString(s, constants); + } + else if (node is JArray jarr) + { + foreach (JNode child in jarr.children) + PropagateGlobalConstantsHelper(child, constants); + } + else if (node is JObject jobj) + { + foreach (JNode child in jobj.children.Values) + PropagateGlobalConstantsHelper(child, constants); + var keys = jobj.children.Keys.ToArray(); + foreach (string key in keys) + { + if (key.IndexOf('$') >= 0) + { + string newKey = PropagateConstantsToString(key, constants); + if (newKey != key) + { + jobj[newKey] = jobj[key]; + jobj.children.Remove(key); + } + } + } + } + } + + private static string PropagateConstantsToString(string s, Dictionary constants) + { + string newValue = s; + foreach (KeyValuePair kv in constants) + newValue = newValue.Replace(kv.Key, kv.Value); + return newValue; + } + + /// + /// Translates menu item values (using as the key in the "menuItems" field) + /// + /// + /// + public static string GetTranslatedMenuItem(string menuItem) + { + if (translations is JObject jobj + && jobj.TryGetValue("menuItems", out JNode menuItemsObjNode) + && menuItemsObjNode is JObject menuItemsObj + && menuItemsObj.TryGetValue(menuItem, out JNode menuItemNode) + && menuItemNode.value is string s) + { + if (s.Length > FuncItem.MAX_FUNC_ITEM_NAME_LENGTH) + { + MessageBox.Show($"The translation {JNode.StrToString(s, true)} has {s.Length} characters, " + + $"but it must have {FuncItem.MAX_FUNC_ITEM_NAME_LENGTH} or fewer characters.\r\n" + + $"Because of this, the untranslated command, {JNode.StrToString(menuItem, true)} is being used.", + "Too many characters in menu item translation", + MessageBoxButtons.OK, + MessageBoxIcon.Warning); + return menuItem; + } + return s; + } + return menuItem; + } + + #region Reloading translations + /// + /// When the UI language of Notepad++ changes, attempt to translate JsonTools to the Notepad++ UI language automatically.

+ /// If translation fails, or if the current UI language is english, restore the language of JsonTools to the default english. + ///
+ /// true if and only if translation was successful AND the plugin was translated to a language other than English + public static bool ResetTranslations(bool atStartup) + { + if (atStartup && HasLoadedAtStartup) + return false; + HasLoadedAtStartup = true; + string oldLanguageName = languageName; + languageName = DEFAULT_LANG; + string newLanguageName = ""; + // first try using the NPPM_GETNATIVELANGNAME API introduced in v8.7.0 + if (Npp.notepad.TryGetNativeLangName(out newLanguageName)) + { + newLanguageName = GetEmptyStringIfLangInvalid(newLanguageName, atStartup); + } + else + { + // if that failed, try to use the Notepad++ nativeLang.xml config file to determine the user's language preference + if (TryGetNppNativeLangXmlText(out string nativeLangXml)) + { + Match langNameMtch = Regex.Match(nativeLangXml, " + /// + /// + /// + /// true if and only if translation was successful AND the plugin was translated to a language other than English + private static bool ResetTranslationsHelper(bool restoreToEnglish, bool atStartup) + { + bool result = true; + List newMenuItemNames = PluginBase.GetUntranslatedFuncItemNames(); + LoadTranslations(atStartup); + if (restoreToEnglish) + result = false; + else + newMenuItemNames = newMenuItemNames.Select(x => GetTranslatedMenuItem(x)).ToList(); + IntPtr allPluginsMenuHandle = Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_GETMENUHANDLE, (int)NppMsg.NPPPLUGINMENU, 0); + if (allPluginsMenuHandle == IntPtr.Zero) + result = false; + else if (!(atStartup && restoreToEnglish)) + PluginBase.ChangePluginMenuItemNames(allPluginsMenuHandle, newMenuItemNames); + //if (Main.selectionRememberingForm != null && !Main.selectionRememberingForm.IsDisposed) + //{ + // TranslateForm(Main.selectionRememberingForm); + //} + if (restoreToEnglish) // now that we're done restoring forms to English, we can reset translations to null + translations = null; + return result; + } + +#endregion // translating menu items + + /// + /// Finds the appropriate translation for a (using as key in the "messageBoxes" field), then displays it and returns the result of

+ /// EXAMPLE 1:

+ /// Suppose the "messageBoxes" field of your active translation file looks like this: + /// + /// { + /// "Foo is not compatible with {0}": { + /// "caption": "Foo no es compatible con {0}", + /// "text": "Cuando foo era {0}, esperaba que {1} estará {2}, pero {1} era {3}" + /// } + /// } + /// + /// - Translator.ShowTranslatedMessageBox("When foo was {0}, expected that {1} would be {2}, but {1} was {3}", "Foo is not compatible with {0}", MessageBoxButtons.OK, MessageBoxIcon.Warning, 4, "fooValue", "bar", "barExpectedValue", "barActualValue", "bar"); would show a MessageBox with caption "Foo no es compatible con bar" and text "Cuando foo era fooValue, esperaba que bar estará barExpectedValue, pero bar era barActualValue" (and the OK button), and would return .

+ /// EXAMPLE 2:

+ /// Suppose that there is no active translation file (so everything is in the original English)

+ /// - Translator.ShowTranslatedMessageBox("When foo was {0}, expected that {1} would be {2}, but {1} was {3}", "Foo is not compatible with {0}", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, 4, "fooValue", "quz", "quzExpectedValue", "quzActualValue", "QUZQUZ"); would show a MessageBox with caption "Foo is not compatible with QUZQUZ" and text "When foo was fooValue, expected that quz would be quzExpectedValue, but quz was quzActualValue" (and Yes and No buttons), and would return if the user clicked Yes + ///
+ /// the text of the MessageBox, which may be a format string of the form "expected {0}, got {1}" + /// the text of the MessageBox, which may be a format string of the form "expected {0}, got {1}" + /// which buttons to show in the MB + /// the icon to use + /// the first nTextParams values in the are used to format the argument + /// the first nTextParams values in the are used to format the argument + /// all values in this array after the first are used to format the caption + /// the result of MessageBox.Show with the translated box + public static DialogResult ShowTranslatedMessageBox(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, int nTextParams = 0, params object[] formattingParams) + { + string translatedText = text, translatedCaption = caption; + if (translations is JObject jobj + && jobj.TryGetValue("messageBoxes", out JNode messagesNode) && messagesNode is JObject messages + && messages.TryGetValue(caption, out JNode mbTransNode) && mbTransNode is JObject mbTrans + && mbTrans.children is Dictionary mbDict) + { + if (mbDict.TryGetValue("caption", out JNode captionTrans) && captionTrans.value is string captionTransStr) + translatedCaption = captionTransStr; + if (mbDict.TryGetValue("text", out JNode textTrans) && textTrans.value is string textTransStr) + translatedText = textTransStr; + } + if (formattingParams.Length < nTextParams) + { + MessageBox.Show($"While attempting to call ShowTranslatedMessageBox({JNode.StrToString(text, true)}, {JNode.StrToString(caption, true)}, {buttons}, {icon}, {nTextParams}, ...)\r\ngot {formattingParams.Length} formatting params, but expected {nTextParams}", "error in formatting for ShowTranslatedMessageBox", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return MessageBox.Show(translatedText, translatedCaption, buttons, icon); + } + int nCaptionParams = formattingParams.Length - nTextParams; + object[] textParams = formattingParams.Take(nTextParams).ToArray(), captionParams = formattingParams.LazySlice(nTextParams).ToArray(); + string formattedText = nTextParams == 0 ? translatedText : TryTranslateWithFormatting(text, translatedText, textParams); + string formattedCaption = nCaptionParams == 0 ? translatedCaption : TryTranslateWithFormatting(caption, translatedCaption, captionParams); + return MessageBox.Show(formattedText, formattedCaption, buttons, icon); + } + + public static string TryTranslateWithFormatting(string untranslated, string translated, params object[] formatParams) + { + try + { + return string.Format(translated, formatParams); + } + catch { } + try + { + return string.Format(untranslated, formatParams); + } + catch + { + return untranslated; + } + } + + /// + /// Used to translate the settings in .

+ /// If there is no active translation file, return the propertyInfo's + /// (which can be seen in the source code in the Description decorator of each setting).

+ /// If .Name is a field in the "settingsDescriptions" field of the active translation file, return that value. + ///
+ public static string TranslateSettingsDescription(PropertyInfo propertyInfo) + { + if (propertyInfo is null) + return ""; + if (translations is JObject jobj + && jobj.TryGetValue("settingsDescriptions", out JNode settingsDescNode) + && settingsDescNode is JObject settingsDescObj + && settingsDescObj.TryGetValue(propertyInfo.Name, out JNode descNode) + && descNode.value is string s) + { + return s; + } + if (propertyInfo.GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault() is DescriptionAttribute description) + return description.Description; + return ""; + } + + #region Form translation + + /// + /// This assumes that each character in Size 7.8 font is 7 pixels across.

+ /// In practice, this generally returns a number that is greater than the width of the text. + ///
+ private static int WidthInCharacters(int numChars, float fontSize) + { + return Convert.ToInt32(Math.Round(numChars * fontSize / 7.8 * 7)); + } + + /// + /// translate a form and its controls + /// according to the entry corresponding to the form's name in the "forms" field of the translations object. + /// + public static void TranslateForm(Form form) + { + if (translations is JObject jobj + && jobj.TryGetValue("forms", out JNode formsNode) + && formsNode is JObject allFormsObj + && allFormsObj.TryGetValue(form.Name, out JNode formNode) + && formNode is JObject formObj && formObj.children is Dictionary formDict) + { + if (formDict.TryGetValue("title", out JNode transTitle) && transTitle.value is string transStr) + form.Text = transStr; + if (formDict.TryGetValue("controls", out JNode controlTransNode) + && controlTransNode is JObject controlTranslationsObj + && controlTranslationsObj.children is Dictionary controlTranslations) + { + TranslateControl(form, controlTranslations); + foreach (Form childForm in form.OwnedForms) + TranslateForm(childForm); + } + } + } + + /// + /// Translate a control according to the logic described in "/translations/english.json5" (relative to the repo root)

+ /// and recursively translate any child controls. + ///
+ /// The translations for all the controls in the form + private static void TranslateControl(Control ctrl, Dictionary controlTranslations) + { + if (!(ctrl is Form) && controlTranslations.TryGetValue(ctrl.Name, out JNode ctrlNode)) + { + if (ctrlNode.value is string s) + ctrl.Text = s; + else if (ctrlNode is JObject ctrlObj && ctrlObj.children is Dictionary ctrlDict) + { + if (ctrl is CheckBox checkBox + && ctrlDict.TryGetValue("checked", out JNode checkedNode) && checkedNode.value is string checkedVal + && ctrlDict.TryGetValue("unchecked", out JNode uncheckedNode) && uncheckedNode.value is string uncheckedVal) + { + checkBox.Text = checkBox.Checked ? checkedVal : uncheckedVal; + checkBox.CheckedChanged += (object sender, EventArgs e) => + { + checkBox.Text = checkBox.Checked ? checkedVal : uncheckedVal; + }; + } + else if (ctrl is LinkLabel llbl + && ctrlDict.TryGetValue("text", out JNode textNode) && textNode.value is string textVal + && ctrlDict.TryGetValue("linkStart", out JNode linkStartNode) && linkStartNode.value is long linkStartVal + && ctrlDict.TryGetValue("linkLength", out JNode linkLengthNode) && linkLengthNode.value is long linkLengthVal) + { + llbl.Text = textVal; + llbl.LinkArea = new LinkArea(Convert.ToInt32(linkStartVal), Convert.ToInt32(linkLengthVal)); + } + } + else if (ctrlNode is JArray ctrlArr && ctrlArr.children is List ctrlList && ctrl is ComboBox comboBox) + { + int maxTranslationLen = 0; + for (int ii = 0; ii < comboBox.Items.Count && ii < ctrlList.Count; ii++) + { + JNode translationII = ctrlList[ii]; + if (translationII.value is string translationVal) + { + comboBox.Items[ii] = translationVal; + if (translationVal.Length > maxTranslationLen) + maxTranslationLen = translationVal.Length; + } + } + int newDropDownWidth = WidthInCharacters(maxTranslationLen, ctrl.Font.Size); + if (newDropDownWidth > comboBox.DropDownWidth) + comboBox.DropDownWidth = newDropDownWidth; + } + } + int ctrlCount = ctrl.Controls.Count; + if (ctrlCount > 0) + { + // translate each child of this control + var childrenByLeft = new List<(Control ctrl, bool textWasMultiline, bool isAnchoredRight)>(ctrlCount); + foreach (Control child in ctrl.Controls) + { + int childWidthInChars = WidthInCharacters(child.Text.Length, child.Font.Size); + bool textWasMultiline = !child.AutoSize && ((child is Label || child is LinkLabel) && childWidthInChars > child.Width); + TranslateControl(child, controlTranslations); + childrenByLeft.Add((child, textWasMultiline, (child.Anchor & AnchorStyles.Right) == AnchorStyles.Right)); + } + // the child controls may have more text now, + // so we need to resolve any collisions that may have resulted. + childrenByLeft.Sort((x, y) => x.ctrl.Left.CompareTo(y.ctrl.Left)); + for (int ii = 0; ii < ctrlCount; ii++) + { + (Control child, bool textWasMultiline, bool isAnchoredRight) = childrenByLeft[ii]; + int textLen = child.Text.Length; + int widthIfButtonOrLabel = WidthInCharacters(textLen, child.Font.Size + 1); + if (child is Button && textLen > 2 && textLen < 14) + { + int increase = textLen <= 5 ? 25 : (15 - textLen) * 2 + 7; + widthIfButtonOrLabel += increase; + } + if (!textWasMultiline && (child is Button || child is Label || child is CheckBox) && + !child.Text.Contains('\n') && widthIfButtonOrLabel > child.Width) + { + child.Width = widthIfButtonOrLabel; + if (isAnchoredRight) + child.Left -= 5; // to account for weirdness when you resize a right-anchored thing + // if there are overlapping controls to the right, move those far enough right that they don't overlap. + for (int jj = ii + 1; jj < ctrlCount; jj++) + { + Control otherChild = childrenByLeft[jj].ctrl; + if (otherChild.Left > child.Left && Overlap(child, otherChild)) + { + if (otherChild.Left > child.Left) + otherChild.Left = child.Right + 2; + } + } + } + child.Refresh(); + } + int maxRight = 0; + foreach (Control child in ctrl.Controls) + maxRight = child.Right > maxRight ? child.Right : maxRight; + // Temporarily turn off the normal layout logic, + // because this would cause controls to move right when the form is resized + // (yes, even if they're not anchored right. No, that doesn't make sense) + ctrl.SuspendLayout(); + if (maxRight > ctrl.Width) + { + int padding = ctrl is Form ? 25 : 5; + ctrl.Width = maxRight + padding; + } + // Resume layout logic, ignoring the pending requests to move controls right due to resizing of form + ctrl.ResumeLayout(false); + } + } + + private static bool VerticalOverlap(Control ctrl1, Control ctrl2) + { + return !(ctrl1.Bottom < ctrl2.Top || ctrl2.Bottom < ctrl1.Top); + } + + private static bool HorizontalOverlap(Control ctrl1, Control ctrl2) + { + return !(ctrl1.Right < ctrl2.Left || ctrl2.Right < ctrl1.Left); + } + + private static bool Overlap(Control ctrl1, Control ctrl2) + { + return HorizontalOverlap(ctrl1, ctrl2) && VerticalOverlap(ctrl1, ctrl2); + } + #endregion + } +} diff --git a/nppRandomStringGenerator/app.manifest b/nppRandomStringGenerator/app.manifest deleted file mode 100644 index 20b4817..0000000 --- a/nppRandomStringGenerator/app.manifest +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/nppRandomStringGenerator/nppRandomStringGenerator.csproj b/nppRandomStringGenerator/nppRandomStringGenerator.csproj index 76e14d3..d0278da 100644 --- a/nppRandomStringGenerator/nppRandomStringGenerator.csproj +++ b/nppRandomStringGenerator/nppRandomStringGenerator.csproj @@ -1,7 +1,5 @@  - - Debug x86 @@ -69,16 +67,16 @@ OnOutputUpdated - - app.manifest - + + + @@ -88,6 +86,8 @@ About.cs + + @@ -120,6 +120,12 @@ + + + + + + @@ -136,33 +142,25 @@ - - - PreserveNewest - - - + + + 1.2.1 + true + all + + - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - + diff --git a/nppRandomStringGenerator/packages.config b/nppRandomStringGenerator/packages.config deleted file mode 100644 index 09b27c4..0000000 --- a/nppRandomStringGenerator/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file From b5d28de53a395cf726a2b8a9da256ee73eb03cb7 Mon Sep 17 00:00:00 2001 From: Maurice Beckman Date: Wed, 12 Feb 2025 19:52:41 +0100 Subject: [PATCH 3/3] Updates the build system and upgrades to the new dllexport --- .../Forms/ConfigAndGenerate.Designer.cs | 17 ++++++++++- .../Forms/ConfigAndGenerate.cs | 8 ++++- .../Properties/Resources.Designer.cs | 10 +++++++ .../Properties/Resources.resx | 7 +++-- nppRandomStringGenerator/Resources/undo.png | Bin 0 -> 823 bytes .../nppRandomStringGenerator.csproj | 28 +++++++++++++++--- 6 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 nppRandomStringGenerator/Resources/undo.png diff --git a/nppRandomStringGenerator/Forms/ConfigAndGenerate.Designer.cs b/nppRandomStringGenerator/Forms/ConfigAndGenerate.Designer.cs index 15d8837..958e4fb 100644 --- a/nppRandomStringGenerator/Forms/ConfigAndGenerate.Designer.cs +++ b/nppRandomStringGenerator/Forms/ConfigAndGenerate.Designer.cs @@ -74,6 +74,7 @@ private void InitializeComponent() this.ButtonCancel = new System.Windows.Forms.Button(); this.TabControl1 = new System.Windows.Forms.TabControl(); this.TabPageRandom = new System.Windows.Forms.TabPage(); + this.bReset = new System.Windows.Forms.Button(); this.TabPageGUID = new System.Windows.Forms.TabPage(); this.button1 = new System.Windows.Forms.Button(); this.label20 = new System.Windows.Forms.Label(); @@ -611,7 +612,7 @@ private void InitializeComponent() // ButtonCancel // this.ButtonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.ButtonCancel.BackColor = System.Drawing.Color.DarkGray; + this.ButtonCancel.BackColor = System.Drawing.Color.Maroon; this.ButtonCancel.Enabled = false; this.ButtonCancel.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.ButtonCancel.ForeColor = System.Drawing.Color.White; @@ -639,6 +640,7 @@ private void InitializeComponent() // TabPageRandom // this.TabPageRandom.BackColor = System.Drawing.SystemColors.Control; + this.TabPageRandom.Controls.Add(this.bReset); this.TabPageRandom.Controls.Add(this.label1); this.TabPageRandom.Controls.Add(this.NumericUpDownLength); this.TabPageRandom.Controls.Add(this.label16); @@ -676,6 +678,18 @@ private void InitializeComponent() this.TabPageRandom.TabIndex = 0; this.TabPageRandom.Text = "Random strings"; // + // bReset + // + this.bReset.Image = global::nppRandomStringGenerator.Properties.Resources.undo; + this.bReset.Location = new System.Drawing.Point(383, 166); + this.bReset.Margin = new System.Windows.Forms.Padding(0); + this.bReset.Name = "bReset"; + this.bReset.Size = new System.Drawing.Size(24, 24); + this.bReset.TabIndex = 42; + this.toolTip1.SetToolTip(this.bReset, "Restore default symbols"); + this.bReset.UseVisualStyleBackColor = true; + this.bReset.Click += new System.EventHandler(this.bReset_Click); + // // TabPageGUID // this.TabPageGUID.BackColor = System.Drawing.SystemColors.Control; @@ -882,5 +896,6 @@ private void InitializeComponent() private System.Windows.Forms.ComboBox ComboBoxGUIDFormat; private System.Windows.Forms.Label label20; private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button bReset; } } \ No newline at end of file diff --git a/nppRandomStringGenerator/Forms/ConfigAndGenerate.cs b/nppRandomStringGenerator/Forms/ConfigAndGenerate.cs index cf0ecc7..7a58370 100644 --- a/nppRandomStringGenerator/Forms/ConfigAndGenerate.cs +++ b/nppRandomStringGenerator/Forms/ConfigAndGenerate.cs @@ -198,7 +198,8 @@ private async void ButtonGenerate_Click(Object sender, EventArgs e) }; ButtonGenerate.Enabled = false; - if (!RadioButtonInline.Checked) ButtonCancel.Enabled = true; + //if (!RadioButtonInline.Checked) + ButtonCancel.Enabled = true; await Task.Run(() => Generator.GenerateStrings()); @@ -366,5 +367,10 @@ private void button1_Click(object sender, EventArgs e) label20.Text = GuidInfos.FirstOrDefault(c => c.Key == ComboBoxGUIDFormat.Text).Value; label20.Visible = true; } + + private void bReset_Click(object sender, EventArgs e) + { + TextboxSymbols.Text = "!@#$%^&*()_+-=[]{};':,.<>?"; + } } } diff --git a/nppRandomStringGenerator/Properties/Resources.Designer.cs b/nppRandomStringGenerator/Properties/Resources.Designer.cs index cca762f..48f0135 100644 --- a/nppRandomStringGenerator/Properties/Resources.Designer.cs +++ b/nppRandomStringGenerator/Properties/Resources.Designer.cs @@ -108,5 +108,15 @@ internal static string nppRandomStringGeneratorSettings { return ResourceManager.GetString("nppRandomStringGeneratorSettings", resourceCulture); } } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap undo { + get { + object obj = ResourceManager.GetObject("undo", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } } } diff --git a/nppRandomStringGenerator/Properties/Resources.resx b/nppRandomStringGenerator/Properties/Resources.resx index e9c504b..062abdc 100644 --- a/nppRandomStringGenerator/Properties/Resources.resx +++ b/nppRandomStringGenerator/Properties/Resources.resx @@ -121,10 +121,13 @@ ..\Resources\cancel.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\information_button.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Storage\nppRandomStringGeneratorSettings.ini;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 - - ..\Resources\information_button.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\undo.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a \ No newline at end of file diff --git a/nppRandomStringGenerator/Resources/undo.png b/nppRandomStringGenerator/Resources/undo.png new file mode 100644 index 0000000000000000000000000000000000000000..3babbdb992b314cd79b4d7da5960bc6790285297 GIT binary patch literal 823 zcmV-71IYY|P)n$~z$U!ARYjWl|)>N#X z2d6a&Q!0b38u;bZl4L@P#e^#lK4K{P?YIDgLb8b1yD?9=aQ9VH$x=6p7r7BnFoY*{ za1MkblL(J15JfZQXQzQ00#P22FBtBVlqG(z&Y^p{V*Or|1@Hi#M*(T zbyoqPsQ!s77#fXe)C0yCn5Jn0rz%uM1puf&55*XRf8YZg$-=S6Cx4IufU1d1kua!= zim%Zah@m|fpleX%zjL4Eu%N1B1uk{=G#2dZ915H*y$%2gS>BcH&V(WZZwE&p38bBP zYufns(z1WbV>SVDVhXqKKhX%-@??u;EH2A$N{fq~CX)%by1Nyp!=5Xc!8mPWu_`G-gU7))hg{+wjdwx20tM* z6nvGQp4k8Z!c62P)h(3-R>r*5;^EBjiiJAPZ7PJj?%_xet^&7SQpAB&9YR-3&h(7O8$=RE$803jp|fC#|( zb2|WtW--N;I(PjxnuxxS4fQ?+5Csqc5aygmzXLa)TMWo + $(ProgramW6432)\Notepad++ + $(MSBuildProgramFiles32)\Notepad++ Program $(ProgramW6432)\Notepad++\notepad++.exe $(MSBuildProgramFiles32)\Notepad++\notepad++.exe @@ -159,6 +161,9 @@ all + + + @@ -167,9 +172,24 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file
/// This class holds helpers for sending messages defined in the Msgs_h.cs file. It is at the moment @@ -27,20 +82,81 @@ public void FileNew() Win32.SendMessage(PluginBase.nppData._nppHandle, (uint) NppMsg.NPPM_MENUCOMMAND, Unused, NppMenuCmd.IDM_FILE_NEW); } + public void AddToolbarIcon(int funcItemsIndex, toolbarIcons icon) + { + IntPtr pTbIcons = Marshal.AllocHGlobal(Marshal.SizeOf(icon)); + try { + Marshal.StructureToPtr(icon, pTbIcons, false); + _ = Win32.SendMessage( + PluginBase.nppData._nppHandle, + (uint) NppMsg.NPPM_ADDTOOLBARICON, + PluginBase._funcItems.Items[funcItemsIndex]._cmdID, + pTbIcons); + } finally { + Marshal.FreeHGlobal(pTbIcons); + } + } + + public void AddToolbarIcon(int funcItemsIndex, Bitmap icon) + { + var tbi = new toolbarIcons(); + tbi.hToolbarBmp = icon.GetHbitmap(); + AddToolbarIcon(funcItemsIndex, tbi); + } + /// /// Gets the path of the current document. /// public string GetCurrentFilePath() { var path = new StringBuilder(2000); - Win32.SendMessage(PluginBase.nppData._nppHandle, (uint) NppMsg.NPPM_GETFULLCURRENTPATH, 0, path); + Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_GETFULLCURRENTPATH, 0, path); return path.ToString(); } + /// + /// This method incapsulates a common pattern in the Notepad++ API: when + /// you need to retrieve a string, you can first query the buffer size. + /// This method queries the necessary buffer size, allocates the temporary + /// memory, then returns the string retrieved through that buffer. + /// + /// Message ID of the data string to query. + /// String returned by Notepad++. + public string GetString(NppMsg message) + { + int len = Win32.SendMessage( + PluginBase.nppData._nppHandle, + (uint) message, Unused, Unused).ToInt32() + + 1; + var res = new StringBuilder(len); + _ = Win32.SendMessage( + PluginBase.nppData._nppHandle, (uint) message, len, res); + return res.ToString(); + } + + /// The path to the Notepad++ executable. + public string GetNppPath() + => GetString(NppMsg.NPPM_GETNPPDIRECTORY); + + /// The path to the Config folder for plugins. + public string GetPluginConfigPath() + => GetString(NppMsg.NPPM_GETPLUGINSCONFIGDIR); + + /// + /// Open a file for editing in Notepad++, pretty much like using the app's + /// File - Open menu. + /// + /// The path to the file to open. + /// True on success. + public bool OpenFile(string path) + => Win32.SendMessage( + PluginBase.nppData._nppHandle, (uint) NppMsg.NPPM_DOOPEN, Unused, path).ToInt32() + != 0; + /// /// Gets the path of the current document. /// - public unsafe string GetFilePath(int bufferId) + public unsafe string GetFilePath(IntPtr bufferId) { var path = new StringBuilder(2000); Win32.SendMessage(PluginBase.nppData._nppHandle, (uint) NppMsg.NPPM_GETFULLPATHFROMBUFFERID, bufferId, path); @@ -51,7 +167,160 @@ public void SetCurrentLanguage(LangType language) { Win32.SendMessage(PluginBase.nppData._nppHandle, (uint) NppMsg.NPPM_SETCURRENTLANGTYPE, Unused, (int) language); } - } + + /// + /// open a standard save file dialog to save the current file

+ /// Returns true if the file was saved + ///
+ public bool SaveCurrentFile() + { + IntPtr result = Win32.SendMessage(PluginBase.nppData._nppHandle, + (uint)(NppMsg.NPPM_SAVECURRENTFILEAS), + 0, 0); + return result.ToInt32() == 1; + } + + public void HideDockingForm(System.Windows.Forms.Form form) + { + Win32.SendMessage(PluginBase.nppData._nppHandle, + (uint)(NppMsg.NPPM_DMMHIDE), + 0, form.Handle); + } + + public void ShowDockingForm(System.Windows.Forms.Form form) + { + Win32.SendMessage(PluginBase.nppData._nppHandle, + (uint)(NppMsg.NPPM_DMMSHOW), + 0, form.Handle); + } + + public Color GetDefaultForegroundColor() + { + var rawColor = (int)Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_GETEDITORDEFAULTFOREGROUNDCOLOR, 0, 0); + return Color.FromArgb(rawColor & 0xff, (rawColor >> 8) & 0xff, (rawColor >> 16) & 0xff); + } + + public Color GetDefaultBackgroundColor() + { + var rawColor = (int)Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_GETEDITORDEFAULTBACKGROUNDCOLOR, 0, 0); + return Color.FromArgb(rawColor & 0xff, (rawColor >> 8) & 0xff, (rawColor >> 16) & 0xff); + } + + /// + /// Figure out default N++ config file path

+ /// Path is usually -> .\Users\\AppData\Roaming\Notepad++\plugins\config\ + ///
+ public string GetConfigDirectory() + { + var sbIniFilePath = new StringBuilder(Win32.MAX_PATH); + Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_GETPLUGINSCONFIGDIR, Win32.MAX_PATH, sbIniFilePath); + return sbIniFilePath.ToString(); + } + + /// + /// 3-int array: {major, minor, bugfix}

+ /// Thus GetNppVersion() would return {8, 5, 0} for version 8.5.0 + /// and {7, 7, 1} for version 7.7.1 + ///
+ /// + public int[] GetNppVersion() + { + int version = Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_GETNPPVERSION, 0, 0).ToInt32(); + int major = version >> 16; + int minor = Math.DivRem(version & 0xffff, 10, out int bugfix); + if (minor == 0) + (bugfix, minor) = (minor, bugfix); + return new int[] { major, minor, bugfix }; + } + + /// + /// Get all open filenames in both views (all in first view, then all in second view) + /// + /// + public string[] GetOpenFileNames() + { + var bufs = new List(); + foreach (int view in GetVisibleViews()) + { + int nbOpenFiles = (int)Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_GETNBOPENFILES, 0, view + 1); + for (int ii = 0; ii < nbOpenFiles; ii++) + { + IntPtr bufId = Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_GETBUFFERIDFROMPOS, ii, view); + bufs.Add(Npp.notepad.GetFilePath(bufId)); + } + } + return bufs.ToArray(); + } + + public void AddModelessDialog(IntPtr formHandle) + { + Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_MODELESSDIALOG, IntPtr.Zero, formHandle); + } + + public void RemoveModelessDialog(IntPtr formHandle) + { + Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_MODELESSDIALOG, new IntPtr(1), formHandle); + } + + /// + /// the status bar is the bar at the bottom with the document type, EOL type, current position, line, etc.

+ /// Set the message for one of the sections of that bar. + ///
+ /// + /// + public void SetStatusBarSection(string message, StatusBarSection section) + { + Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_SETSTATUSBAR, (int)section, message); + } + + public unsafe bool AllocateIndicators(int numberOfIndicators, out int[] indicators) + { + indicators = null; + if (numberOfIndicators < 1) + return false; + indicators = new int[numberOfIndicators]; + fixed (int * indicatorsPtr = indicators) + { + IntPtr success = Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_ALLOCATEINDICATOR, (IntPtr)numberOfIndicators, (IntPtr)indicatorsPtr); + for (int ii = 1; ii < numberOfIndicators; ii++) + indicators[ii] = indicators[ii - 1] + 1; + return success != IntPtr.Zero; + } + } + + public unsafe bool TryGetNativeLangName(out string langName) + { + langName = ""; + int fnameLen = Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_GETNATIVELANGFILENAME, IntPtr.Zero, IntPtr.Zero).ToInt32() + 1; + if (fnameLen == 1) + return false; + var fnameArr = new byte[fnameLen]; + fixed (byte * fnameBuf = fnameArr) + { + IntPtr fnamePtr = (IntPtr)fnameBuf; + Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_GETNATIVELANGFILENAME, (IntPtr)fnameLen, fnamePtr); + langName = Marshal.PtrToStringAnsi(fnamePtr); + } + if (!string.IsNullOrEmpty(langName) && langName.EndsWith(".xml")) + { + langName = langName.Substring(0, langName.Length - 4); + return true; + } + return false; + } + + public List GetVisibleViews() + { + var openViews = new List(); + for (int view = 0; view < 2; view++) + { + // NPPM_GETCURRENTDOCINDEX(0, view) returns -1 if that view is invisible + if ((int)Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_GETCURRENTDOCINDEX, 0, view) >= 0) + openViews.Add(view); + } + return openViews; + } + } ///
/// This class holds helpers for sending messages defined in the Resource_h.cs file. It is at the moment diff --git a/nppRandomStringGenerator/PluginInfrastructure/NppPluginNETBase.cs b/nppRandomStringGenerator/PluginInfrastructure/NppPluginNETBase.cs index 250a901..7c9d40f 100644 --- a/nppRandomStringGenerator/PluginInfrastructure/NppPluginNETBase.cs +++ b/nppRandomStringGenerator/PluginInfrastructure/NppPluginNETBase.cs @@ -1,5 +1,9 @@ // NPP plugin platform for .Net v0.94.00 by Kasper B. Graversen etc. using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using NppPluginNET.Utils; namespace Kbg.NppPluginNET.PluginInfrastructure { @@ -7,35 +11,192 @@ class PluginBase { internal static NppData nppData; internal static FuncItems _funcItems = new FuncItems(); + private static List _untranslatedFuncItemNames = new List(); + public static List GetUntranslatedFuncItemNames() => _untranslatedFuncItemNames.ToList(); internal static void SetCommand(int index, string commandName, NppFuncItemDelegate functionPointer) { SetCommand(index, commandName, functionPointer, new ShortcutKey(), false); } - + internal static void SetCommand(int index, string commandName, NppFuncItemDelegate functionPointer, ShortcutKey shortcut) { SetCommand(index, commandName, functionPointer, shortcut, false); } - + internal static void SetCommand(int index, string commandName, NppFuncItemDelegate functionPointer, bool checkOnInit) { SetCommand(index, commandName, functionPointer, new ShortcutKey(), checkOnInit); } - + internal static void SetCommand(int index, string commandName, NppFuncItemDelegate functionPointer, ShortcutKey shortcut, bool checkOnInit) { FuncItem funcItem = new FuncItem(); funcItem._cmdID = index; - funcItem._itemName = commandName; + funcItem._itemName = Translator.GetTranslatedMenuItem(commandName); if (functionPointer != null) funcItem._pFunc = new NppFuncItemDelegate(functionPointer); if (shortcut._key != 0) funcItem._pShKey = shortcut; funcItem._init2Check = checkOnInit; + _untranslatedFuncItemNames.Add(commandName); _funcItems.Add(funcItem); } + /// + /// if a menu item (for your plugin's drop-down menu) has a checkmark, check/uncheck it, and call its associated funcId. + /// + /// the index of the menu item of interest + /// whether the menu item should be checked + public static void CheckMenuItem(int funcId, bool isChecked) + { + Win32.CheckMenuItem( + Win32.GetMenu(nppData._nppHandle), + PluginBase._funcItems.Items[funcId]._cmdID, + Win32.MF_BYCOMMAND | (isChecked ? Win32.MF_CHECKED : Win32.MF_UNCHECKED)); + } + + //private static IntPtr GetThisPluginMenuHandle() + //{ + // IntPtr mainMenuHandle = Win32.GetMenu(nppData._nppHandle); + // int pluginMenuIndex = 10; + // IntPtr allPluginsMenuHandle = Win32.GetSubMenu(mainMenuHandle, pluginMenuIndex); + // int itemCount = Win32.GetMenuItemCount(allPluginsMenuHandle); + // if (itemCount < 0) + // return IntPtr.Zero; + // for (int ii = 0; ii < itemCount; ii++) + // { + // var mii = new Win32.MenuItemInfo(Win32.MenuItemMask.MIIM_STRING | Win32.MenuItemMask.MIIM_SUBMENU); + // mii.cch = + // } + //} + + private static IntPtr MenuItemInfoToHGlobal(Win32.MenuItemInfo mii) + { + IntPtr miiPtr = Marshal.AllocHGlobal((int)mii.cbSize); + Marshal.StructureToPtr(mii, miiPtr, false); + return miiPtr; + } + + private static bool SetMenuItemText(IntPtr hMenu, int index, string newText) + { + if (index >= _funcItems.Items.Count || index < 0 || newText is null) + return false; + var mii = new Win32.MenuItemInfo(Win32.MenuItemMask.MIIM_STRING | Win32.MenuItemMask.MIIM_FTYPE); + IntPtr newTextPtr = Marshal.StringToHGlobalAnsi(newText); + mii.dwTypeData = newTextPtr; + IntPtr miiPtr = MenuItemInfoToHGlobal(mii); + bool res = Win32.SetMenuItemInfo(hMenu, (uint)index, true, miiPtr); + Marshal.FreeHGlobal(miiPtr); + Marshal.FreeHGlobal(newTextPtr); + return res; + } + + private static unsafe bool TryGetMenuItemText(IntPtr hMenu, int index, out string str) + { + str = null; + if (index < 0) // we assume the user has already checked the menu item count using Win32.GetMenuItemCount(hMenu) + return false; + var mii = new Win32.MenuItemInfo(Win32.MenuItemMask.MIIM_STRING | Win32.MenuItemMask.MIIM_STATE); + IntPtr miiPtr = MenuItemInfoToHGlobal(mii); + if (Win32.GetMenuItemInfo(hMenu, (uint)index, true, miiPtr)) + { + mii = (Win32.MenuItemInfo)Marshal.PtrToStructure(miiPtr, typeof(Win32.MenuItemInfo)); + mii.cch++; + byte[] textBuffer = new byte[mii.cch]; + fixed (byte * textPtr = textBuffer) + { + IntPtr textPtrSafe = (IntPtr)textPtr; + mii.dwTypeData = textPtrSafe; + Marshal.StructureToPtr(mii, miiPtr, true); + Win32.GetMenuItemInfo(hMenu, (uint)index, true, miiPtr); + str = Marshal.PtrToStringAnsi(textPtrSafe); + } + } + Marshal.FreeHGlobal(miiPtr); + return !(str is null); + } + + private static unsafe bool TryGetSubMenuWithName(IntPtr hMenu, string subMenuName, out IntPtr hSubMenu, out int idSubMenu) + { + idSubMenu = -1; + hSubMenu = IntPtr.Zero; + int itemCount = Win32.GetMenuItemCount(hMenu); + if (itemCount < 0) + return false; + for (int ii = 0; ii < itemCount; ii++) + { + if (!TryGetMenuItemText(hMenu, ii, out string menuItemName)) + return false; + if (menuItemName == subMenuName) + { + idSubMenu = ii; + hSubMenu = Win32.GetSubMenu(hMenu, ii); + return true; + } + } + return false; + } + + private static IntPtr _allPluginsMenuHandle = IntPtr.Zero; + private static int _thisPluginIdxInAllPluginsMenu = -1; + private static IntPtr _thisPluginMenuHandle = IntPtr.Zero; + + /// + /// If allPluginsMenuHandle is a valid menu handle, and this plugin's name is the name of one of the submenus of allPluginsMenuHandle,

+ /// set _allPluginsMenuHandle to allPluginsMenuHandle, and set _thisPluginMenuHandle to the handle of the submenu with the same name as this plugin. + ///
+ /// the menu handle to the Plugins submenu of the Notepad++ main menu + /// + private static unsafe bool TrySetPluginsMenuHandle(IntPtr allPluginsMenuHandle) + { + if (_thisPluginMenuHandle != IntPtr.Zero && _allPluginsMenuHandle != IntPtr.Zero && _thisPluginIdxInAllPluginsMenu >= 0 + && TryGetMenuItemText(_allPluginsMenuHandle, _thisPluginIdxInAllPluginsMenu, out string pluginName) + && pluginName == Main.PluginName) + { + return true; + } + if (!TryGetSubMenuWithName(allPluginsMenuHandle, Main.PluginName, out _thisPluginMenuHandle, out _thisPluginIdxInAllPluginsMenu)) + return false; + _allPluginsMenuHandle = allPluginsMenuHandle; + return true; + } + + /// + /// attempt to change the names of this plugin's menu items to newNames, assuming that allPluginsMenuHandle is the handle of the Plugins submenu of the Notepad++ main menu.

+ /// Returns true if and only if all the menu items could be renamed. + ///
+ /// + /// + /// + public static bool ChangePluginMenuItemNames(IntPtr allPluginsMenuHandle, List newNames) + { + if (newNames.Count != _funcItems.Items.Count || !TrySetPluginsMenuHandle(allPluginsMenuHandle)) + return false; + for (int ii = 0; ii < newNames.Count; ii++) + { + string newName = newNames[ii]; + if (newName != "---" && !SetMenuItemText(_thisPluginMenuHandle, ii, newName)) + return false; + } + return true; + } + + /// + /// if a menu item (for your plugin's drop-down menu) has a checkmark:

+ /// - if it's checked, uncheck it

+ /// - if it's unchecked, check it. + /// Either way, call its associated funcId. + ///
+ /// the index of the menu item of interest + /// whether the menu item is currently checked + internal static void CheckMenuItemToggle(int funcId, ref bool isCurrentlyChecked) + { + // toggle value + isCurrentlyChecked = !isCurrentlyChecked; + CheckMenuItem(funcId, isCurrentlyChecked); + } + internal static IntPtr GetCurrentScintilla() { int curScintilla; diff --git a/nppRandomStringGenerator/PluginInfrastructure/NppPluginNETHelper.cs b/nppRandomStringGenerator/PluginInfrastructure/NppPluginNETHelper.cs index c337fea..362c0f0 100644 --- a/nppRandomStringGenerator/PluginInfrastructure/NppPluginNETHelper.cs +++ b/nppRandomStringGenerator/PluginInfrastructure/NppPluginNETHelper.cs @@ -37,7 +37,13 @@ public ShortcutKey(bool isCtrl, bool isAlt, bool isShift, Keys key) [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct FuncItem { - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + ///
+ /// The maximum number of UTF-16 characters in a FuncItem name.

+ /// This is one less than the number listed
the relevant code, because that is the length of a C string + ///
+ public const int MAX_FUNC_ITEM_NAME_LENGTH = 63; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_FUNC_ITEM_NAME_LENGTH + 1)] // +1 for terminating nul char public string _itemName; public NppFuncItemDelegate _pFunc; public int _cmdID; @@ -175,17 +181,21 @@ public enum DockMgrMsg : uint //nmhdr.hwndFrom = hwndNpp; //nmhdr.IdFrom = ctrlIdNpp; - DMN_DOCK = (DMN_FIRST + 2), - DMN_FLOAT = (DMN_FIRST + 3) - //nmhdr.Code = DWORD(DMN_XXX, int newContainer); - //nmhdr.hwndFrom = hwndNpp; - //nmhdr.IdFrom = ctrlIdNpp; + DMN_DOCK = (DMN_FIRST + 2), + DMN_FLOAT = (DMN_FIRST + 3), + DMN_SWITCHIN = (DMN_FIRST + 4), + DMN_SWITCHOFF = (DMN_FIRST + 5), + DMN_FLOATDROPPED = (DMN_FIRST + 6), + //nmhdr.Code = DWORD(DMN_XXX, int newContainer); + //nmhdr.hwndFrom = hwndNpp; + //nmhdr.IdFrom = ctrlIdNpp; } [StructLayout(LayoutKind.Sequential)] public struct toolbarIcons { - public IntPtr hToolbarBmp; - public IntPtr hToolbarIcon; + public IntPtr hToolbarBmp; // standard icon (color) + public IntPtr hToolbarIcon; // Fluent UI icon (black) + public IntPtr hToolbarIconDarkMode; // Fluent UI icon (white) } } diff --git a/nppRandomStringGenerator/PluginInfrastructure/Preference_h.cs b/nppRandomStringGenerator/PluginInfrastructure/Preference_h.cs index f1d1c2a..403a85f 100644 --- a/nppRandomStringGenerator/PluginInfrastructure/Preference_h.cs +++ b/nppRandomStringGenerator/PluginInfrastructure/Preference_h.cs @@ -4,10 +4,6 @@ // "notepad-plus-plus/scintilla/include/Scintilla.iface" // found at // https://github.com/notepad-plus-plus/notepad-plus-plus/blob/master/scintilla/include/Scintilla.iface -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace NppPluginNET.PluginInfrastructure { diff --git a/nppRandomStringGenerator/PluginInfrastructure/Resource_h.cs b/nppRandomStringGenerator/PluginInfrastructure/Resource_h.cs index 45933bd..89634c4 100644 --- a/nppRandomStringGenerator/PluginInfrastructure/Resource_h.cs +++ b/nppRandomStringGenerator/PluginInfrastructure/Resource_h.cs @@ -4,10 +4,6 @@ // "notepad-plus-plus/scintilla/include/Scintilla.iface" // found at // https://github.com/notepad-plus-plus/notepad-plus-plus/blob/master/scintilla/include/Scintilla.iface -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using Kbg.NppPluginNET.PluginInfrastructure; namespace NppPluginNET.PluginInfrastructure diff --git a/nppRandomStringGenerator/PluginInfrastructure/ScintillaGateway.cs b/nppRandomStringGenerator/PluginInfrastructure/ScintillaGateway.cs index 4483904..f086a52 100644 --- a/nppRandomStringGenerator/PluginInfrastructure/ScintillaGateway.cs +++ b/nppRandomStringGenerator/PluginInfrastructure/ScintillaGateway.cs @@ -20,6 +20,45 @@ public class ScintillaGateway : IScintillaGateway public static readonly int LengthZeroTerminator = "\0".Length; + /// + /// if bytes is null, returns null.

+ /// Else, returns bytes decoded from UTF-8 as a string, with all trailing NULL bytes stripped off. + ///
+ public static string Utf8BytesToNullStrippedString(byte[] bytes) + { + if (bytes is null) + return null; + int lastNullCharPos = bytes.Length - 1; + // this only bypasses NULL chars because no char + // other than NULL can have any 0-valued bytes in UTF-8. + // See https://en.wikipedia.org/wiki/UTF-8#Encoding + for (; lastNullCharPos >= 0 && bytes[lastNullCharPos] == '\x00'; lastNullCharPos--) { } + return Encoding.UTF8.GetString(bytes, 0, lastNullCharPos + 1); + } + + /// + /// Recall that all Scintilla methods have the signature + /// (scintilla* scin, SciMsg msg, void* wParam, void* lParam) -> void*

+ /// Many of these scintilla methods are bimodal in the following way

+ /// * if lParam is 0, return the length of the buffer to be filled and have no side effects. The wParam may be involved in telling Scintilla how big the buffer needs to be.

+ /// * if lParam is greater than 0, it is assumed to be a pointer to a buffer. Now the wParam indicates what the text will need to be.



+ /// This sets lParam to 0 to get the length, allocates a buffer of that length,

+ /// uses the second mode to fill a buffer,

+ /// and returns a string of the UTF8-decoded buffer with all trailing '\x00' chars stripped off. + ///
+ /// message to send + /// another parameter for defining what the buffer should contain + /// + private unsafe string GetNullStrippedStringFromMessageThatReturnsLength(SciMsg msg, IntPtr wParam=default) + { + int length = Win32.SendMessage(scintilla, msg, wParam, (IntPtr)Unused).ToInt32(); + byte[] textBuffer = new byte[length]; + fixed (byte* textPtr = textBuffer) + { + Win32.SendMessage(scintilla, msg, wParam, (IntPtr)textPtr); + return Utf8BytesToNullStrippedString(textBuffer); + } + } public ScintillaGateway(IntPtr scintilla) { @@ -139,9 +178,21 @@ public void ClearDocumentStyle() } /// Returns the number of bytes in the document. (Scintilla feature 2006) - public int GetLength() + public long GetLength() { - return (int)Win32.SendMessage(scintilla, SciMsg.SCI_GETLENGTH, (IntPtr) Unused, (IntPtr) Unused); + return (long)Win32.SendMessage(scintilla, SciMsg.SCI_GETLENGTH, (IntPtr) Unused, (IntPtr) Unused); + } + + public bool TryGetLengthAsInt(out int result) + { + long longRes = GetLength(); + if (longRes > int.MaxValue) + { + result = -1; + return false; + } + result = (int)longRes; + return true; } /// Returns the character byte at the position. (Scintilla feature 2007) @@ -310,14 +361,9 @@ public void SetAnchor(int anchor) /// Result is NUL-terminated. /// (Scintilla feature 2027) ///
- public unsafe string GetCurLine(int length) + public unsafe string GetCurLine() { - byte[] textBuffer = new byte[10000]; - fixed (byte* textPtr = textBuffer) - { - Win32.SendMessage(scintilla, SciMsg.SCI_GETCURLINE, (IntPtr) length, (IntPtr) textPtr); - return Encoding.UTF8.GetString(textBuffer).TrimEnd('\0'); - } + return GetNullStrippedStringFromMessageThatReturnsLength(SciMsg.SCI_GETCURLINE); } /// Retrieve the position of the last correctly styled character. (Scintilla feature 2028) @@ -708,12 +754,7 @@ public int StyleGetSize(int style) ///