diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9ba5eaa..f901b33 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,7 +38,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- this problem only seems to appear after the user has opened a docking form, and maybe not even every time
- bug with calling arg functions on projections - seems like object projections are treated as arrays when calling arg functions on them in some cases?
- issue with treeview closing when a file with a treeview is moved from one view to another
-- `loop()` function used in `s_sub` callbacks is not thread-safe. This doesn't matter right now because RemesPath is single-threaded, but it could matter in the future.
+- `loop()` function used in `s_sub` callbacks is not thread-safe. *This doesn't matter right now* because RemesPath is single-threaded, but it could matter in the future.
- __Known issues with `Select This` and `Select all children` commands in tree view (FIX THESE ASAP)__:
* Empty strings are ignored by `Select this`
* Floating point numbers may not be selected correctly (JSON string may have different length from the number representation in the CSV)
@@ -49,13 +49,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1. Option to customize which [toolbar icons](/docs/README.md#toolbar-icons) are displayed, and their order.
2. New [regex search form](/docs/README.md#regex-search-form) for using treeview to see regex search results in any file
-3. [For loops in RemesPath](/docs/RemesPath.md#for-loopsloop-variables-added-in-v60)
-4. [`bool`, `s_csv` and `s_fa` vectorized arg functions](/docs/RemesPath.md#vectorized-functions) and [`randint`, `csv_regex`, and `to_csv` non-vectorized arg functions](/docs/RemesPath.md#non-vectorized-functions) to RemesPath.
-5. Make second argument of [`s_split` RemesPath function](/docs/RemesPath.md#vectorized-functions) optional; 1-argument variant splits on whitespace.
-6. Right-click dropdown menu in [error form](/docs/README.md#error-form-and-status-bar), allowing export of errors to JSON or refreshing the form.
-7. The parser is now much better at recovering when an object is missing its closing `'}'` or an array is missing its closing `']'`.
-8. Support for [JSON Schema validation](/docs/README.md#validating-json-against-json-schema) of `enum` keyword where the `type` is missing or an array.
-9. `Ctrl+Up` now snaps to parent of currently selected node in tree view. `Ctrl+Down` now snaps to the last direct child of the currently selected node.
+3. New [document type list box in tree view](/docs/README.md#document-type-box-added-in-v60)
+4. [For loops in RemesPath](/docs/RemesPath.md#for-loopsloop-variables-added-in-v60)
+5. [`bool`, `s_csv` and `s_fa` RemesPath vectorized arg functions](/docs/RemesPath.md#vectorized-functions)
+6. [`randint`, `csv_regex`, `set`, and `to_csv` RemesPath non-vectorized arg functions](/docs/RemesPath.md#non-vectorized-functions)
+7. Make second argument of [`s_split` RemesPath function](/docs/RemesPath.md#vectorized-functions) optional; 1-argument variant splits on whitespace.
+8. Right-click dropdown menu in [error form](/docs/README.md#error-form-and-status-bar), allowing export of errors to JSON or refreshing the form.
+9. The parser is now much better at recovering when an object is missing its closing `'}'` or an array is missing its closing `']'`.
+10. Support for [JSON Schema validation](/docs/README.md#validating-json-against-json-schema) of `enum` keyword where the `type` is missing or an array.
+11. `Ctrl+Up` now snaps to parent of currently selected node in tree view. `Ctrl+Down` now snaps to the last direct child of the currently selected node.
### Changed
diff --git a/JsonToolsNppPlugin/Forms/TreeViewer.Designer.cs b/JsonToolsNppPlugin/Forms/TreeViewer.Designer.cs
index b36b371..a8e4a2c 100644
--- a/JsonToolsNppPlugin/Forms/TreeViewer.Designer.cs
+++ b/JsonToolsNppPlugin/Forms/TreeViewer.Designer.cs
@@ -60,12 +60,13 @@ private void InitializeComponent()
this.PythonStylePathItem = new System.Windows.Forms.ToolStripMenuItem();
this.RemesPathStylePathItem = new System.Windows.Forms.ToolStripMenuItem();
this.ToggleSubtreesItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.SelectThisItem = new System.Windows.Forms.ToolStripMenuItem();
this.OpenSortFormItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.SelectAllChildrenItem = new System.Windows.Forms.ToolStripMenuItem();
this.CurrentPathBox = new System.Windows.Forms.TextBox();
this.RefreshButton = new System.Windows.Forms.Button();
this.FindReplaceButton = new System.Windows.Forms.Button();
- this.SelectThisItem = new System.Windows.Forms.ToolStripMenuItem();
- this.SelectAllChildrenItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.DocumentTypeListBox = new System.Windows.Forms.ListBox();
this.NodeRightClickMenu.SuspendLayout();
this.SuspendLayout();
//
@@ -74,10 +75,10 @@ private void InitializeComponent()
this.Tree.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
- this.Tree.Location = new System.Drawing.Point(4, 84);
+ this.Tree.Location = new System.Drawing.Point(4, 99);
this.Tree.Name = "Tree";
- this.Tree.Size = new System.Drawing.Size(457, 346);
- this.Tree.TabIndex = 6;
+ this.Tree.Size = new System.Drawing.Size(457, 331);
+ this.Tree.TabIndex = 8;
this.Tree.BeforeExpand += new System.Windows.Forms.TreeViewCancelEventHandler(this.Tree_BeforeExpand);
this.Tree.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.Tree_AfterSelect);
this.Tree.NodeMouseClick += new System.Windows.Forms.TreeNodeMouseClickEventHandler(this.Tree_NodeMouseClick);
@@ -105,7 +106,7 @@ private void InitializeComponent()
this.QueryBox.Location = new System.Drawing.Point(4, 4);
this.QueryBox.Multiline = true;
this.QueryBox.Name = "QueryBox";
- this.QueryBox.Size = new System.Drawing.Size(203, 74);
+ this.QueryBox.Size = new System.Drawing.Size(203, 89);
this.QueryBox.TabIndex = 0;
this.QueryBox.Text = "@";
this.QueryBox.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.QueryBox_KeyPress);
@@ -163,7 +164,7 @@ private void InitializeComponent()
this.OpenSortFormItem,
this.SelectAllChildrenItem});
this.NodeRightClickMenu.Name = "NodeRightClickMenu";
- this.NodeRightClickMenu.Size = new System.Drawing.Size(268, 200);
+ this.NodeRightClickMenu.Size = new System.Drawing.Size(268, 172);
//
// CopyValueMenuItem
//
@@ -233,6 +234,12 @@ private void InitializeComponent()
this.ToggleSubtreesItem.Size = new System.Drawing.Size(267, 24);
this.ToggleSubtreesItem.Text = "Expand/collapse all subtrees";
//
+ // SelectThisItem
+ //
+ this.SelectThisItem.Name = "SelectThisItem";
+ this.SelectThisItem.Size = new System.Drawing.Size(267, 24);
+ this.SelectThisItem.Text = "Select this";
+ //
// OpenSortFormItem
//
this.OpenSortFormItem.Name = "OpenSortFormItem";
@@ -240,6 +247,13 @@ private void InitializeComponent()
this.OpenSortFormItem.Text = "Sort array...";
this.OpenSortFormItem.Visible = false;
//
+ // SelectAllChildrenItem
+ //
+ this.SelectAllChildrenItem.Name = "SelectAllChildrenItem";
+ this.SelectAllChildrenItem.Size = new System.Drawing.Size(267, 24);
+ this.SelectAllChildrenItem.Text = "Select all children";
+ this.SelectAllChildrenItem.Visible = false;
+ //
// CurrentPathBox
//
this.CurrentPathBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
@@ -248,7 +262,7 @@ private void InitializeComponent()
this.CurrentPathBox.Name = "CurrentPathBox";
this.CurrentPathBox.ReadOnly = true;
this.CurrentPathBox.Size = new System.Drawing.Size(337, 22);
- this.CurrentPathBox.TabIndex = 8;
+ this.CurrentPathBox.TabIndex = 10;
this.CurrentPathBox.TabStop = false;
//
// RefreshButton
@@ -270,30 +284,35 @@ private void InitializeComponent()
this.FindReplaceButton.Location = new System.Drawing.Point(4, 435);
this.FindReplaceButton.Name = "FindReplaceButton";
this.FindReplaceButton.Size = new System.Drawing.Size(114, 23);
- this.FindReplaceButton.TabIndex = 7;
+ this.FindReplaceButton.TabIndex = 9;
this.FindReplaceButton.Text = "Find/replace";
this.FindReplaceButton.UseVisualStyleBackColor = true;
this.FindReplaceButton.Click += new System.EventHandler(this.FindReplaceButton_Click);
this.FindReplaceButton.KeyUp += new System.Windows.Forms.KeyEventHandler(this.TreeViewer_KeyUp);
//
- // SelectThisItem
- //
- this.SelectThisItem.Name = "SelectThisItem";
- this.SelectThisItem.Size = new System.Drawing.Size(267, 24);
- this.SelectThisItem.Text = "Select this";
- //
- // SelectAllChildrenItem
- //
- this.SelectAllChildrenItem.Name = "SelectAllChildrenItem";
- this.SelectAllChildrenItem.Size = new System.Drawing.Size(267, 24);
- this.SelectAllChildrenItem.Text = "Select all children";
- this.SelectAllChildrenItem.Visible = false;
+ // DocumentTypeListBox
+ //
+ this.DocumentTypeListBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+ this.DocumentTypeListBox.FormattingEnabled = true;
+ this.DocumentTypeListBox.ItemHeight = 16;
+ this.DocumentTypeListBox.Items.AddRange(new object[] {
+ "JSON mode",
+ "JSONL mode",
+ "INI mode",
+ "REGEX mode"});
+ this.DocumentTypeListBox.Location = new System.Drawing.Point(220, 69);
+ this.DocumentTypeListBox.Name = "DocumentTypeListBox";
+ this.DocumentTypeListBox.Size = new System.Drawing.Size(130, 20);
+ this.DocumentTypeListBox.TabIndex = 6;
+ this.DocumentTypeListBox.SelectedIndexChanged += new System.EventHandler(this.DocumentTypeListBox_SelectedIndexChanged);
+ this.DocumentTypeListBox.KeyUp += new System.Windows.Forms.KeyEventHandler(this.TreeViewer_KeyUp);
//
// TreeViewer
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(471, 461);
+ this.Controls.Add(this.DocumentTypeListBox);
this.Controls.Add(this.FindReplaceButton);
this.Controls.Add(this.RefreshButton);
this.Controls.Add(this.CurrentPathBox);
@@ -337,5 +356,6 @@ private void InitializeComponent()
private System.Windows.Forms.ToolStripMenuItem OpenSortFormItem;
private System.Windows.Forms.ToolStripMenuItem SelectThisItem;
private System.Windows.Forms.ToolStripMenuItem SelectAllChildrenItem;
+ private System.Windows.Forms.ListBox DocumentTypeListBox;
}
}
\ No newline at end of file
diff --git a/JsonToolsNppPlugin/Forms/TreeViewer.cs b/JsonToolsNppPlugin/Forms/TreeViewer.cs
index a626f3d..c92a22c 100644
--- a/JsonToolsNppPlugin/Forms/TreeViewer.cs
+++ b/JsonToolsNppPlugin/Forms/TreeViewer.cs
@@ -51,6 +51,11 @@ public partial class TreeViewer : Form
///
private bool isExpandingAllSubtrees;
+ ///
+ /// avoid unnecessary parsing if a change in the selected index of the DocumentTypeComboBox was not performed manually (in which case this is true)
+ ///
+ public bool documentTypeIndexChangeWasAutomatic;
+
///
/// If the user performs an undo or redo action,
/// this will be set to true so that the next time the user performs a RemesPath query,
@@ -90,6 +95,9 @@ public TreeViewer(JNode json)
findReplaceForm = null;
csvDelim = '\x00';
csvQuote = '\x00';
+ documentTypeIndexChangeWasAutomatic = true; // avoid parsing twice on initialization
+ SetDocumentTypeListBoxIndex(GetDocumentType());
+ documentTypeIndexChangeWasAutomatic = false;
FormStyle.ApplyStyle(this, Main.settings.use_npp_styling);
}
@@ -149,7 +157,8 @@ private void TreeViewer_KeyUp(object sender, KeyEventArgs e)
else if (e.KeyCode == Keys.Tab)
{
Control next = GetNextControl((Control)sender, !e.Shift);
- while ((next == null) || (!next.TabStop)) next = GetNextControl(next, !e.Shift);
+ while (next is null || !next.TabStop)
+ next = GetNextControl(next, !e.Shift);
next.Focus();
}
else if (sender is TreeView && e.Control)
@@ -158,6 +167,8 @@ private void TreeViewer_KeyUp(object sender, KeyEventArgs e)
if (e.KeyCode == Keys.Up)
{
TreeNode selected = Tree.SelectedNode;
+ if (selected is null)
+ return;
TreeNode parent = selected.Parent;
if (parent is null)
return;
@@ -167,7 +178,7 @@ private void TreeViewer_KeyUp(object sender, KeyEventArgs e)
else if (e.KeyCode == Keys.Down)
{
TreeNode selected = Tree.SelectedNode;
- if (selected.Nodes.Count > 0)
+ if (!(selected is null) && selected.Nodes.Count > 0)
{
if (!selected.IsExpanded)
selected.Expand();
@@ -1149,7 +1160,7 @@ private void RefreshButton_Click(object sender, EventArgs e)
{
shouldRefresh = false;
string cur_fname = Npp.notepad.GetCurrentFilePath();
- (bool _, JNode new_json, bool _, DocumentType _) = Main.TryParseJson();
+ (bool _, JNode new_json, bool _, DocumentType _) = Main.TryParseJson(GetDocumentTypeFromListBox());
if (new_json == null)
return;
fname = cur_fname;
@@ -1173,6 +1184,45 @@ private void TreeViewer_DoubleClick(object sender, EventArgs e)
Npp.notepad.OpenFile(fname);
}
+ public void SetDocumentTypeListBoxIndex(DocumentType documentType)
+ {
+ switch (documentType)
+ {
+ case DocumentType.NONE:
+ case DocumentType.JSON:
+ DocumentTypeListBox.SelectedIndex = 0;
+ break;
+ case DocumentType.JSONL: DocumentTypeListBox.SelectedIndex = 1; break;
+ case DocumentType.INI: DocumentTypeListBox.SelectedIndex = 2; break;
+ case DocumentType.REGEX: DocumentTypeListBox.SelectedIndex = 3; break;
+ default: break;
+ }
+ }
+
+ public DocumentType GetDocumentTypeFromListBox()
+ {
+ switch (DocumentTypeListBox.SelectedIndex)
+ {
+ case 0: return DocumentType.JSON;
+ case 1: return DocumentType.JSONL;
+ case 2: return DocumentType.INI;
+ case 3: return DocumentType.REGEX;
+ default: return DocumentType.NONE;
+ }
+ }
+
+ private void DocumentTypeListBox_SelectedIndexChanged(object sender, EventArgs e)
+ {
+ if (documentTypeIndexChangeWasAutomatic)
+ return;
+ DocumentType newDocumentType = GetDocumentTypeFromListBox();
+ DocumentType oldDocumentType = GetDocumentType();
+ if (oldDocumentType == newDocumentType)
+ return;
+ (_, JNode newJson, _, _) =Main.TryParseJson(newDocumentType);
+ JsonTreePopulate(newJson);
+ }
+
///
/// Just the filename, no directory information.
/// If no fname supplied, gets the relative filename for this TreeViewer's fname.
diff --git a/JsonToolsNppPlugin/Forms/TreeViewer.resx b/JsonToolsNppPlugin/Forms/TreeViewer.resx
index 5439864..bc10a73 100644
--- a/JsonToolsNppPlugin/Forms/TreeViewer.resx
+++ b/JsonToolsNppPlugin/Forms/TreeViewer.resx
@@ -125,7 +125,7 @@
AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w
LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0
ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAADC
- CgAAAk1TRnQBSQFMAgEBCAEAAZgBAQGYAQEBEAEAARABAAT/AQkBAAj/AUIBTQE2AQQGAAE2AQQCAAEo
+ CgAAAk1TRnQBSQFMAgEBCAEAAbABAQGwAQEBEAEAARABAAT/AQkBAAj/AUIBTQE2AQQGAAE2AQQCAAEo
AwABQAMAATADAAEBAQABCAYAAQwYAAGAAgABgAMAAoABAAGAAwABgAEAAYABAAKAAgADwAEAAcAB3AHA
AQAB8AHKAaYBAAEzBQABMwEAATMBAAEzAQACMwIAAxYBAAMcAQADIgEAAykBAANVAQADTQEAA0IBAAM5
AQABgAF8Af8BAAJQAf8BAAGTAQAB1gEAAf8B7AHMAQABxgHWAe8BAAHWAucBAAGQAakBrQIAAf8BMwMA
diff --git a/JsonToolsNppPlugin/JSONTools/JNode.cs b/JsonToolsNppPlugin/JSONTools/JNode.cs
index b8f2374..2e6e207 100644
--- a/JsonToolsNppPlugin/JSONTools/JNode.cs
+++ b/JsonToolsNppPlugin/JSONTools/JNode.cs
@@ -1977,6 +1977,7 @@ public JNode GetQuery()
///
public JNode Evaluate(JNode inp)
{
+ ArgFunction.InitializeGlobals(mutatesInput);
JNode lastStatement = EvaluateStatementsFromStartToEnd(inp, 0, statements.Count);
Reset();
return lastStatement;
@@ -2062,7 +2063,6 @@ private JNode EvaluateStatementsFromStartToEnd(JNode inp, int start, int end)
{
JNode lastStatement = null;
indexInStatements = start;
- ArgFunction.regexSearchResultsShouldBeCached = !mutatesInput;
while (indexInStatements < end)
{
JNode statement = statements[indexInStatements];
diff --git a/JsonToolsNppPlugin/JSONTools/RemesPath.cs b/JsonToolsNppPlugin/JSONTools/RemesPath.cs
index 575c995..66266e4 100644
--- a/JsonToolsNppPlugin/JSONTools/RemesPath.cs
+++ b/JsonToolsNppPlugin/JSONTools/RemesPath.cs
@@ -1198,8 +1198,6 @@ private JNode ParseQuery(List toks)
int pos = 0;
int end = toks.Count;
var context = new JQueryContext();
- ArgFunction.csvDelimiterInLastQuery = '\x00';
- ArgFunction.csvQuoteCharInLastQuery = '\x00';
Dictionary isVarnameFunctionOfInput = DetermineWhichVariablesAreFunctionsOfInput(toks);
while (pos < end)
{
@@ -1243,7 +1241,7 @@ private Dictionary DetermineWhichVariablesAreFunctionsOfInput(List
else
pos = endOfStatement + 1;
}
- ArgFunction.regexSearchResultsShouldBeCached = !containsMutation; // any mutation could potentially mutate values inside the cache, which is very bad
+ ArgFunction.InitializeGlobals(containsMutation);
if (containsMutation)
{
// no variable is safe from the mutation, not even ones that were declared before the mutation expression
diff --git a/JsonToolsNppPlugin/JSONTools/RemesPathFunctions.cs b/JsonToolsNppPlugin/JSONTools/RemesPathFunctions.cs
index 4af4da1..054a54e 100644
--- a/JsonToolsNppPlugin/JSONTools/RemesPathFunctions.cs
+++ b/JsonToolsNppPlugin/JSONTools/RemesPathFunctions.cs
@@ -1084,6 +1084,21 @@ public void PadToMaxArgs(List args)
}
}
+ ///
+ /// there are currently three mutable public global variables that are set in queries:
+ /// regexSearchResultsShouldBeCached, csvDelimiterInLastQuery, and csvQuoteCharInLastQuery.
+ /// These all relate to s_csv and s_fa.
+ /// This is basically a hack, because RemesPath does not currently support deep top-down introspection of a query's AST
+ /// to determine if s_csv or s_fa has been called in a certain way.
+ ///
+ ///
+ public static void InitializeGlobals(bool containsMutation)
+ {
+ regexSearchResultsShouldBeCached = !containsMutation;
+ csvDelimiterInLastQuery = '\x00';
+ csvQuoteCharInLastQuery = '\x00';
+ }
+
#region NON_VECTORIZED_ARG_FUNCTIONS
///
@@ -1691,6 +1706,28 @@ public static JNode Items(List args)
return new JArray(0, its);
}
+ ///
+ /// set(x: array) -> object
+ /// returns an object where the keys are all the stringified unique values in x and the values are all null.
+ ///
+ public static JNode Set(List args)
+ {
+ List arr = ((JArray)args[0]).children;
+ var set = new Dictionary();
+ foreach (JNode child in arr)
+ {
+ var val = child.value;
+ string key = val is string s ? JNode.StrToString(s, false) : child.ToString();
+ set[key] = new JNode();
+ }
+ return new JObject(0, set);
+ }
+
+ ///
+ /// unique(x: array, is_sorted: bool=false) -> array
+ /// returns an array of all the distinct values in x (they must all be scalars).
+ /// If is_sorted, the returned array is sorted. Otherwise the order is random.
+ ///
public static JNode Unique(List args)
{
var itbl = (JArray)args[0];
@@ -1698,6 +1735,8 @@ public static JNode Unique(List args)
var uniq = new HashSet();
foreach (JNode val in itbl.children)
{
+ if ((val.type & Dtype.SCALAR) == 0)
+ throw new RemesPathArgumentException("First argument to unique must be an array of all scalars.", 0, FUNCTIONS["unique"]);
uniq.Add(val.value);
}
var uniq_list = new List();
@@ -2347,7 +2386,7 @@ public static JNode StrFind(List args)
private static readonly int MAX_DOC_SIZE_CACHE_REGEX_SEARCH = IntPtr.Size * 1_250_000;
///
- /// do not cache for any reason. This is usually because the query involves mutation and there is some danger of mutating a value in the cache.
+ /// if false, do not cache for any reason. This is usually because the query involves mutation and there is some danger of mutating a value in the cache.
///
public static bool regexSearchResultsShouldBeCached = true;
@@ -3306,6 +3345,7 @@ public static JNode ObjectsToJNode(object obj)
["randint"] = new ArgFunction(RandomInteger, "randint", Dtype.INT, 1, 2, false, new Dtype[] {Dtype.INT, Dtype.INT}, false),
["range"] = new ArgFunction(Range, "range", Dtype.ARR, 1, 3, false, new Dtype[] {Dtype.INT, Dtype.INT, Dtype.INT}),
["s_join"] = new ArgFunction(StringJoin, "s_join", Dtype.STR, 2, 2, false, new Dtype[] {Dtype.STR, Dtype.ARR}),
+ ["set"] = new ArgFunction(Set, "set", Dtype.OBJ, 1, 1, false, new Dtype[] {Dtype.ARR}),
["sort_by"] = new ArgFunction(SortBy, "sort_by", Dtype.ARR, 2, 3, false, new Dtype[] { Dtype.ARR, Dtype.STR | Dtype.INT | Dtype.FUNCTION, Dtype.BOOL }),
["sorted"] = new ArgFunction(Sorted, "sorted", Dtype.ARR, 1, 2, false, new Dtype[] {Dtype.ARR, Dtype.BOOL}),
["stringify"] = new ArgFunction(Stringify, "stringify", Dtype.STR, 1, 1, false, new Dtype[] { Dtype.ANYTHING }),
diff --git a/JsonToolsNppPlugin/Main.cs b/JsonToolsNppPlugin/Main.cs
index fc5c307..9490795 100644
--- a/JsonToolsNppPlugin/Main.cs
+++ b/JsonToolsNppPlugin/Main.cs
@@ -510,8 +510,7 @@ public static (bool fatal, JNode node, bool usesSelections, DocumentType Documen
if (wasAutotriggered && len > sizeThreshold)
return (false, null, false, DocumentType.NONE);
string text = Npp.editor.GetText(len + 1);
- if (documentType == DocumentType.JSON || documentType == DocumentType.NONE || // if user didn't specify how to parse the document
- (documentType == DocumentType.REGEX && (info == null || info.json == null || info.json.type == Dtype.NULL))) // or user specified REGEX and document was not already parsed
+ if (documentType == DocumentType.NONE) // if user didn't specify how to parse the document
{
// 1. check if the document has a file extension with an associated DocumentType
string fileExtension = Npp.FileExtension().ToLower();
@@ -520,7 +519,7 @@ public static (bool fatal, JNode node, bool usesSelections, DocumentType Documen
documentType = docTypeFromExtension;
// 2. check if the user previously chose a document type, and use that if so.
// this overrides the default for the file extension.
- if (previouslyChosenDocType != DocumentType.NONE && documentType != DocumentType.REGEX)
+ if (previouslyChosenDocType != DocumentType.NONE)
documentType = previouslyChosenDocType;
}
if (documentType == DocumentType.INI)
@@ -574,13 +573,21 @@ public static (bool fatal, JNode node, bool usesSelections, DocumentType Documen
foreach ((int start, int end) in selRanges)
{
string selRange = Npp.GetSlice(start, end);
- JNode subJson = documentType == DocumentType.REGEX ? new JNode(selRange, start) : jsonParser.Parse(selRange);
+ JNode subJson;
+ if (documentType == DocumentType.REGEX)
+ {
+ subJson = new JNode(selRange, 0);
+ }
+ else
+ {
+ subJson = jsonParser.Parse(selRange);
+ lints.AddRange(jsonParser.lint);
+ fatalErrors.Add(jsonParser.fatal);
+ if (errorMessage == null)
+ errorMessage = jsonParser.fatalError?.ToString();
+ }
string key = $"{start},{end}";
obj[key] = subJson;
- lints.AddRange(jsonParser.lint);
- fatalErrors.Add(jsonParser.fatal);
- if (errorMessage == null)
- errorMessage = jsonParser.fatalError?.ToString();
}
}
if (!isRecursion && oneSelRange && SelectionManager.NoSelectionIsValidJson(json, !noTextSelected, fatalErrors))
@@ -627,7 +634,8 @@ public static (bool fatal, JNode node, bool usesSelections, DocumentType Documen
Npp.notepad.SetStatusBarSection($"JSON with fatal errors - {lintCount} errors (Alt-P-J-E to view)",
StatusBarSection.DocType);
}
- else if (!(!info.usesSelections && documentType != DocumentType.JSON && documentType != DocumentType.JSONL)) // only touch status bar if whole doc is parsed as JSON or JSON Lines
+ else if (jsonParser.state < ParserState.FATAL && !(!info.usesSelections && documentType != DocumentType.JSON && documentType != DocumentType.JSONL))
+ // only touch status bar if whole doc is parsed as JSON or JSON Lines, and there are no fatal errors
{
string doctypeDescription;
switch (jsonParser.state)
@@ -648,7 +656,12 @@ public static (bool fatal, JNode node, bool usesSelections, DocumentType Documen
Npp.notepad.SetStatusBarSection(doctypeStatusBarEntry, StatusBarSection.DocType);
}
if (info.tv != null)
+ {
info.tv.json = json;
+ info.tv.documentTypeIndexChangeWasAutomatic = true;
+ info.tv.SetDocumentTypeListBoxIndex(documentType);
+ info.tv.documentTypeIndexChangeWasAutomatic = false;
+ }
info.comments = comments;
jsonFileInfos[activeFname] = info;
return (fatal, json, info.usesSelections, documentType);
diff --git a/JsonToolsNppPlugin/Properties/AssemblyInfo.cs b/JsonToolsNppPlugin/Properties/AssemblyInfo.cs
index f0397d3..a8a7a82 100644
--- a/JsonToolsNppPlugin/Properties/AssemblyInfo.cs
+++ b/JsonToolsNppPlugin/Properties/AssemblyInfo.cs
@@ -29,5 +29,5 @@
// Build Number
// Revision
//
-[assembly: AssemblyVersion("5.8.0.16")]
-[assembly: AssemblyFileVersion("5.8.0.16")]
+[assembly: AssemblyVersion("5.8.0.17")]
+[assembly: AssemblyFileVersion("5.8.0.17")]
diff --git a/JsonToolsNppPlugin/Tests/RemesPathTests.cs b/JsonToolsNppPlugin/Tests/RemesPathTests.cs
index 2b8fd4a..ab05e2d 100644
--- a/JsonToolsNppPlugin/Tests/RemesPathTests.cs
+++ b/JsonToolsNppPlugin/Tests/RemesPathTests.cs
@@ -488,6 +488,7 @@ public static bool Test()
new Query_DesiredResult("to_csv(@.foo[1], `.`)", "\"\\\"3.0\\\"\\r\\n\\\"4.0\\\"\\r\\n\\\"5.0\\\"\""),
new Query_DesiredResult("to_csv(@.foo[:][0],,`\\n`)", "\"0\\n3.0\\n6.0\""),
new Query_DesiredResult("to_csv(@.foo[:]{a: @[1]})", "\"a\\r\\n1\\r\\n4.0\\r\\n7.0\""),
+ new Query_DesiredResult("set(concat(@.foo[0], @.foo[1][:1], j`[\"a\", \"a\"]`))", "{\"0\": null, \"1\": null, \"2\": null, \"3.0\": null, \"a\": null}"),
// ===================== s_csv CSV parser ========================
// 3-column 14 rows, ',' delimiter, CRLF newline, '"' quote character, newline before EOF
new Query_DesiredResult("s_csv(`nums,names,cities\\r\\n" +
diff --git a/JsonToolsNppPlugin/Tests/TestRunner.cs b/JsonToolsNppPlugin/Tests/TestRunner.cs
index c203e1e..b17eebc 100644
--- a/JsonToolsNppPlugin/Tests/TestRunner.cs
+++ b/JsonToolsNppPlugin/Tests/TestRunner.cs
@@ -42,7 +42,7 @@ public static async Task RunAll()
(RemesParserTester.Test, "RemesPath parser and compiler", false, false),
(RemesPathThrowsWhenAppropriateTester.Test, "RemesPath throws errors on bad inputs", false, false),
(RemesPathAssignmentTester.Test, "RemesPath assignment operations", false, false),
- (() => RemesPathFuzzTester.Test(10000, 20), fuzzTestName, false, false),
+ (() => RemesPathFuzzTester.Test(5000, 20), fuzzTestName, false, false),
(RemesPathComplexQueryTester.Test, "multi-statement queries in RemesPath", false, false),
(JsonSchemaMakerTester.Test, "JsonSchema generator", false, false),
diff --git a/JsonToolsNppPlugin/Tests/UserInterfaceTests.cs b/JsonToolsNppPlugin/Tests/UserInterfaceTests.cs
index e734484..8635f87 100644
--- a/JsonToolsNppPlugin/Tests/UserInterfaceTests.cs
+++ b/JsonToolsNppPlugin/Tests/UserInterfaceTests.cs
@@ -15,9 +15,6 @@ public class UserInterfaceTester
private static int lowestFilenameNumberNotUsed = 0;
- // later on tests will be added in a method; we don't want to add them every time the tests are run, only the first time
- public static bool hasRunTestsThisSession = false;
-
public static string lastClipboardValue = null;
private static RemesParser remesParser = new RemesParser();
@@ -279,430 +276,484 @@ public static bool ExecuteFileManipulation(string command, List messages
messages.Add(regexSettingsErrorMsg);
Main.regexSearchForm.SearchButton.PerformClick();
break;
+ case "set_document_type":
+ string newDocumentTypeName = (string)args[0];
+ if (!hasTreeView)
+ {
+ messages.Add($"FAIL: Wanted to set the document type to {newDocumentTypeName} with the treeview's combo box, but the treeview wasn't open");
+ return true;
+ }
+ switch (newDocumentTypeName)
+ {
+ case "NONE":
+ case "JSON":
+ Main.openTreeViewer.SetDocumentTypeListBoxIndex(DocumentType.JSON);
+ break;
+ case "JSONL": Main.openTreeViewer.SetDocumentTypeListBoxIndex(DocumentType.JSONL); break;
+ case "INI": Main.openTreeViewer.SetDocumentTypeListBoxIndex(DocumentType.INI); break;
+ case "REGEX": Main.openTreeViewer.SetDocumentTypeListBoxIndex(DocumentType.REGEX); break;
+ }
+ messages.Add($"Set document type to {newDocumentTypeName} with the treeview's combo box");
+ break;
default:
throw new ArgumentException($"Unrecognized command {command}");
}
return false;
}
- public static List<(string command, object[] args)> testcases = new List<(string command, object[] args)>
+ public static bool Test()
{
- ("overwrite", new object[]{"[1, 2, false]\r\n{\"a\": 3, \"b\": 1.5}\r\n[{\"c\": [null]}, -7]"}), // first line: 0 to 13, second line: 15 to 33, third line: 35 to 54
- ("select", new object[]{new string[]{ "0,13", "15,33", "35,54" } }),
- // when pretty-printed in PPrint style looks like this:
- //[
- // 1,
- // 2,
- // false
- //]
- //{
- // "a": 3,
- // "b": 1.5
- //}
- //[
- // {"c": [null]},
- // -7
- //]
- // first JSON: 0 to 31, second JSON: 33 to 64, third JSON: 66 to 98
- ("pretty_print", new object[]{ }),
- ("compare_text", new object[]{ "[\r\n 1,\r\n 2,\r\n false\r\n]\r\n{\r\n \"a\": 3,\r\n \"b\": 1.5\r\n}\r\n[\r\n {\"c\": [null]},\r\n -7\r\n]"}),
- ("compare_selections", new object[]{new string[]{"0,31", "33,64", "66,98"} }),
- // when compressed looks like this:
- //[1,2,false]
- //{"a":3,"b":1.5}
- //[{"c":[null]},-7]
- // first JSON: 0 to 11, second JSON: 13 to 28, third JSON: 30 to 47
- ("compress", new object[]{}),
- ("compare_text", new object[]{"[1,2,false]\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}),
- ("compare_selections", new object[]{new string[] {"0,11", "13,28", "30,47"} }),
- // TEST THAT TREENODE CLICKS GO TO RIGHT LOCATION
- ("tree_open", new object[]{}),
- ("treenode_click", new object[]{ new string[] { "0,11 : [3]", "2 : false"} }),
- ("compare_selections", new object[]{new string[] {"5,5"} }), // in front of @.`0,11`[2]
- ("treenode_click", new object[]{ new string[] {"30,47 : [2]", "0 : {1}", "c : [1]", "0 : null"} }),
- ("compare_selections", new object[]{new string[] {"37,37"} }), // in front of @.`30,47`[0].c[0]
- ("insert_text", new object[]{11, "\r\n"}),
- ("compare_text", new object[]{"[1,2,false]\r\n\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}),
- // TEST QUERY ONLY SOME SELECTIONS
- ("tree_query", new object[]{"@..g`[bc]`"}),
- ("treenode_click", new object[]{new string[]{"15,30 : [1]", "0 : 1.5"} }),
- ("compare_selections", new object[]{new string[]{"26,26" } }),
- ("treenode_click", new object[]{new string[] {"32,49 : [1]", "0 : [1]", "0 : null"} }),
- ("compare_selections", new object[]{new string[] {"39,39"} }),
- // TEST DELETIONS/INSERTIONS THAT START AND END WITHIN A SINGLE SELECTION
- ("delete_text", new object[]{2, 2}),
- ("compare_text", new object[]{"[1,false]\r\n\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}),
- ("tree_query", new object[]{"@..*[is_num(@)][@ >= 2]"}),
- ("treenode_click", new object[]{new string[] {"0,9 : []"} }),
- ("treenode_click", new object[]{new string[] {"13,28 : [1]", "0 : 3"} }),
- ("compare_selections", new object[]{new string[] {"18,18"} }),
- ("insert_text", new object[]{27, ", \"c\": 0"}), // add new key to second JSON
- ("compare_text", new object[]{"[1,false]\r\n\r\n{\"a\":3,\"b\":1.5, \"c\": 0}\r\n[{\"c\":[null]},-7]"}),
- ("tree_query", new object[]{"@..c"}),
- ("treenode_click", new object[]{new string[] {"13,36 : [1]", "0 : 0"} }),
- ("treenode_click", new object[]{new string[] {"38,55 : [1]"} }),
- // TEST SELECT_EVERY_VALID
- ("overwrite", new object[]{ "Errör 1 [foo]: [1,2,NaN]\r\nWarning 2: {\"ä\":3}\r\nInfo 3 {bar}: [[6,{\"b\":false}],-7]\r\nError 4: \"baz\" \"quz\"\r\nError 5: \"string with no close quote\r\nError 6: not {\"even\" [json" }),
- ("select", new object[]{new string[]{"0,0"} }),
- ("select_every_valid", new object[]{}),
- ("compare_selections", new object[]{new string[] { "16,25", "38,46", "62,82", "93,98", "99,104" } }),
- ("compare_path_to_position", new object[]{22, "[`16,25`][2]"}),
- ("compare_path_to_position", new object[]{101, "[`99,104`]"}),
- ("compare_path_to_position", new object[]{74, "[`62,82`][0][1].b"}),
- // TEST SELECT_EVERY_VALID ON A SUBSET OF THE FILE
- ("select", new object[]{new string[] {"1,46", "93,100", "161,167"} }),
- ("select_every_valid", new object[]{}),
- ("compare_selections", new object[]{new string[] {"16,25", "38,46", "93,98", "161,167"} }),
- // TEST SORT FORM
- ("overwrite", new object[]{"[1,3,2]\r\n[6,5,44]\r\n[\"a\",\"c\",\"boo\"]"}),
- ("select", new object[]{new string[]{"0,0"} }),
- ("select_every_valid", new object[]{}),
- ("compare_selections", new object[]{ new string[] { "0,7", "9,17", "19,34" } }),
- ("sort_form_open", new object[]{}),
- ("sort_form_run", new object[]{".g`^(?!0,)`", true, false, 1, ""}), // sort selections not starting at 0, as strings, ascending
- ("compare_text", new object[]{"[\r\n 1,\r\n 3,\r\n 2\r\n]\r\n[\r\n 44,\r\n 5,\r\n 6\r\n]\r\n[\r\n \"a\",\r\n \"boo\",\r\n \"c\"\r\n]"}),
- ("sort_form_run", new object[]{"", true, true, 3, "s_len(str(@))"}), // sort all arrays biggest to smallest using the length of a node's string representation as key
- ("compare_text", new object[]{"[\r\n 1,\r\n 3,\r\n 2\r\n]\r\n[\r\n 44,\r\n 5,\r\n 6\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}),
- // TEST SELECTING NON-JSON RANGE DOES NOT FORGET SELECTIONS
- ("select", new object[]{new string[] {"8,15"} }),
- ("compress", new object[]{}),
- ("compare_text", new object[]{"[1,3,2]\r\n[44,5,6]\r\n[\"boo\",\"a\",\"c\"]"}),
- // TEST SELECTING JSON (AND JSON CHILDREN) FROM TREEVIEW IN SELECTION-BASED DOC
- ("delete_text", new object[]{20, 5}), // select treenode json and json children with array
- ("insert_text", new object[]{20, "[1, NaN,\"b\"]"}),
- ("compare_text", new object[]{"[1,3,2]\r\n[44,5,6]\r\n[[1, NaN,\"b\"],\"a\",\"c\"]"}),
- ("tree_query", new object[]{"@"}),
- ("treenode_click", new object[]{new string[] {"19,41 : [3]", "0 : [3]"} }),
- ("select_treenode_json", new object[]{}),
- ("compare_selections", new object[]{new string[] {"20,32"} }),
- ("select_treenode_json_children", new object[]{}),
- ("compare_selections", new object[]{new string[] {"21,22", "24,27", "28,31"} }),
- ("delete_text", new object[]{20, 12}), // now select treenode json and json children with object
- ("insert_text", new object[]{20, "{\"a\": [null], \"b\": {}}"}),
- ("tree_query", new object[]{"@"}),
- ("treenode_click", new object[]{new string[] {"19,51 : [3]", "0 : {2}"} }),
- ("select_treenode_json", new object[]{}),
- ("compare_selections", new object[]{new string[] {"20,42"} }),
- ("select_treenode_json_children", new object[]{}),
- ("compare_selections", new object[]{new string[] {"26,32", "39,41"} }),
- ("treenode_click", new object[]{new string[] {"19,51 : [3]", "0 : {2}", "a : [1]", "0 : null"} }), // try selecting treenode json with scalar
- ("select_treenode_json", new object[]{}),
- ("compare_selections", new object[]{new string[] {"27,31"} }),
- ("delete_text", new object[]{20, 22}), // revert to how it was before select-treenode-json tests
- ("insert_text", new object[]{20, "\"boo\""}),
- ("tree_query", new object[]{"@"}),
- ("treenode_click", new object[]{new string[] {"9,17 : [3]"} }), // try selecting json and json children on a different array
- ("select_treenode_json_children", new object[]{}),
- ("compare_selections", new object[]{new string[] {"10,12", "13,14", "15,16"} }),
- ("select_treenode_json", new object[]{}),
- ("compare_selections", new object[]{new string[] {"9,17"} }),
- // TEST FILE WITH ONLY ONE JSON DOCUMENT
- ("file_open", new object[]{1}),
- ("overwrite", new object[]{"[\r\n [\"Я\", 1, \"a\"], // foo\r\n [\"◐\", 2, \"b\"], // bar\r\n [\"ồ\", 3, \"c\"], // baz\r\n [\"ェ\", 4, \"d\"],\r\n [\"草\", 5, \"e\"],\r\n [\"😀\", 6, \"f\"]\r\n]//a"}),
- // TEST PRETTY-PRINT WITH COMMENTS AND TABS
- ("pretty_print", new object[]{true, true}),
- ("compare_text", new object[]{"[\r\n\t[\r\n\t\t\"Я\",\r\n\t\t1,\r\n\t\t\"a\"\r\n\t],\r\n\t// foo\r\n\t[\r\n\t\t\"◐\",\r\n\t\t2,\r\n\t\t\"b\"\r\n\t],\r\n\t// bar\r\n\t[\r\n\t\t\"ồ\",\r\n\t\t3,\r\n\t\t\"c\"\r\n\t],\r\n\t// baz\r\n\t[\r\n\t\t\"ェ\",\r\n\t\t4,\r\n\t\t\"d\"\r\n\t],\r\n\t[\r\n\t\t\"草\",\r\n\t\t5,\r\n\t\t\"e\"\r\n\t],\r\n\t[\r\n\t\t\"😀\",\r\n\t\t6,\r\n\t\t\"f\"\r\n\t]\r\n]\r\n//a\r\n"}),
- // TEST PRETTY-PRINT WITH COMMENTS ONLY
- ("pretty_print", new object[]{false, true}),
- ("compare_text", new object[]{"[\r\n [\r\n \"Я\",\r\n 1,\r\n \"a\"\r\n ],\r\n // foo\r\n [\r\n \"◐\",\r\n 2,\r\n \"b\"\r\n ],\r\n // bar\r\n [\r\n \"ồ\",\r\n 3,\r\n \"c\"\r\n ],\r\n // baz\r\n [\r\n \"ェ\",\r\n 4,\r\n \"d\"\r\n ],\r\n [\r\n \"草\",\r\n 5,\r\n \"e\"\r\n ],\r\n [\r\n \"😀\",\r\n 6,\r\n \"f\"\r\n ]\r\n]\r\n//a\r\n"
-}),
- // TEST PRETTY-PRINT WITH TABS ONLY
- ("pretty_print", new object[]{true}),
- ("compare_text", new object[]{"[\r\n\t[\"Я\", 1, \"a\"],\r\n\t[\"◐\", 2, \"b\"],\r\n\t[\"ồ\", 3, \"c\"],\r\n\t[\"ェ\", 4, \"d\"],\r\n\t[\"草\", 5, \"e\"],\r\n\t[\"😀\", 6, \"f\"]\r\n]"}),
- // TEST TREE ON WHOLE DOCUMENT
- ("overwrite", new object[]{"[\r\n [\"Я\", 1, \"a\"], // foo\r\n [\"◐\", 2, \"b\"], // bar\r\n [\"ồ\", 3, \"c\"], // baz\r\n [\"ェ\", 4, \"d\"],\r\n [\"草\", 5, \"e\"],\r\n [\"😀\", 6, \"f\"]\r\n]"}),
- ("tree_open", new object[]{}),
- ("treenode_click", new object[]{new string[] {"1 : [3]", "0 : \"◐\"" } }),
- ("compare_selections", new object[]{new string[] {"36,36"} }),
- ("compare_path_to_position", new object[]{36, "[1][0]"}),
- ("treenode_click", new object[]{ new string[] { "5 : [3]", "1 : 6" } }),
- ("compare_selections", new object[]{new string[] {"146,146"} }),
- ("compare_path_to_position", new object[]{147, "[5][1]"}),
- ("tree_query", new object[]{"@[:][1] = @ + 3" }), // mutate document
- ("compare_text", new object[]{"[\r\n [\"Я\", 4, \"a\"],\r\n [\"◐\", 5, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n [\"草\", 8, \"e\"],\r\n [\"😀\", 9, \"f\"]\r\n]"}),
- ("compare_path_to_position", new object[]{126, "[5][1]"}),
- // TEST SELECT NON-JSON RANGE AND MAKE SURE WHOLE DOCUMENT STILL PARSED (WHEN NOT IN SELECTION MODE)
- ("select", new object[]{new string[] {"1,7"} }),
- ("compress", new object[]{}),
- ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],[\"草\",8,\"e\"],[\"😀\",9,\"f\"]]"}),
- // TEST SELECTING JSON (AND JSON CHILDREN) FROM TREEVIEW IN SELECTION-BASED DOC
- ("tree_query", new object[]{"@"}),
- ("treenode_click", new object[]{new string[] {"2 : [3]"} }), // try selecting json and json children with array
- ("select_treenode_json_children", new object[]{}),
- ("compare_selections", new object[]{new string[] {"29,34", "35,36", "37,40"} }),
- ("select_treenode_json", new object[]{}),
- ("compare_selections", new object[]{new string[] {"28,41"} }),
- ("treenode_click", new object[]{new string[] {"3 : [3]", "0 : \"ェ\"" } }), // try selecting treenode json with scalar
- ("select_treenode_json", new object[]{}),
- ("compare_selections", new object[]{new string[] {"43,48"} }),
- ("overwrite", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"草\":[8],\"e\":-.5, gor:/**/ '😀'},[\"😀\",9,\"f\"]]"}), // try selecting treenode json and json children with object
- ("tree_query", new object[]{"@"}),
- ("treenode_click", new object[]{new string[] {"4 : {3}"} }),
- ("select_treenode_json", new object[]{}),
- ("compare_selections", new object[]{new string[] {"56,92"} }),
- ("select_treenode_json_children", new object[]{}),
- ("compare_selections", new object[]{new string[] {"63,66", "71,74", "85,91"} }),
- ("select", new object[]{new string[]{"0,0"} }),
- ("tree_query", new object[]{"@![4][@[1] >= 7]"}), // try selecting treenode json children when parent is RemesPath query node
- ("treenode_click", new object[]{new string[]{ } }),
- ("select_treenode_json_children", new object[]{}),
- ("pretty_print", new object[]{}),
- ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\r\n \"ェ\",\r\n 7,\r\n \"d\"\r\n],{\"草\":[8],\"e\":-.5, gor:/**/ '😀'},[\r\n \"😀\",\r\n 9,\r\n \"f\"\r\n]]"}),
- // TEST NON-MUTATING MULTI-STATEMENT QUERIES ON A DOCUMENT THAT DOES NOT USE SELECTIONS
- ("select_whole_doc", new object[]{}),
- ("compress", new object[]{}),
- ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}),
- ("tree_query", new object[]{"var a = @[0];\r\nvar b = @[1];\r\nvar c = @[-1];\r\nvar d = a + b * s_len(str(c));"}),
- ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}),
- ("treenode_click", new object[]{new string[] { "0 : \"Я◐◐\"" } }),
- ("treenode_click", new object[]{new string[] {"1 : 9"} }),
- ("treenode_click", new object[]{new string[] { "2 : \"ab\"" } }),
- ("tree_query", new object[]{"var mod_3s = (@[:][type(@) != object])[:]->append(@, @[1] % 3);\r\n" +
- "var gb_m3 = group_by(mod_3s, -1);\r\n" +
- "gb_m3.`1`"}),
- ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}),
- ("treenode_click", new object[]{new string[] { "0 : [4]", "2 : \"a\"" } }),
- ("treenode_click", new object[]{new string[] { "1 : [4]", "1 : 7" } }),
- // TEST MUTATING MULTI-STATEMENT QUERIES ON A DOCUMENT THAT DOES NOT USE SELECTIONS
- ("tree_query", new object[]{"var a = @[0];\r\n" +
- "var b = @[1];\r\n" +
- "var c = @[-1];\r\n" +
- "var d = a + b * s_len(str(c));\r\n" +
- "a[0] = @ + d[0];\r\nb[1] = @ + c[1];\r\nc[2] = @ + a[0]"}),
- ("compare_text", new object[]{"[\r\n [\"ЯЯ◐◐\", 4, \"a\"],\r\n [\"◐\", 14, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n {\"e\": -0.5, \"gor\": \"😀\", \"草\": [8]},\r\n [\"😀\", 9, \"fЯЯ◐◐\"]\r\n]"}),
- ("treenode_click", new object[]{new string[]{"0 : [3]", "0 : \"ЯЯ◐◐\"" } }),
- ("compare_selections", new object[]{new string[]{ "8,8" } }),
- ("treenode_click", new object[]{new string[] {"1 : [3]", "1 : 14" } }),
- ("compare_selections", new object[]{new string[]{ "44,44" } }),
- ("treenode_click", new object[]{new string[]{"5 : [3]", "2 : \"fЯЯ◐◐\"" } }),
- ("compare_selections", new object[]{new string[]{ "160,160" } }),
- ("tree_query", new object[]{"var bumba = @[1][1]; bumba = @ / 7; bumba"}),
- ("compare_text", new object[]{"[\r\n [\"ЯЯ◐◐\", 4, \"a\"],\r\n [\"◐\", 2.0, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n {\"e\": -0.5, \"gor\": \"😀\", \"草\": [8]},\r\n [\"😀\", 9, \"fЯЯ◐◐\"]\r\n]"}),
- ("treenode_click", new object[]{ new string[] { } }),
- ("compare_selections", new object[]{new string[] {"44,44"} }),
- ("tree_compare_query_result", new object[]{"2.0"}),
- // TEST THAT JAVASCRIPT AND PYTHON COMMENTS AT EOF DON'T CAUSE PROBLEMS
- ("overwrite", new object[]{"\r\n[1,2,\r\n3]#"}),
- ("compress", new object[]{}),
- ("compare_text", new object[]{"[1,2,3]"}),
- ("overwrite", new object[]{"[1,2,3]#"}),
- ("compress", new object[]{}),
- ("compare_text", new object[]{"[1,2,3]"}),
- ("overwrite", new object[]{"[1\r\n ,2,\r\n3]//"}),
- ("compress", new object[]{}),
- ("compare_text", new object[]{"[1,2,3]"}),
- ("overwrite", new object[]{"[1,2,\r\n3]#bar"}),
- ("compress", new object[]{}),
- ("compare_text", new object[]{"[1,2,3]"}),
- ("overwrite", new object[]{"[1,2,-9e15]\r\n//foo"}),
- ("compress", new object[]{}),
- ("compare_text", new object[]{"[1,2,-9E+15]"}),
- // TEST PARSE JSON LINES
- ("overwrite", new object[]{"[1,2,3]\r\n{\"a\": 1, \"b\": [-3,-4]}\r\n-7\r\nfalse"}),
- ("tree_open", new object[]{}), // to close the tree so it can be reopened
- ("tree_open", new object[]{DocumentType.JSONL}),
- ("tree_query", new object[]{"@..* [abs(+@) < 3] = @ / 4"}), // divide values in open range (-3, 3) by 4; this should keep the file in JSON Lines format
- ("compare_text", new object[]{"[0.25,0.5,3]\n{\"a\":0.25,\"b\":[-3,-4]}\n-7\n0.0"}),
- ("tree_query", new object[]{"@"}), // re-parse document
- ("treenode_click", new object[]{new string[] {"3 : 0.0"} }),
- ("compare_selections", new object[]{new string[] {"39,39"} }),
- ("compare_path_to_position", new object[]{32, "[1].b[1]"}),
- // TEST PARSE INI FILE
- ("overwrite", new object[]{"[foồ]\r\n;a\r\nfoo=1\r\n[bar]\r\n bar=2\r\n [дaz]\r\n baz=3\r\n ;b\r\n baz2 = 7 \r\n[quz]\r\nquz=4\r\n;c"}),
- ("tree_open", new object[]{}),
- ("tree_open", new object[]{DocumentType.INI}),
- ("tree_query", new object[]{"@..g`z`"}),
- ("treenode_click", new object[]{new string[] {"0 : {2}", "baz2 : \"7 \""} }),
- ("compare_selections", new object[]{new string[]{"63,63" } }),
- ("compare_path_to_position", new object[]{81, ".quz.quz"}),
- ("tree_query", new object[]{"@.bar.bar = @ * int(@)"}), // edit one value
- ("compare_text", new object[]{"[foồ]\r\n;a\r\nfoo=1\r\n[bar]\r\nbar=22\r\n[дaz]\r\nbaz=3\r\n;b\r\nbaz2=7 \r\n[quz]\r\nquz=4\r\n;c\r\n"}),
- // TEST RUNNING SAME QUERY MULTIPLE TIMES ON SAME INPUT DOES NOT HAVE DIFFERENT RESULTS
- // test when mutating a compile-time constant array
- ("tree_query", new object[]{"var onetwo = j`[1,1]`; onetwo[1] = @ + 1; onetwo"}),
- ("tree_query", new object[]{"var onetwo = j`[1,1]`; onetwo[1] = @ + 1; onetwo"}),
- ("treenode_click", new object[]{new string[] {"1 : 2"} }),
- // test when mutating a compile-time constant loop variable
- ("tree_query", new object[]{"for onetwo = j`[1,1]`; onetwo = @ + 1; end for;"}),
- ("tree_query", new object[]{"for onetwo = j`[1,1]`; onetwo = @ + 1; end for;"}),
- ("treenode_click", new object[]{new string[] {"0 : 2"} }),
- ("treenode_click", new object[]{new string[] {"1 : 2"} }),
- // test when mutating projections (both object and array)
- ("tree_query", new object[]{"var onetwo = @{1, 1}; var mappy = @{a: 1}; onetwo[1] = @ + 1; mappy.a = @ + 1; @{onetwo, mappy}"}),
- ("tree_query", new object[]{"var onetwo = @{1, 1}; var mappy = @{a: 1}; onetwo[1] = @ + 1; mappy.a = @ + 1; @{onetwo, mappy}"}),
- ("treenode_click", new object[]{new string[] {"0 : [2]", "1 : 2"} }),
- ("treenode_click", new object[]{new string[] {"1 : {1}", "a : 2"} }),
- // test when mutating map projection
- ("tree_query", new object[]{"var x = @->j`[-1]`; x[0] = @ + 1; x"}),
- ("tree_query", new object[]{"var x = @->j`[-1]`; x[0] = @ + 1; x"}),
- ("treenode_click", new object[]{new string[] {"0 : 0"} }),
- // TEST ASSIGNMENT OPERATIONS ON MULTIPLE SELECTIONS (back to file with three arrays that were sorted by the sort form)
- ("file_open", new object[]{0}),
- ("tree_query", new object[]{"@[:][is_num(@)] = @ / 4"}),
- ("compare_text", new object[]{"[\r\n 0.25,\r\n 0.75,\r\n 0.5\r\n]\r\n[\r\n 11.0,\r\n 1.25,\r\n 1.5\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}),
- ("treenode_click", new object[]{new string[] {"37,72 : [3]", "1 : 1.25"} }),
- ("compare_selections", new object[]{ new string[] { "55,55" } }),
- // TEST DELETION THAT STARTS BEFORE A SELECTION AND ENDS INSIDE IT
- ("insert_text", new object[]{47, "["}),
- ("delete_text", new object[]{ 35, 12 }),
- ("compare_text", new object[]{"[\r\n 0.25,\r\n 0.75,\r\n 0.5\r\n][0,\r\n 1.25,\r\n 1.5\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}),
- ("compress", new object[]{}),
- ("compare_text", new object[]{"[0.25,0.75,0.5][0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}),
- // TEST INSERTION THAT BEGINS ON THE FIRST CHAR OF A SELECTION
- ("insert_text", new object[]{15, "blah"}),
- ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}),
- ("compress", new object[]{}),
- ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}),
- // TEST DELETION THAT STARTS INSIDE A SELECTION AND ENDS AFTER IT
- ("insert_text", new object[]{26, "]"}),
- ("delete_text", new object[]{27, 7}),
- ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}),
- ("compress", new object[]{}),
- ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}),
- // TEST QUERY THAT PRODUCES OBJECT WITH NON-"START,END" KEYS ON A FILE WITH SELECTIONS
- ("tree_query", new object[]{"j`{\"a\": \"foo\", \"b\\n\": [1, 2]}`"}),
- ("treenode_click", new object[]{new string[] {"a : \"foo\""} }),
- ("treenode_click", new object[]{new string[] {"b\\n : [2]", "1 : 2"} }),
- // TEST MULTI-STATEMENT QUERY THAT DOESN'T MUTATE ON A FILE WITH SELECTIONS
- ("tree_query", new object[]{"var s = str(@);\r\n" +
- "var sl = s_len(s);\r\n" +
- "(s + ` `) * sl"}),
- ("treenode_click", new object[]{new string[] {"27,42 : [3]", "0 : \"boo boo boo \""} }),
- ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}),
- ("tree_query", new object[]{"var this_s = @[:]{@, str(@)};\r\n" +
- "var s_lens = s_len(this_s[:][1]);\r\n" +
- "var min_slen = min(s_lens);\r\n" +
- "var shortest_strs = this_s[s_lens == min_slen][0]"}),
- ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}),
- ("treenode_click", new object[]{ new string[] { "0,15 : [1]", "0 : 0.5"} }),
- ("compare_selections", new object[]{new string[]{"11,11" } }),
- ("treenode_click", new object[]{ new string[] { "27,42 : [2]", "1 : \"c\""} }),
- ("compare_selections", new object[]{new string[]{"38,38" } }),
- // TEST MULTI-STATEMENT QUERY THAT *DOES* MUTATE ON A FILE WITH SELECTIONS
- ("tree_query", new object[]{"var this_s = @[:]{@, str(@)};\r\n" +
- "var s_lens = s_len(this_s[:][1]);\r\n" +
- "var max_slen = max(s_lens);\r\n" +
- "var longest_strs = this_s[s_lens == max_slen][0];\r\n" +
- "longest_strs = str(@) + ` is champion!`;\r\n" +
- "longest_strs"}),
- ("compare_text", new object[]{"[\r\n \"0.25 is champion!\",\r\n \"0.75 is champion!\",\r\n 0.5\r\n]blah[\r\n 0,\r\n \"1.25 is champion!\"\r\n][\r\n \"boo is champion!\",\r\n \"a\",\r\n \"c\"\r\n]"}),
- ("treenode_click", new object[]{ new string[] { "69,106 : [1]", "0 : \"1.25 is champion!\""} }),
- ("treenode_click", new object[]{ new string[] { "106,154 : [1]", "0 : \"boo is champion!\""} }),
- ("tree_compare_query_result", new object[]{"{\"0,65\": [\"0.25 is champion!\", \"0.75 is champion!\"], \"106,154\": [\"boo is champion!\"], \"69,106\": [\"1.25 is champion!\"]}"}),
- // TEST PRINTING SELECTION-BASED FILE WITH TABS
- ("pretty_print", new object[]{true}),
- ("compare_text", new object[]{"[\r\n\t\"0.25 is champion!\",\r\n\t\"0.75 is champion!\",\r\n\t0.5\r\n]blah[\r\n\t0,\r\n\t\"1.25 is champion!\"\r\n][\r\n\t\"boo is champion!\",\r\n\t\"a\",\r\n\t\"c\"\r\n]"}),
- // TEST QUERIES WITH LOOP VARIABLES ON SELECTION-BASED FILE
- ("tree_query", new object[]{// find all the strings in the array, then add the i^th string in the array to the i^th-from-last string in the array
- "var strs = @[:][is_str(@)];\r\n" +
- "var strs_cpy = str(strs);\r\n" +
- // need to iterate through (the i^th string, a *copy* of the i^th string, and a *copy* of the i^th-from-last string),
- // because if we don't we will mutate some strings multiple times due to the in-place nature of mutation
- "for s = zip(strs, strs_cpy, strs_cpy[::-1]);\r\n" +
- " s[0] = s[1] + s[2];\r\n" +
- "end for"
- }),
- ("compare_text", new object[]{"[\r\n \"0.25 is champion!0.75 is champion!\",\r\n \"0.75 is champion!0.25 is champion!\",\r\n 0.5\r\n]blah[\r\n 0,\r\n \"1.25 is champion!1.25 is champion!\"\r\n][\r\n \"boo is champion!c\",\r\n \"aa\",\r\n \"cboo is champion!\"\r\n]"}),
- // TEST REGEX SEARCH FORM (REGEX MODE)
- ("file_open", new object[]{2, "txt"}),
- ("overwrite", new object[]{"\u200a\u202f\n foo: 1\nBA\u042f: \u00a0-3\nbaz: +85"}),
- ("regex_search", new object[]{
- "{\"csv\":false,\"regex\":\"^\\\\x20*([a-zЯ]+):\\\\s+(INT)\",\"ignoreCase\":true,\"fullMatch\":false,\"numCols\":[1]}"
- }),
- ("treenode_click", new object[]{new string[] {"0 : [2]"} }),
- ("compare_selections", new object[]{new string[] {"7,7"} }),
- ("treenode_click", new object[]{new string[] {"1 : [2]", "1 : -3"} }),
- ("compare_selections", new object[]{new string[] {"23,23"} }),
- ("select_treenode_json", new object[]{}),
- ("compare_selections", new object[]{new string[] {"23,25"} }),
- ("treenode_click", new object[]{new string[] {"2 : [2]", "1 : 85"} }),
- ("compare_selections", new object[]{new string[] {"31,31"} }),
- ("regex_search", new object[]{
- "{\"csv\":false,\"regex\":\"^\\\\x20*([a-zЯ]+):\\\\s+(NUMBER)\",\"ignoreCase\":false,\"fullMatch\":true}"
- }),
- ("treenode_click", new object[]{new string[] {"1 : [3]", "0 : \"baz: +85\""} }),
- ("treenode_click", new object[]{new string[] {"0 : [3]", "1 : \"foo\""} }),
- ("treenode_click", new object[]{new string[] {"0 : [3]", "2 : \"1\""} }),
- ("tree_query", new object[]{
- "@ = s_sub(@,\r\n" +
- " g`(?i)^\\x20*([a-zЯ]+):\\s+(INT)`,\r\n" +
- " ifelse(float(@[2]) < 0,\r\n" +
- " str(loop()) + @[0],\r\n" +
- " str(loop()) + `. ` + @[1] + ` (` + str(int(@[2])) + `)`\r\n" +
- " )\r\n" +
- ")"}),
- ("compare_text", new object[]{"\u200a\u202f\n1. foo (1)\n2BA\u042f: \u00a0-3\n3. baz (85)"}),
- // TEST REGEX SEARCH FORM (CSV MODE)
- ("overwrite", new object[]{
- "ab\tc\t😀\tf\r\n" +
- "'fo\nö'\t7\tbar\t-8.5\r\n" +
- "baz\t19\t''\t-4e3\r\n" +
- "'zorq'\t\tkywq\t'.75'"
- }),
- ("regex_search", new object[]
- {
- "{\"csv\":true,\"delim\":\"\\\\t\",\"quote\":\"'\",\"newline\":\"\\r\\n\",\"header\":\"d\",\"nColumns\":4,\"numCols\":[-1,1]}"
- }),
- ("treenode_click", new object[]{new string[]{"0 : {4}", "ab : \"fo\\nö\"" } }),
- ("treenode_click", new object[]{new string[]{"1 : {4}", "c : 19"} }),
- ("treenode_click", new object[]{new string[]{"1 : {4}", "😀 : \"\"" } }),
- ("treenode_click", new object[]{new string[]{"2 : {4}", "f : \".75\""} }),
- ("compare_selections", new object[]{new string[] {"62,62"} }),
- ("regex_search", new object[]
+ List<(string command, object[] args)> testcases = new List<(string command, object[] args)>
{
- "{\"csv\": true, \"delim\": \"\\\\t\", \"quote\": \"'\", \"newline\": \"\\r\\n\", \"header\": 1, \"nColumns\": 4, \"numCols\": [1]}"
- }),
- ("treenode_click", new object[]{new string[] {"0 : [4]", "3 : \"f\""} }),
- ("treenode_click", new object[]{new string[] {"1 : [4]", "1 : 7"} }),
- ("treenode_click", new object[]{new string[] {"2 : [4]", "3 : \"-4e3\""} }),
- ("treenode_click", new object[]{new string[] {"3 : [4]", "0 : \"zorq\""} }),
- // TEST SELECT ALL CHILDREN IN CSV FILE
- ("tree_query", new object[]{"s_csv(@, 4, `\\t`, `\\r\\n`, `'`, h)[:][0]"}),
- ("treenode_click", new object[]{new string[]{ } }), // click root
- ("select_treenode_json_children", new object[]{}),
- ("compare_selections", new object[]{new string[] {"0,2", "13,20", "33,36", "49,55"} }),
- ("treenode_click", new object[]{new string[]{ "1 : \"fo\\nö\"" } }),
- ("select_treenode_json", new object[]{}),
- ("compare_selections", new object[]{new string[] {"13,20"} }),
- // MUTATE CSV FILE WITH QUERY
- ("tree_query", new object[]{
- "var c = s_csv(@, 4, `\\t`, `\\r\\n`, `'`, h, 1);\r\n" +
- "c[:][1][is_num(@)] = @/4;\r\n" +
- "@ = to_csv(c,`,`, `\\n`, `\"`)"}),
- ("compare_text", new object[]{"ab,c,😀,f\n\"fo\nö\",1.75,bar,-8.5\nbaz,4.75,,-4e3\nzorq,,kywq,.75\n"}),
- // TEST REGEX SEARCH FORM (SINGLE-CAPTURE-GROUP CSV AND REGEX MODES)
- ("overwrite", new object[]{" 草1\nェ2\n◐3\n'4"}), // last row has CSV quote char to make sure CSV delimiter is forgotten now that a non-csv search was made
- ("regex_search", new object[]{
- "{\"csv\":false,\"regex\":\"^\\\\S\\\\d\\\\r?$\",\"ignoreCase\":true,\"fullMatch\":false}"
- }),
- ("treenode_click", new object[]{new string[]{ } }), // click root
- ("select_treenode_json_children", new object[]{}),
- ("compare_selections", new object[]{new string[] {"6,10", "11,15", "16,18"} }),
- ("treenode_click", new object[]{new string[] { "1 : \"◐3\"" } }),
- ("compare_selections", new object[]{new string[] {"11,11"} }),
- ("select_treenode_json", new object[]{}),
- ("compare_selections", new object[]{new string[] {"11,15"} }),
- ("regex_search", new object[]{
- "{\"csv\":false,\"regex\":\"^\\\\S(\\\\d)\\\\r?$\",\"ignoreCase\":true,\"fullMatch\":false}" // this time capture just the number after the nonspace
- }),
- ("treenode_click", new object[]{new string[] { "1 : \"3\"" } }),
- ("compare_selections", new object[]{new string[] {"14,14"} }),
- ("regex_search", new object[]
- {
- "{\"csv\": true, \"delim\": \",\", \"quote\": \"'\", \"newline\": \"\\n\", \"header\": 0, \"nColumns\": 1}"
- }),
- ("treenode_click", new object[]{new string[] { "0 : \"ェ2\"" } }),
- ("compare_selections", new object[]{new string[] {"6,6"} }),
- ("regex_search", new object[]
- {
- "{\"csv\": true, \"delim\": \",\", \"quote\": \"'\", \"newline\": 1, \"header\": 2, \"nColumns\": 1}"
- }),
- ("treenode_click", new object[]{new string[] { "0 : {1}", " 草1 : \"ェ2\"" } }),
- ("compare_selections", new object[]{new string[] {"6,6"} }),
- };
+ ("overwrite", new object[]{"[1, 2, false]\r\n{\"a\": 3, \"b\": 1.5}\r\n[{\"c\": [null]}, -7]"}), // first line: 0 to 13, second line: 15 to 33, third line: 35 to 54
+ ("select", new object[]{new string[]{ "0,13", "15,33", "35,54" } }),
+ // when pretty-printed in PPrint style looks like this:
+ //[
+ // 1,
+ // 2,
+ // false
+ //]
+ //{
+ // "a": 3,
+ // "b": 1.5
+ //}
+ //[
+ // {"c": [null]},
+ // -7
+ //]
+ // first JSON: 0 to 31, second JSON: 33 to 64, third JSON: 66 to 98
+ ("pretty_print", new object[]{ }),
+ ("compare_text", new object[]{ "[\r\n 1,\r\n 2,\r\n false\r\n]\r\n{\r\n \"a\": 3,\r\n \"b\": 1.5\r\n}\r\n[\r\n {\"c\": [null]},\r\n -7\r\n]"}),
+ ("compare_selections", new object[]{new string[]{"0,31", "33,64", "66,98"} }),
+ // when compressed looks like this:
+ //[1,2,false]
+ //{"a":3,"b":1.5}
+ //[{"c":[null]},-7]
+ // first JSON: 0 to 11, second JSON: 13 to 28, third JSON: 30 to 47
+ ("compress", new object[]{}),
+ ("compare_text", new object[]{"[1,2,false]\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}),
+ ("compare_selections", new object[]{new string[] {"0,11", "13,28", "30,47"} }),
+ // TEST THAT TREENODE CLICKS GO TO RIGHT LOCATION
+ ("tree_open", new object[]{}),
+ ("treenode_click", new object[]{ new string[] { "0,11 : [3]", "2 : false"} }),
+ ("compare_selections", new object[]{new string[] {"5,5"} }), // in front of @.`0,11`[2]
+ ("treenode_click", new object[]{ new string[] {"30,47 : [2]", "0 : {1}", "c : [1]", "0 : null"} }),
+ ("compare_selections", new object[]{new string[] {"37,37"} }), // in front of @.`30,47`[0].c[0]
+ ("insert_text", new object[]{11, "\r\n"}),
+ ("compare_text", new object[]{"[1,2,false]\r\n\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}),
+ // TEST QUERY ONLY SOME SELECTIONS
+ ("tree_query", new object[]{"@..g`[bc]`"}),
+ ("treenode_click", new object[]{new string[]{"15,30 : [1]", "0 : 1.5"} }),
+ ("compare_selections", new object[]{new string[]{"26,26" } }),
+ ("treenode_click", new object[]{new string[] {"32,49 : [1]", "0 : [1]", "0 : null"} }),
+ ("compare_selections", new object[]{new string[] {"39,39"} }),
+ // TEST DELETIONS/INSERTIONS THAT START AND END WITHIN A SINGLE SELECTION
+ ("delete_text", new object[]{2, 2}),
+ ("compare_text", new object[]{"[1,false]\r\n\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}),
+ ("tree_query", new object[]{"@..*[is_num(@)][@ >= 2]"}),
+ ("treenode_click", new object[]{new string[] {"0,9 : []"} }),
+ ("treenode_click", new object[]{new string[] {"13,28 : [1]", "0 : 3"} }),
+ ("compare_selections", new object[]{new string[] {"18,18"} }),
+ ("insert_text", new object[]{27, ", \"c\": 0"}), // add new key to second JSON
+ ("compare_text", new object[]{"[1,false]\r\n\r\n{\"a\":3,\"b\":1.5, \"c\": 0}\r\n[{\"c\":[null]},-7]"}),
+ ("tree_query", new object[]{"@..c"}),
+ ("treenode_click", new object[]{new string[] {"13,36 : [1]", "0 : 0"} }),
+ ("treenode_click", new object[]{new string[] {"38,55 : [1]"} }),
+ // TEST SELECT_EVERY_VALID
+ ("overwrite", new object[]{ "Errör 1 [foo]: [1,2,NaN]\r\nWarning 2: {\"ä\":3}\r\nInfo 3 {bar}: [[6,{\"b\":false}],-7]\r\nError 4: \"baz\" \"quz\"\r\nError 5: \"string with no close quote\r\nError 6: not {\"even\" [json" }),
+ ("select", new object[]{new string[]{"0,0"} }),
+ ("select_every_valid", new object[]{}),
+ ("compare_selections", new object[]{new string[] { "16,25", "38,46", "62,82", "93,98", "99,104" } }),
+ ("compare_path_to_position", new object[]{22, "[`16,25`][2]"}),
+ ("compare_path_to_position", new object[]{101, "[`99,104`]"}),
+ ("compare_path_to_position", new object[]{74, "[`62,82`][0][1].b"}),
+ // TEST SELECT_EVERY_VALID ON A SUBSET OF THE FILE
+ ("select", new object[]{new string[] {"1,46", "93,100", "161,167"} }),
+ ("select_every_valid", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"16,25", "38,46", "93,98", "161,167"} }),
+ // TEST SORT FORM
+ ("overwrite", new object[]{"[1,3,2]\r\n[6,5,44]\r\n[\"a\",\"c\",\"boo\"]"}),
+ ("select", new object[]{new string[]{"0,0"} }),
+ ("select_every_valid", new object[]{}),
+ ("compare_selections", new object[]{ new string[] { "0,7", "9,17", "19,34" } }),
+ ("sort_form_open", new object[]{}),
+ ("sort_form_run", new object[]{".g`^(?!0,)`", true, false, 1, ""}), // sort selections not starting at 0, as strings, ascending
+ ("compare_text", new object[]{"[\r\n 1,\r\n 3,\r\n 2\r\n]\r\n[\r\n 44,\r\n 5,\r\n 6\r\n]\r\n[\r\n \"a\",\r\n \"boo\",\r\n \"c\"\r\n]"}),
+ ("sort_form_run", new object[]{"", true, true, 3, "s_len(str(@))"}), // sort all arrays biggest to smallest using the length of a node's string representation as key
+ ("compare_text", new object[]{"[\r\n 1,\r\n 3,\r\n 2\r\n]\r\n[\r\n 44,\r\n 5,\r\n 6\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}),
+ // TEST SELECTING NON-JSON RANGE DOES NOT FORGET SELECTIONS
+ ("select", new object[]{new string[] {"8,15"} }),
+ ("compress", new object[]{}),
+ ("compare_text", new object[]{"[1,3,2]\r\n[44,5,6]\r\n[\"boo\",\"a\",\"c\"]"}),
+ // TEST SELECTING JSON (AND JSON CHILDREN) FROM TREEVIEW IN SELECTION-BASED DOC
+ ("delete_text", new object[]{20, 5}), // select treenode json and json children with array
+ ("insert_text", new object[]{20, "[1, NaN,\"b\"]"}),
+ ("compare_text", new object[]{"[1,3,2]\r\n[44,5,6]\r\n[[1, NaN,\"b\"],\"a\",\"c\"]"}),
+ ("tree_query", new object[]{"@"}),
+ ("treenode_click", new object[]{new string[] {"19,41 : [3]", "0 : [3]"} }),
+ ("select_treenode_json", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"20,32"} }),
+ ("select_treenode_json_children", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"21,22", "24,27", "28,31"} }),
+ ("delete_text", new object[]{20, 12}), // now select treenode json and json children with object
+ ("insert_text", new object[]{20, "{\"a\": [null], \"b\": {}}"}),
+ ("tree_query", new object[]{"@"}),
+ ("treenode_click", new object[]{new string[] {"19,51 : [3]", "0 : {2}"} }),
+ ("select_treenode_json", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"20,42"} }),
+ ("select_treenode_json_children", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"26,32", "39,41"} }),
+ ("treenode_click", new object[]{new string[] {"19,51 : [3]", "0 : {2}", "a : [1]", "0 : null"} }), // try selecting treenode json with scalar
+ ("select_treenode_json", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"27,31"} }),
+ ("delete_text", new object[]{20, 22}), // revert to how it was before select-treenode-json tests
+ ("insert_text", new object[]{20, "\"boo\""}),
+ ("tree_query", new object[]{"@"}),
+ ("treenode_click", new object[]{new string[] {"9,17 : [3]"} }), // try selecting json and json children on a different array
+ ("select_treenode_json_children", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"10,12", "13,14", "15,16"} }),
+ ("select_treenode_json", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"9,17"} }),
+ // TEST FILE WITH ONLY ONE JSON DOCUMENT
+ ("file_open", new object[]{1}),
+ ("overwrite", new object[]{"[\r\n [\"Я\", 1, \"a\"], // foo\r\n [\"◐\", 2, \"b\"], // bar\r\n [\"ồ\", 3, \"c\"], // baz\r\n [\"ェ\", 4, \"d\"],\r\n [\"草\", 5, \"e\"],\r\n [\"😀\", 6, \"f\"]\r\n]//a"}),
+ // TEST PRETTY-PRINT WITH COMMENTS AND TABS
+ ("pretty_print", new object[]{true, true}),
+ ("compare_text", new object[]{"[\r\n\t[\r\n\t\t\"Я\",\r\n\t\t1,\r\n\t\t\"a\"\r\n\t],\r\n\t// foo\r\n\t[\r\n\t\t\"◐\",\r\n\t\t2,\r\n\t\t\"b\"\r\n\t],\r\n\t// bar\r\n\t[\r\n\t\t\"ồ\",\r\n\t\t3,\r\n\t\t\"c\"\r\n\t],\r\n\t// baz\r\n\t[\r\n\t\t\"ェ\",\r\n\t\t4,\r\n\t\t\"d\"\r\n\t],\r\n\t[\r\n\t\t\"草\",\r\n\t\t5,\r\n\t\t\"e\"\r\n\t],\r\n\t[\r\n\t\t\"😀\",\r\n\t\t6,\r\n\t\t\"f\"\r\n\t]\r\n]\r\n//a\r\n"}),
+ // TEST PRETTY-PRINT WITH COMMENTS ONLY
+ ("pretty_print", new object[]{false, true}),
+ ("compare_text", new object[]{"[\r\n [\r\n \"Я\",\r\n 1,\r\n \"a\"\r\n ],\r\n // foo\r\n [\r\n \"◐\",\r\n 2,\r\n \"b\"\r\n ],\r\n // bar\r\n [\r\n \"ồ\",\r\n 3,\r\n \"c\"\r\n ],\r\n // baz\r\n [\r\n \"ェ\",\r\n 4,\r\n \"d\"\r\n ],\r\n [\r\n \"草\",\r\n 5,\r\n \"e\"\r\n ],\r\n [\r\n \"😀\",\r\n 6,\r\n \"f\"\r\n ]\r\n]\r\n//a\r\n"
+ }),
+ // TEST PRETTY-PRINT WITH TABS ONLY
+ ("pretty_print", new object[]{true}),
+ ("compare_text", new object[]{"[\r\n\t[\"Я\", 1, \"a\"],\r\n\t[\"◐\", 2, \"b\"],\r\n\t[\"ồ\", 3, \"c\"],\r\n\t[\"ェ\", 4, \"d\"],\r\n\t[\"草\", 5, \"e\"],\r\n\t[\"😀\", 6, \"f\"]\r\n]"}),
+ // TEST TREE ON WHOLE DOCUMENT
+ ("overwrite", new object[]{"[\r\n [\"Я\", 1, \"a\"], // foo\r\n [\"◐\", 2, \"b\"], // bar\r\n [\"ồ\", 3, \"c\"], // baz\r\n [\"ェ\", 4, \"d\"],\r\n [\"草\", 5, \"e\"],\r\n [\"😀\", 6, \"f\"]\r\n]"}),
+ ("tree_open", new object[]{}),
+ ("treenode_click", new object[]{new string[] {"1 : [3]", "0 : \"◐\"" } }),
+ ("compare_selections", new object[]{new string[] {"36,36"} }),
+ ("compare_path_to_position", new object[]{36, "[1][0]"}),
+ ("treenode_click", new object[]{ new string[] { "5 : [3]", "1 : 6" } }),
+ ("compare_selections", new object[]{new string[] {"146,146"} }),
+ ("compare_path_to_position", new object[]{147, "[5][1]"}),
+ ("tree_query", new object[]{"@[:][1] = @ + 3" }), // mutate document
+ ("compare_text", new object[]{"[\r\n [\"Я\", 4, \"a\"],\r\n [\"◐\", 5, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n [\"草\", 8, \"e\"],\r\n [\"😀\", 9, \"f\"]\r\n]"}),
+ ("compare_path_to_position", new object[]{126, "[5][1]"}),
+ // TEST SELECT NON-JSON RANGE AND MAKE SURE WHOLE DOCUMENT STILL PARSED (WHEN NOT IN SELECTION MODE)
+ ("select", new object[]{new string[] {"1,7"} }),
+ ("compress", new object[]{}),
+ ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],[\"草\",8,\"e\"],[\"😀\",9,\"f\"]]"}),
+ // TEST SELECTING JSON (AND JSON CHILDREN) FROM TREEVIEW IN SELECTION-BASED DOC
+ ("tree_query", new object[]{"@"}),
+ ("treenode_click", new object[]{new string[] {"2 : [3]"} }), // try selecting json and json children with array
+ ("select_treenode_json_children", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"29,34", "35,36", "37,40"} }),
+ ("select_treenode_json", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"28,41"} }),
+ ("treenode_click", new object[]{new string[] {"3 : [3]", "0 : \"ェ\"" } }), // try selecting treenode json with scalar
+ ("select_treenode_json", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"43,48"} }),
+ ("overwrite", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"草\":[8],\"e\":-.5, gor:/**/ '😀'},[\"😀\",9,\"f\"]]"}), // try selecting treenode json and json children with object
+ ("tree_query", new object[]{"@"}),
+ ("treenode_click", new object[]{new string[] {"4 : {3}"} }),
+ ("select_treenode_json", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"56,92"} }),
+ ("select_treenode_json_children", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"63,66", "71,74", "85,91"} }),
+ ("select", new object[]{new string[]{"0,0"} }),
+ ("tree_query", new object[]{"@![4][@[1] >= 7]"}), // try selecting treenode json children when parent is RemesPath query node
+ ("treenode_click", new object[]{new string[]{ } }),
+ ("select_treenode_json_children", new object[]{}),
+ ("pretty_print", new object[]{}),
+ ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\r\n \"ェ\",\r\n 7,\r\n \"d\"\r\n],{\"草\":[8],\"e\":-.5, gor:/**/ '😀'},[\r\n \"😀\",\r\n 9,\r\n \"f\"\r\n]]"}),
+ // TEST NON-MUTATING MULTI-STATEMENT QUERIES ON A DOCUMENT THAT DOES NOT USE SELECTIONS
+ ("select_whole_doc", new object[]{}),
+ ("compress", new object[]{}),
+ ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}),
+ ("tree_query", new object[]{"var a = @[0];\r\nvar b = @[1];\r\nvar c = @[-1];\r\nvar d = a + b * s_len(str(c));"}),
+ ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}),
+ ("treenode_click", new object[]{new string[] { "0 : \"Я◐◐\"" } }),
+ ("treenode_click", new object[]{new string[] {"1 : 9"} }),
+ ("treenode_click", new object[]{new string[] { "2 : \"ab\"" } }),
+ ("tree_query", new object[]{"var mod_3s = (@[:][type(@) != object])[:]->append(@, @[1] % 3);\r\n" +
+ "var gb_m3 = group_by(mod_3s, -1);\r\n" +
+ "gb_m3.`1`"}),
+ ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}),
+ ("treenode_click", new object[]{new string[] { "0 : [4]", "2 : \"a\"" } }),
+ ("treenode_click", new object[]{new string[] { "1 : [4]", "1 : 7" } }),
+ // TEST MUTATING MULTI-STATEMENT QUERIES ON A DOCUMENT THAT DOES NOT USE SELECTIONS
+ ("tree_query", new object[]{"var a = @[0];\r\n" +
+ "var b = @[1];\r\n" +
+ "var c = @[-1];\r\n" +
+ "var d = a + b * s_len(str(c));\r\n" +
+ "a[0] = @ + d[0];\r\nb[1] = @ + c[1];\r\nc[2] = @ + a[0]"}),
+ ("compare_text", new object[]{"[\r\n [\"ЯЯ◐◐\", 4, \"a\"],\r\n [\"◐\", 14, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n {\"e\": -0.5, \"gor\": \"😀\", \"草\": [8]},\r\n [\"😀\", 9, \"fЯЯ◐◐\"]\r\n]"}),
+ ("treenode_click", new object[]{new string[]{"0 : [3]", "0 : \"ЯЯ◐◐\"" } }),
+ ("compare_selections", new object[]{new string[]{ "8,8" } }),
+ ("treenode_click", new object[]{new string[] {"1 : [3]", "1 : 14" } }),
+ ("compare_selections", new object[]{new string[]{ "44,44" } }),
+ ("treenode_click", new object[]{new string[]{"5 : [3]", "2 : \"fЯЯ◐◐\"" } }),
+ ("compare_selections", new object[]{new string[]{ "160,160" } }),
+ ("tree_query", new object[]{"var bumba = @[1][1]; bumba = @ / 7; bumba"}),
+ ("compare_text", new object[]{"[\r\n [\"ЯЯ◐◐\", 4, \"a\"],\r\n [\"◐\", 2.0, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n {\"e\": -0.5, \"gor\": \"😀\", \"草\": [8]},\r\n [\"😀\", 9, \"fЯЯ◐◐\"]\r\n]"}),
+ ("treenode_click", new object[]{ new string[] { } }),
+ ("compare_selections", new object[]{new string[] {"44,44"} }),
+ ("tree_compare_query_result", new object[]{"2.0"}),
+ // TEST THAT JAVASCRIPT AND PYTHON COMMENTS AT EOF DON'T CAUSE PROBLEMS
+ ("overwrite", new object[]{"\r\n[1,2,\r\n3]#"}),
+ ("compress", new object[]{}),
+ ("compare_text", new object[]{"[1,2,3]"}),
+ ("overwrite", new object[]{"[1,2,3]#"}),
+ ("compress", new object[]{}),
+ ("compare_text", new object[]{"[1,2,3]"}),
+ ("overwrite", new object[]{"[1\r\n ,2,\r\n3]//"}),
+ ("compress", new object[]{}),
+ ("compare_text", new object[]{"[1,2,3]"}),
+ ("overwrite", new object[]{"[1,2,\r\n3]#bar"}),
+ ("compress", new object[]{}),
+ ("compare_text", new object[]{"[1,2,3]"}),
+ ("overwrite", new object[]{"[1,2,-9e15]\r\n//foo"}),
+ ("compress", new object[]{}),
+ ("compare_text", new object[]{"[1,2,-9E+15]"}),
+ // TEST PARSE JSON LINES
+ ("overwrite", new object[]{"[1,2,3]\r\n{\"a\": 1, \"b\": [-3,-4]}\r\n-7\r\nfalse"}),
+ ("tree_open", new object[]{}), // to close the tree so it can be reopened
+ ("tree_open", new object[]{DocumentType.JSONL}),
+ ("tree_query", new object[]{"@..* [abs(+@) < 3] = @ / 4"}), // divide values in open range (-3, 3) by 4; this should keep the file in JSON Lines format
+ ("compare_text", new object[]{"[0.25,0.5,3]\n{\"a\":0.25,\"b\":[-3,-4]}\n-7\n0.0"}),
+ ("tree_query", new object[]{"@"}), // re-parse document
+ ("treenode_click", new object[]{new string[] {"3 : 0.0"} }),
+ ("compare_selections", new object[]{new string[] {"39,39"} }),
+ ("compare_path_to_position", new object[]{32, "[1].b[1]"}),
+ // TEST PARSE INI FILE
+ ("overwrite", new object[]{"[foồ]\r\n;a\r\nfoo=1\r\n[bar]\r\n bar=2\r\n [дaz]\r\n baz=3\r\n ;b\r\n baz2 = 7 \r\n[quz]\r\nquz=4\r\n;c"}),
+ ("set_document_type", new object[]{"INI"}),
+ ("tree_query", new object[]{"@..g`z`"}),
+ ("treenode_click", new object[]{new string[] {"0 : {2}", "baz2 : \"7 \""} }),
+ ("compare_selections", new object[]{new string[]{"63,63" } }),
+ ("compare_path_to_position", new object[]{81, ".quz.quz"}),
+ ("tree_query", new object[]{"@.bar.bar = @ * int(@)"}), // edit one value
+ ("compare_text", new object[]{"[foồ]\r\n;a\r\nfoo=1\r\n[bar]\r\nbar=22\r\n[дaz]\r\nbaz=3\r\n;b\r\nbaz2=7 \r\n[quz]\r\nquz=4\r\n;c\r\n"}),
+ // TEST RUNNING SAME QUERY MULTIPLE TIMES ON SAME INPUT DOES NOT HAVE DIFFERENT RESULTS
+ // test when mutating a compile-time constant array
+ ("tree_query", new object[]{"var onetwo = j`[1,1]`; onetwo[1] = @ + 1; onetwo"}),
+ ("tree_query", new object[]{"var onetwo = j`[1,1]`; onetwo[1] = @ + 1; onetwo"}),
+ ("treenode_click", new object[]{new string[] {"1 : 2"} }),
+ // test when mutating a compile-time constant loop variable
+ ("tree_query", new object[]{"for onetwo = j`[1,1]`; onetwo = @ + 1; end for;"}),
+ ("tree_query", new object[]{"for onetwo = j`[1,1]`; onetwo = @ + 1; end for;"}),
+ ("treenode_click", new object[]{new string[] {"0 : 2"} }),
+ ("treenode_click", new object[]{new string[] {"1 : 2"} }),
+ // test when mutating projections (both object and array)
+ ("tree_query", new object[]{"var onetwo = @{1, 1}; var mappy = @{a: 1}; onetwo[1] = @ + 1; mappy.a = @ + 1; @{onetwo, mappy}"}),
+ ("tree_query", new object[]{"var onetwo = @{1, 1}; var mappy = @{a: 1}; onetwo[1] = @ + 1; mappy.a = @ + 1; @{onetwo, mappy}"}),
+ ("treenode_click", new object[]{new string[] {"0 : [2]", "1 : 2"} }),
+ ("treenode_click", new object[]{new string[] {"1 : {1}", "a : 2"} }),
+ // test when mutating map projection
+ ("tree_query", new object[]{"var x = @->j`[-1]`; x[0] = @ + 1; x"}),
+ ("tree_query", new object[]{"var x = @->j`[-1]`; x[0] = @ + 1; x"}),
+ ("treenode_click", new object[]{new string[] {"0 : 0"} }),
+ // TEST ASSIGNMENT OPERATIONS ON MULTIPLE SELECTIONS (back to file with three arrays that were sorted by the sort form)
+ ("file_open", new object[]{0}),
+ ("tree_query", new object[]{"@[:][is_num(@)] = @ / 4"}),
+ ("compare_text", new object[]{"[\r\n 0.25,\r\n 0.75,\r\n 0.5\r\n]\r\n[\r\n 11.0,\r\n 1.25,\r\n 1.5\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}),
+ ("treenode_click", new object[]{new string[] {"37,72 : [3]", "1 : 1.25"} }),
+ ("compare_selections", new object[]{ new string[] { "55,55" } }),
+ // TEST DELETION THAT STARTS BEFORE A SELECTION AND ENDS INSIDE IT
+ ("insert_text", new object[]{47, "["}),
+ ("delete_text", new object[]{ 35, 12 }),
+ ("compare_text", new object[]{"[\r\n 0.25,\r\n 0.75,\r\n 0.5\r\n][0,\r\n 1.25,\r\n 1.5\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}),
+ ("compress", new object[]{}),
+ ("compare_text", new object[]{"[0.25,0.75,0.5][0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}),
+ // TEST INSERTION THAT BEGINS ON THE FIRST CHAR OF A SELECTION
+ ("insert_text", new object[]{15, "blah"}),
+ ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}),
+ ("compress", new object[]{}),
+ ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}),
+ // TEST DELETION THAT STARTS INSIDE A SELECTION AND ENDS AFTER IT
+ ("insert_text", new object[]{26, "]"}),
+ ("delete_text", new object[]{27, 7}),
+ ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}),
+ ("compress", new object[]{}),
+ ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}),
+ // TEST QUERY THAT PRODUCES OBJECT WITH NON-"START,END" KEYS ON A FILE WITH SELECTIONS
+ ("tree_query", new object[]{"j`{\"a\": \"foo\", \"b\\n\": [1, 2]}`"}),
+ ("treenode_click", new object[]{new string[] {"a : \"foo\""} }),
+ ("treenode_click", new object[]{new string[] {"b\\n : [2]", "1 : 2"} }),
+ // TEST MULTI-STATEMENT QUERY THAT DOESN'T MUTATE ON A FILE WITH SELECTIONS
+ ("tree_query", new object[]{"var s = str(@);\r\n" +
+ "var sl = s_len(s);\r\n" +
+ "(s + ` `) * sl"}),
+ ("treenode_click", new object[]{new string[] {"27,42 : [3]", "0 : \"boo boo boo \""} }),
+ ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}),
+ ("tree_query", new object[]{"var this_s = @[:]{@, str(@)};\r\n" +
+ "var s_lens = s_len(this_s[:][1]);\r\n" +
+ "var min_slen = min(s_lens);\r\n" +
+ "var shortest_strs = this_s[s_lens == min_slen][0]"}),
+ ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}),
+ ("treenode_click", new object[]{ new string[] { "0,15 : [1]", "0 : 0.5"} }),
+ ("compare_selections", new object[]{new string[]{"11,11" } }),
+ ("treenode_click", new object[]{ new string[] { "27,42 : [2]", "1 : \"c\""} }),
+ ("compare_selections", new object[]{new string[]{"38,38" } }),
+ // TEST MULTI-STATEMENT QUERY THAT *DOES* MUTATE ON A FILE WITH SELECTIONS
+ ("tree_query", new object[]{"var this_s = @[:]{@, str(@)};\r\n" +
+ "var s_lens = s_len(this_s[:][1]);\r\n" +
+ "var max_slen = max(s_lens);\r\n" +
+ "var longest_strs = this_s[s_lens == max_slen][0];\r\n" +
+ "longest_strs = str(@) + ` is champion!`;\r\n" +
+ "longest_strs"}),
+ ("compare_text", new object[]{"[\r\n \"0.25 is champion!\",\r\n \"0.75 is champion!\",\r\n 0.5\r\n]blah[\r\n 0,\r\n \"1.25 is champion!\"\r\n][\r\n \"boo is champion!\",\r\n \"a\",\r\n \"c\"\r\n]"}),
+ ("treenode_click", new object[]{ new string[] { "69,106 : [1]", "0 : \"1.25 is champion!\""} }),
+ ("treenode_click", new object[]{ new string[] { "106,154 : [1]", "0 : \"boo is champion!\""} }),
+ ("tree_compare_query_result", new object[]{"{\"0,65\": [\"0.25 is champion!\", \"0.75 is champion!\"], \"106,154\": [\"boo is champion!\"], \"69,106\": [\"1.25 is champion!\"]}"}),
+ // TEST PRINTING SELECTION-BASED FILE WITH TABS
+ ("pretty_print", new object[]{true}),
+ ("compare_text", new object[]{"[\r\n\t\"0.25 is champion!\",\r\n\t\"0.75 is champion!\",\r\n\t0.5\r\n]blah[\r\n\t0,\r\n\t\"1.25 is champion!\"\r\n][\r\n\t\"boo is champion!\",\r\n\t\"a\",\r\n\t\"c\"\r\n]"}),
+ // TEST QUERIES WITH LOOP VARIABLES ON SELECTION-BASED FILE
+ ("tree_query", new object[]{// find all the strings in the array, then add the i^th string in the array to the i^th-from-last string in the array
+ "var strs = @[:][is_str(@)];\r\n" +
+ "var strs_cpy = str(strs);\r\n" +
+ // need to iterate through (the i^th string, a *copy* of the i^th string, and a *copy* of the i^th-from-last string),
+ // because if we don't we will mutate some strings multiple times due to the in-place nature of mutation
+ "for s = zip(strs, strs_cpy, strs_cpy[::-1]);\r\n" +
+ " s[0] = s[1] + s[2];\r\n" +
+ "end for"
+ }),
+ ("compare_text", new object[]{"[\r\n \"0.25 is champion!0.75 is champion!\",\r\n \"0.75 is champion!0.25 is champion!\",\r\n 0.5\r\n]blah[\r\n 0,\r\n \"1.25 is champion!1.25 is champion!\"\r\n][\r\n \"boo is champion!c\",\r\n \"aa\",\r\n \"cboo is champion!\"\r\n]"}),
+ // TEST REGEX SEARCH FORM (REGEX MODE)
+ ("file_open", new object[]{2, "txt"}),
+ ("overwrite", new object[]{"\u200a\u202f\n foo: 1\nBA\u042f: \u00a0-3\nbaz: +85"}),
+ ("regex_search", new object[]{
+ "{\"csv\":false,\"regex\":\"^\\\\x20*([a-zЯ]+):\\\\s+(INT)\",\"ignoreCase\":true,\"fullMatch\":false,\"numCols\":[1]}"
+ }),
+ ("treenode_click", new object[]{new string[] {"0 : [2]"} }),
+ ("compare_selections", new object[]{new string[] {"7,7"} }),
+ ("treenode_click", new object[]{new string[] {"1 : [2]", "1 : -3"} }),
+ ("compare_selections", new object[]{new string[] {"23,23"} }),
+ ("select_treenode_json", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"23,25"} }),
+ ("treenode_click", new object[]{new string[] {"2 : [2]", "1 : 85"} }),
+ ("compare_selections", new object[]{new string[] {"31,31"} }),
+ ("regex_search", new object[]{
+ "{\"csv\":false,\"regex\":\"^\\\\x20*([a-zЯ]+):\\\\s+(NUMBER)\",\"ignoreCase\":false,\"fullMatch\":true}"
+ }),
+ ("treenode_click", new object[]{new string[] {"1 : [3]", "0 : \"baz: +85\""} }),
+ ("treenode_click", new object[]{new string[] {"0 : [3]", "1 : \"foo\""} }),
+ ("treenode_click", new object[]{new string[] {"0 : [3]", "2 : \"1\""} }),
+ ("tree_query", new object[]{
+ "@ = s_sub(@,\r\n" +
+ " g`(?i)^\\x20*([a-zЯ]+):\\s+(INT)`,\r\n" +
+ " ifelse(float(@[2]) < 0,\r\n" +
+ " str(loop()) + @[0],\r\n" +
+ " str(loop()) + `. ` + @[1] + ` (` + str(int(@[2])) + `)`\r\n" +
+ " )\r\n" +
+ ")"}),
+ ("compare_text", new object[]{"\u200a\u202f\n1. foo (1)\n2BA\u042f: \u00a0-3\n3. baz (85)"}),
+ // TEST REGEX SEARCH FORM (CSV MODE)
+ ("overwrite", new object[]{
+ "ab\tc\t😀\tf\r\n" +
+ "'fo\nö'\t7\tbar\t-8.5\r\n" +
+ "baz\t19\t''\t-4e3\r\n" +
+ "'zorq'\t\tkywq\t'.75'"
+ }),
+ ("regex_search", new object[]
+ {
+ "{\"csv\":true,\"delim\":\"\\\\t\",\"quote\":\"'\",\"newline\":\"\\r\\n\",\"header\":\"d\",\"nColumns\":4,\"numCols\":[-1,1]}"
+ }),
+ ("treenode_click", new object[]{new string[]{"0 : {4}", "ab : \"fo\\nö\"" } }),
+ ("treenode_click", new object[]{new string[]{"1 : {4}", "c : 19"} }),
+ ("treenode_click", new object[]{new string[]{"1 : {4}", "😀 : \"\"" } }),
+ ("treenode_click", new object[]{new string[]{"2 : {4}", "f : \".75\""} }),
+ ("compare_selections", new object[]{new string[] {"62,62"} }),
+ ("regex_search", new object[]
+ {
+ "{\"csv\": true, \"delim\": \"\\\\t\", \"quote\": \"'\", \"newline\": \"\\r\\n\", \"header\": 1, \"nColumns\": 4, \"numCols\": [1]}"
+ }),
+ ("treenode_click", new object[]{new string[] {"0 : [4]", "3 : \"f\""} }),
+ ("treenode_click", new object[]{new string[] {"1 : [4]", "1 : 7"} }),
+ ("treenode_click", new object[]{new string[] {"2 : [4]", "3 : \"-4e3\""} }),
+ ("treenode_click", new object[]{new string[] {"3 : [4]", "0 : \"zorq\""} }),
+ // TEST SELECT ALL CHILDREN IN CSV FILE
+ ("tree_query", new object[]{"s_csv(@, 4, `\\t`, `\\r\\n`, `'`, h)[:][0]"}),
+ ("treenode_click", new object[]{new string[]{ } }), // click root
+ ("select_treenode_json_children", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"0,2", "13,20", "33,36", "49,55"} }),
+ ("treenode_click", new object[]{new string[]{ "1 : \"fo\\nö\"" } }),
+ ("select_treenode_json", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"13,20"} }),
+ // MUTATE CSV FILE WITH QUERY
+ ("tree_query", new object[]{
+ "var c = s_csv(@, 4, `\\t`, `\\r\\n`, `'`, h, 1);\r\n" +
+ "c[:][1][is_num(@)] = @/4;\r\n" +
+ "@ = to_csv(c,`,`, `\\n`, `\"`)"}),
+ ("compare_text", new object[]{"ab,c,😀,f\n\"fo\nö\",1.75,bar,-8.5\nbaz,4.75,,-4e3\nzorq,,kywq,.75\n"}),
+ // TEST REGEX SEARCH FORM (SINGLE-CAPTURE-GROUP CSV AND REGEX MODES)
+ ("overwrite", new object[]{" 草1\nェ2\n◐3\n'4"}), // last row has CSV quote char to make sure CSV delimiter is forgotten now that a non-csv search was made
+ ("regex_search", new object[]{
+ "{\"csv\":false,\"regex\":\"^\\\\S\\\\d\\\\r?$\",\"ignoreCase\":true,\"fullMatch\":false}"
+ }),
+ ("treenode_click", new object[]{new string[]{ } }), // click root
+ ("select_treenode_json_children", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"6,10", "11,15", "16,18"} }),
+ ("treenode_click", new object[]{new string[] { "1 : \"◐3\"" } }),
+ ("compare_selections", new object[]{new string[] {"11,11"} }),
+ ("select_treenode_json", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"11,15"} }),
+ ("regex_search", new object[]{
+ "{\"csv\":false,\"regex\":\"^\\\\S(\\\\d)\\\\r?$\",\"ignoreCase\":true,\"fullMatch\":false}" // this time capture just the number after the nonspace
+ }),
+ ("treenode_click", new object[]{new string[] { "1 : \"3\"" } }),
+ ("compare_selections", new object[]{new string[] {"14,14"} }),
+ ("regex_search", new object[]
+ {
+ "{\"csv\": true, \"delim\": \",\", \"quote\": \"'\", \"newline\": \"\\n\", \"header\": 0, \"nColumns\": 1}"
+ }),
+ ("treenode_click", new object[]{new string[] { "0 : \"ェ2\"" } }),
+ ("compare_selections", new object[]{new string[] {"6,6"} }),
+ ("regex_search", new object[]
+ {
+ "{\"csv\": true, \"delim\": \",\", \"quote\": \"'\", \"newline\": 1, \"header\": 2, \"nColumns\": 1}"
+ }),
+ ("treenode_click", new object[]{new string[] { "0 : {1}", " 草1 : \"ェ2\"" } }),
+ ("compare_selections", new object[]{new string[] {"6,6"} }),
+ // TEST REGEX SEARCH ON FILE WITH MULTIPLE SELECTIONS
+ ("overwrite", new object[]{"1 2\r[3]ö4\r\"\u00a0\r5 6\r'foo'"}),
+ ("tree_query", new object[]{"s_csv(@, 1, `,`, `\\r`, `\"`, h)"}),
+ ("treenode_click", new object[]{new string[] {} }),
+ ("select_treenode_json_children", new object[]{}),
+ ("compare_selections", new object[]{new string[] {"0,3", "4,10", "15,18", "19,24"} }),
+ ("tree_open", new object[]{}),
+ ("tree_open", new object[]{}),
+ ("set_document_type", new object[]{ "REGEX" }), // re-parse document in multi-selection mode
+ ("treenode_click", new object[]{new string[]{ "15,18 : \"5 6\"" } }),
+ ("compare_selections", new object[]{new string[] {"15,15"} }),
+ ("tree_query", new object[]{"s_fa(@, g`\\d+`,, 0)"}),
+ ("treenode_click", new object[]{ new string[] { "4,10 : [2]", "1 : 4" } }),
+ ("select_treenode_json", new object[]{}),
+ ("compare_selections", new object[]{new string[]{"9,10" } }),
+ ("treenode_click", new object[]{ new string[] { "15,18 : [2]" } }),
+ ("select_treenode_json_children", new object[]{}),
+ ("compare_selections", new object[]{new string[]{"15,16", "17,18" } }),
+ // TEST REGEX EDITING ON FILE WITH MULTIPLE SELECTIONS
+ ("select", new object[]{new string[] {"0,0"} }),
+ ("tree_query", new object[]{ "@ = s_sub(@, g`(INT)`, str(int(@[0])->ifelse(@ > 3, -@, @)))" }),
+ ("compare_text", new object[]{"1 2\r[3]ö-4\r\"\u00a0\r-5 -6\r'foo'" }),
+ ("compare_selections", new object[]{new string[] {"0,3", "4,11", "16,21", "22,27"} }),
+ // TEST DOCUMENT TYPE CHANGE BUTTON IN TREEVIEW
+ ("overwrite", new object[]{"[1, 2]\r\n{\"ö\": \"3 4\"}\r\n\"\"\r\n{\"5\": [6]}\r\n'foo'"}),
+ ("set_document_type", new object[]{"JSON"}),
+ ("treenode_click", new object[]{new string[] {"0 : 1"} }),
+ ("treenode_click", new object[]{new string[] {"1 : 2"} }),
+ ("set_document_type", new object[]{"JSONL"}),
+ ("treenode_click", new object[]{new string[] {"3 : {1}", "5 : [1]", "0 : 6"} }),
+ ("compare_selections", new object[]{new string[] {"34,34"} }),
+ ("treenode_click", new object[]{new string[] {"4 : \"foo\""} }),
+ ("set_document_type", new object[]{"REGEX"}),
+ ("tree_query", new object[]{"s_csv(@, 1,`\\t`,`\\r\\n`,`'` ,h)"}),
+ ("treenode_click", new object[]{new string[] { "1 : \"{\\\"ö\\\": \\\"3 4\\\"}\"" } }),
+ ("treenode_click", new object[]{new string[] { "3 : \"{\\\"5\\\": [6]}\"" } }),
+ };
- public static bool Test()
- {
var messages = new List();
int failures = 0;
string previouslyOpenFname = Npp.notepad.GetCurrentFilePath();
@@ -735,30 +786,26 @@ public static bool Test()
// this message box doesn't block the main thread, but it introduces some asynchronous behavior
// that was probably responsible for crashing the UI tests
Main.hasWarnedSelectionsForgotten = true;
- if (!hasRunTestsThisSession)
+ // add command to overwrite with a lot of arrays and select every valid json
+ try
{
- hasRunTestsThisSession = true;
- // add command to overwrite with a lot of arrays and select every valid json
- try
- {
- string oneArray2000xStr = (string)remesParser.Search("@ * 2000", new JNode("[1]\r\n")).value;
- JArray oneArray2000x = (JArray)jsonParser.ParseJsonLines(oneArray2000xStr);
- string[] oneArray2000xSelections = ((JArray)remesParser.Search("(range(2000) * 5)[:]{@, @ + 3}{s_join(`,`, str(@))}[0]", new JNode())).children
- .Select(x => (string)x.value)
- .ToArray();
- string[] oneArray2000xPPrintSelections = ((JArray)remesParser.Search("(range(2000) * 13)[:]{@, @ + 11}{s_join(`,`, str(@))}[0]", new JNode())).children
- .Select(x => (string)x.value)
- .ToArray();
- testcases.Add(("overwrite", new object[] { oneArray2000xStr }));
- testcases.Add(("select_every_valid", new object[] { }));
- testcases.Add(("compare_selections", new object[] { oneArray2000xSelections }));
- testcases.Add(("pretty_print", new object[] { }));
- testcases.Add(("compare_selections", new object[] { oneArray2000xPPrintSelections }));
- }
- catch (Exception ex)
- {
- messages.Add($"Failed to add testcases due to exception {ex}");
- }
+ string oneArray2000xStr = (string)remesParser.Search("@ * 2000", new JNode("[1]\r\n")).value;
+ JArray oneArray2000x = (JArray)jsonParser.ParseJsonLines(oneArray2000xStr);
+ string[] oneArray2000xSelections = ((JArray)remesParser.Search("(range(2000) * 5)[:]{@, @ + 3}{s_join(`,`, str(@))}[0]", new JNode())).children
+ .Select(x => (string)x.value)
+ .ToArray();
+ string[] oneArray2000xPPrintSelections = ((JArray)remesParser.Search("(range(2000) * 13)[:]{@, @ + 11}{s_join(`,`, str(@))}[0]", new JNode())).children
+ .Select(x => (string)x.value)
+ .ToArray();
+ testcases.Add(("overwrite", new object[] { oneArray2000xStr }));
+ testcases.Add(("select_every_valid", new object[] { }));
+ testcases.Add(("compare_selections", new object[] { oneArray2000xSelections }));
+ testcases.Add(("pretty_print", new object[] { }));
+ testcases.Add(("compare_selections", new object[] { oneArray2000xPPrintSelections }));
+ }
+ catch (Exception ex)
+ {
+ messages.Add($"Failed to add testcases due to exception {ex}");
}
// run all the commands
int lastFailureIndex = messages.Count;
diff --git a/docs/README.md b/docs/README.md
index f000b11..714b31c 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -115,6 +115,26 @@ When you parse a document that contains syntax errors, you may be asked if you w
In [v5.3.0](/CHANGELOG.md#530---2023-06-10), a form was added to display errors. Prior to that, errors were shown as text in a new buffer.
+### Document type box *(added in v6.0)* ###
+
+*Beginning in version [v6.0](/CHANGELOG.md#600---unreleased-2023-mm-dd),* the tree view has a document type box just above the tree itself.
+
+This box has four options (auto):
+* `JSON mode`: parse document (or each selection) as JSON
+* `JSONL mode`: parse document (or each selection) as [JSON lines](#json-lines-documents)
+* `INI mode`: parse document (or each selection) as an [INI file](#parsing-ini-files)
+* `REGEX mode`: the document (or each selection) is converted to a JSON string containing its text, which can then be [searched and edited with regex and RemesPath](#regex-search-form).
+
+Observe the three images below to see how the selected box causes the same document to be interpreted in three different ways (`INI mode` not shown, because that's not a valid INI file).
+
+![Document type box - JSON mode example](/docs/document%20type%20box%20example%20-%20JSON%20mode.PNG)
+
+![Document type box - JSON Lines mode example](/docs/document%20type%20box%20example%20-%20JSONL%20mode.PNG)
+
+![Document type box - REGEX mode example](/docs/document%20type%20box%20example%20-%20REGEX%20mode.PNG)
+
+While this box is focused, you can scroll through these options, or navigate to a different option by typing the first key of its name.
+
### Working with selections ###
[Starting in version v5.5](/CHANGELOG.md#550---2023-08-13), you can work with one or more selections rather than treating the entire file as JSON.
diff --git a/docs/RemesPath.md b/docs/RemesPath.md
index 41eac20..37f7864 100644
--- a/docs/RemesPath.md
+++ b/docs/RemesPath.md
@@ -516,6 +516,19 @@ Returns an array of integers.
* `range(3, 1, -1)` returns `[3, 2]`.
* `range(0, 6, 3)` returns `[0, 3]`.
+---
+`set(x: array) -> object`
+
+*Added in [v6.0](/CHANGELOG.md#600---unreleased-2023-mm-dd)*
+
+Returns an object mapping each unique string representation of an element in `x` to null. This may be preferable to `unique` because of the O(1) average-case lookup performance in an object.
+
+Example: ``set(j`["a", "b", "a", 1, 2.0, null, 1, null]`)`` returns `{"a": null, "b": null, "1": null, "2.0": null, "null": null}`
+
+Issues with this function that may make the `unique` function preferable:
+* two different elements may have the same string representation for the purposes of this function (e.g., `null` and `"null"`, `2.0` and `"2.0"`)
+* strings with characters that must be escaped (e.g., `\`, `"`) have those characters escaped before they become keys in the object returned by `set`
+
----
`s_join(sep: string, x: array) -> string`
@@ -524,11 +537,16 @@ Every element of `x` must be a string.
Returns x string-joined with sep (i.e., returns a string that begins with `x[0]` and has `sep` between `x[i - 1]` and `x[i]` for `1 <= i <= len(x)`)
----
-`sort_by(x: array, k: string | int, descending: bool = false)`
+`sort_by(x: array, k: string | int | function, descending: bool = false)`
-`x` must be an array of arrays (in which case `k` must be an int) or an array of objects (in which case `k` must be a string).
+`x` must be:
+* an array of arrays (if `k` is an integer)
+* an array of objects (if `k` is a string)
+* any array (if `k` is a function)
-Returns a new array of subarrays/subobjects `subitbl` such that `subitbl[k]` is sorted ascending.
+Returns:
+* a new array of subarrays/subobjects `subitbl` such that `subitbl[k]` is sorted (if `k` is an integer or string)
+* a new array of children `child` such that `k(child)` is sorted (if `k` is a function)
Analogous to SQL `ORDER BY`.
@@ -538,8 +556,8 @@ Prior to [v5.5.0](/CHANGELOG.md#550---2023-08-13), Python-style negative indices
__Examples:__
* With `[[1, 2], [2, 0], [3, -1]]` as input, `sort_by(@, 1)` returns `[[3,-1],[2,0],[1,2]]` because it sorts ascending by the second element.
-* With `[{"a": 1, "b": 3}, {"a": 2, "b": 2}, {"a": 3, "b": 1}]` as input, `sort_by(@, a, true)` returns `[{"a":3,"b":1},{"a":2,"b":2},{"a":1,"b":3}]` because it sorts ascending by key `a`.
-* With `["a", "bbb", "cc"]` as input, `sort_by(@, s_len(@))` returns `["a", "cc", "bbb"]`, because the children are ordered by string length ascending
+* With `[{"a": 1, "b": 3}, {"a": 2, "b": 2}, {"a": 3, "b": 1}]` as input, `sort_by(@, a, true)` returns `[{"a":3,"b":1},{"a":2,"b":2},{"a":1,"b":3}]` because it sorts descending by key `a`.
+* With `["a", "bbb", "cc"]` as input, `sort_by(@, s_len(@))` returns `["a", "cc", "bbb"]`, because the children are sorted ascending by string length.
----
`sorted(x: array, descending: bool = false)`
diff --git a/docs/document type box example - JSON mode.PNG b/docs/document type box example - JSON mode.PNG
new file mode 100644
index 0000000..ff5ab0f
Binary files /dev/null and b/docs/document type box example - JSON mode.PNG differ
diff --git a/docs/document type box example - JSONL mode.PNG b/docs/document type box example - JSONL mode.PNG
new file mode 100644
index 0000000..a2bfe93
Binary files /dev/null and b/docs/document type box example - JSONL mode.PNG differ
diff --git a/docs/document type box example - REGEX mode.PNG b/docs/document type box example - REGEX mode.PNG
new file mode 100644
index 0000000..27b3ed3
Binary files /dev/null and b/docs/document type box example - REGEX mode.PNG differ
diff --git a/most recent errors.txt b/most recent errors.txt
index 7034631..b3ef6bb 100644
--- a/most recent errors.txt
+++ b/most recent errors.txt
@@ -1,4 +1,4 @@
-Test results for JsonTools v5.8.0.15 on Notepad++ 8.5.8 64bit
+Test results for JsonTools v5.8.0.17 on Notepad++ 8.5.8 64bit
NOTE: Ctrl-F (regular expressions *on*) for "Failed [1-9]\d*" to find all failed tests
Tests failed: YAML dumper
=========================
@@ -114,7 +114,7 @@ Testing RemesPath parser and compiler
The queried JSON in the RemesParser tests is named foo:{"foo": [[0, 1, 2], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]], "bar": {"a": false, "b": ["a`g", "bah"]}, "baz": "z", "quz": {}, "jub": [], "guzo": [[[1]], [[2], [3]]], "7": [{"foo": 2}, 1], "_": {"0": 0}}
Failed 0 tests.
-Passed 453 tests.
+Passed 454 tests.
=========================
Testing RemesPath throws errors on bad inputs
=========================
@@ -133,7 +133,7 @@ Testing RemesPath produces sane outputs on randomly generated queries
Fuzz tests query
{"a": [-4, -3., -2., -1, 0, 1, 2., 3., 4], "bc": NaN,"c`d": "df", "q": ["", "a", "jk", "ian", "", "32", "u", "aa", "moun"],"f": 1,"g": 1,"h": 1,"i": 1,"j": 1}
-Ran 10000 fuzz tests
+Ran 5000 fuzz tests
Failed 0 fuzz tests
=========================
Testing multi-statement queries in RemesPath
@@ -195,40 +195,40 @@ Testing UI tests
=========================
Failed 0 tests
-Passed 298 tests
+Passed 330 tests
=========================
Testing JsonParser performance
=========================
Preview of json: [{"A": "Ky'c^g#~)0", "a": 1850111954, "b": 9318359041, "B": "Oyi:/ xxe2", "C": "sKCSa_^7Gg", "c": 7974777124, "d": 2670309238, "D": "0d_K)HmX!.", "E": ".uM*Z{0EJ_", "e": 6958410336, "f": 8050244728, "F": "1%SG_A!xB\t", "g": 3799657125, "G": "il1^k\\\nat*", "H": {"a": 6079042826, "b": 7292804611, "c"
...
-To convert JSON string of size 89556 into JNode took 2.705 +/- 1.982 ms over 32 trials
-Load times (ms): 2, 2, 1, 3, 1, 2, 5, 1, 1, 1, 3, 1, 1, 4, 1, 1, 11, 4, 1, 1, 4, 1, 1, 1, 2, 4, 1, 2, 2, 2, 2, 1
+To convert JSON string of size 89556 into JNode took 2.432 +/- 1.576 ms over 32 trials
+Load times (ms): 1, 2, 9, 1, 2, 3, 1, 1, 2, 3, 1, 1, 4, 1, 1, 1, 2, 1, 1, 4, 1, 1, 1, 3, 1, 1, 4, 1, 1, 1, 3, 1
=========================
Performance tests for RemesPath (float arithmetic)
=========================
-Compiling query "@[@[:].a * @[:].t < @[:].e]" into took 2.482 +/- 15.103 microseconds over 40 trials
-To run pre-compiled query "@[@[:].a * @[:].t < @[:].e]" on JNode from JSON of size 89556 into took 0.029 +/- 0.013 ms over 40 trials
-Query times (ms): 0.058, 0.058, 0.023, 0.023, 0.023, 0.023, 0.023, 0.025, 0.023, 0.023, 0.023, 0.023, 0.023, 0.024, 0.024, 0.023, 0.023, 0.023, 0.023, 0.024, 0.023, 0.023, 0.023, 0.022, 0.023, 0.039, 0.074, 0.04, 0.035, 0.025, 0.034, 0.075, 0.024, 0.024, 0.034, 0.023, 0.023, 0.028, 0.025, 0.024
+Compiling query "@[@[:].a * @[:].t < @[:].e]" into took 2.335 +/- 14.15 microseconds over 40 trials
+To run pre-compiled query "@[@[:].a * @[:].t < @[:].e]" on JNode from JSON of size 89556 into took 0.115 +/- 0.557 ms over 40 trials
+Query times (ms): 0.07, 0.036, 0.025, 0.023, 0.023, 0.023, 0.023, 0.029, 0.023, 0.024, 0.023, 0.023, 0.023, 0.028, 0.023, 0.023, 0.023, 0.024, 0.023, 0.026, 0.023, 0.024, 0.023, 0.023, 0.024, 0.026, 0.023, 0.023, 0.024, 0.023, 0.023, 0.025, 0.024, 0.023, 0.023, 0.023, 0.024, 0.025, 0.024, 3.594
Preview of result: [{"A": "Ky'c^g#~)0", "a": 1850111954, "b": 9318359041, "B": "Oyi:/ xxe2", "C": "sKCSa_^7Gg", "c": 7974777124, "d": 2670309238, "D": "0d_K)HmX!.", "E": ".uM*Z{0EJ_", "e": 6958410336, "f": 8050244728, "F": "1%SG_A!xB\t", "g": 3799657125, "G": "il1^k\\\nat*", "H": {"a": 6079042826, "b": 7292804611, "c"
...
=========================
Performance tests for RemesPath (string operations)
=========================
-Compiling query "@[@[:].z =~ `(?i)[a-z]{5}`]" into took 2.975 +/- 17.954 microseconds over 40 trials
-To run pre-compiled query "@[@[:].z =~ `(?i)[a-z]{5}`]" on JNode from JSON of size 89556 into took 0.112 +/- 0.02 ms over 40 trials
-Query times (ms): 0.191, 0.165, 0.112, 0.114, 0.112, 0.116, 0.104, 0.111, 0.102, 0.107, 0.128, 0.113, 0.109, 0.111, 0.112, 0.111, 0.111, 0.113, 0.149, 0.112, 0.095, 0.098, 0.106, 0.098, 0.103, 0.107, 0.119, 0.106, 0.105, 0.103, 0.108, 0.123, 0.115, 0.116, 0.104, 0.121, 0.106, 0.111, 0.072, 0.064
+Compiling query "@[@[:].z =~ `(?i)[a-z]{5}`]" into took 2.068 +/- 12.431 microseconds over 40 trials
+To run pre-compiled query "@[@[:].z =~ `(?i)[a-z]{5}`]" on JNode from JSON of size 89556 into took 0.055 +/- 0.01 ms over 40 trials
+Query times (ms): 0.111, 0.055, 0.054, 0.054, 0.082, 0.054, 0.053, 0.054, 0.054, 0.053, 0.051, 0.052, 0.052, 0.052, 0.051, 0.052, 0.052, 0.052, 0.052, 0.051, 0.052, 0.051, 0.052, 0.052, 0.052, 0.052, 0.052, 0.053, 0.052, 0.053, 0.052, 0.053, 0.052, 0.052, 0.052, 0.052, 0.052, 0.053, 0.052, 0.052
Preview of result: [{"A": "\n]o1VQ5t6g", "a": 4710024278, "b": 3268860721, "B": "g4Y7+ew^.v", "C": "NK nmax_notq, `when q=true, nmax = ` + str(nmax_q), `when q=false, nmax= ` + str(nmax_notq))" into took 5.272 +/- 31.406 microseconds over 40 trials
+ifelse(nmax_q > nmax_notq, `when q=true, nmax = ` + str(nmax_q), `when q=false, nmax= ` + str(nmax_notq))" into took 4.99 +/- 29.866 microseconds over 40 trials
To run pre-compiled query "var qmask = @[:].q;
var nmax_q = max(@[qmask].n);
var nmax_notq = max(@[not qmask].n);
-ifelse(nmax_q > nmax_notq, `when q=true, nmax = ` + str(nmax_q), `when q=false, nmax= ` + str(nmax_notq))" on JNode from JSON of size 89556 into took 0.033 +/- 0.042 ms over 40 trials
-Query times (ms): 0.088, 0.027, 0.028, 0.025, 0.018, 0.048, 0.018, 0.017, 0.017, 0.287, 0.026, 0.026, 0.024, 0.025, 0.025, 0.023, 0.024, 0.025, 0.025, 0.032, 0.026, 0.016, 0.016, 0.016, 0.021, 0.023, 0.024, 0.04, 0.025, 0.025, 0.026, 0.025, 0.024, 0.026, 0.024, 0.025, 0.025, 0.025, 0.025, 0.026
+ifelse(nmax_q > nmax_notq, `when q=true, nmax = ` + str(nmax_q), `when q=false, nmax= ` + str(nmax_notq))" on JNode from JSON of size 89556 into took 0.025 +/- 0.031 ms over 40 trials
+Query times (ms): 0.076, 0.026, 0.028, 0.038, 0.024, 0.048, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.206, 0.031, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.015, 0.015, 0.016, 0.015, 0.016
Preview of result: "when q=false, nmax= 9830935647.0"
...
=========================
@@ -267,11 +267,11 @@ Performance tests for RemesPath (references to compile-time constant variables)
Compiling query "var X = X;
var onetwo = j`[1, 2]`;
-@[:]->at(@, X)->at(@, onetwo)" into took 3.157 +/- 19.094 microseconds over 40 trials
+@[:]->at(@, X)->at(@, onetwo)" into took 2.65 +/- 15.941 microseconds over 40 trials
To run pre-compiled query "var X = X;
var onetwo = j`[1, 2]`;
-@[:]->at(@, X)->at(@, onetwo)" on JNode from JSON of size 89556 into took 0.016 +/- 0.01 ms over 40 trials
-Query times (ms): 0.054, 0.026, 0.016, 0.02, 0.013, 0.012, 0.013, 0.013, 0.015, 0.05, 0.028, 0.013, 0.012, 0.013, 0.039, 0.012, 0.013, 0.012, 0.013, 0.013, 0.012, 0.013, 0.013, 0.012, 0.013, 0.012, 0.013, 0.013, 0.012, 0.013, 0.013, 0.012, 0.013, 0.012, 0.013, 0.012, 0.012, 0.013, 0.013, 0.012
+@[:]->at(@, X)->at(@, onetwo)" on JNode from JSON of size 89556 into took 0.014 +/- 0.005 ms over 40 trials
+Query times (ms): 0.038, 0.013, 0.013, 0.012, 0.012, 0.012, 0.012, 0.012, 0.013, 0.03, 0.012, 0.012, 0.013, 0.012, 0.012, 0.013, 0.012, 0.013, 0.013, 0.012, 0.012, 0.012, 0.012, 0.013, 0.012, 0.013, 0.012, 0.012, 0.013, 0.012, 0.013, 0.013, 0.012, 0.013, 0.012, 0.013, 0.012, 0.012, 0.012, 0.012
Preview of result: [[1695727848, 0.287562638736685], [2126430375, 0.00767794129708177], [5310550656, 0.380769772645687], [2519183283, 0.153176220930558], [6610062385, 0.662996225870666], [987168256, 0.924410189999928], [6615003609, 0.917112691225947], [4465232046, 0.684311931851536], [8654414565, 0.631485392105992], [
...
=========================
@@ -280,29 +280,29 @@ Performance tests for RemesPath (references to variables that are not compile-ti
Compiling query "var X = @->`X`;
var onetwo = @{1, 2};
-@[:]->at(@, X)->at(@, onetwo)" into took 4.12 +/- 24.849 microseconds over 40 trials
+@[:]->at(@, X)->at(@, onetwo)" into took 2.662 +/- 15.939 microseconds over 40 trials
To run pre-compiled query "var X = @->`X`;
var onetwo = @{1, 2};
-@[:]->at(@, X)->at(@, onetwo)" on JNode from JSON of size 89556 into took 0.031 +/- 0.011 ms over 40 trials
-Query times (ms): 0.091, 0.029, 0.029, 0.028, 0.028, 0.04, 0.028, 0.058, 0.028, 0.028, 0.028, 0.028, 0.027, 0.028, 0.028, 0.027, 0.028, 0.028, 0.027, 0.028, 0.028, 0.028, 0.028, 0.028, 0.027, 0.029, 0.027, 0.028, 0.028, 0.028, 0.028, 0.034, 0.028, 0.027, 0.029, 0.028, 0.028, 0.028, 0.028, 0.027
+@[:]->at(@, X)->at(@, onetwo)" on JNode from JSON of size 89556 into took 0.017 +/- 0.005 ms over 40 trials
+Query times (ms): 0.051, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.017, 0.017, 0.016, 0.016
Preview of result: [[1695727848, 0.287562638736685], [2126430375, 0.00767794129708177], [5310550656, 0.380769772645687], [2519183283, 0.153176220930558], [6610062385, 0.662996225870666], [987168256, 0.924410189999928], [6615003609, 0.917112691225947], [4465232046, 0.684311931851536], [8654414565, 0.631485392105992], [
...
=========================
Performance tests for RemesPath (simple string mutations)
=========================
-Compiling query "@[:].z = s_sub(@, g, B)" into took 2.535 +/- 15.319 microseconds over 40 trials
-To run pre-compiled query "@[:].z = s_sub(@, g, B)" on JNode from JSON of size 89556 into took 0.025 +/- 0.007 ms over 40 trials
-Query times (ms): 0.054, 0.025, 0.022, 0.022, 0.021, 0.024, 0.027, 0.026, 0.022, 0.031, 0.022, 0.02, 0.021, 0.021, 0.019, 0.024, 0.022, 0.023, 0.026, 0.023, 0.024, 0.023, 0.024, 0.03, 0.021, 0.024, 0.02, 0.02, 0.021, 0.02, 0.02, 0.035, 0.032, 0.035, 0.022, 0.028, 0.031, 0.032, 0.016, 0.015
+Compiling query "@[:].z = s_sub(@, g, B)" into took 1.79 +/- 10.842 microseconds over 40 trials
+To run pre-compiled query "@[:].z = s_sub(@, g, B)" on JNode from JSON of size 89556 into took 0.014 +/- 0.007 ms over 40 trials
+Query times (ms): 0.024, 0.011, 0.011, 0.01, 0.009, 0.01, 0.011, 0.01, 0.01, 0.01, 0.014, 0.013, 0.014, 0.013, 0.015, 0.012, 0.011, 0.012, 0.012, 0.01, 0.012, 0.011, 0.011, 0.011, 0.01, 0.01, 0.014, 0.013, 0.016, 0.01, 0.011, 0.011, 0.011, 0.011, 0.013, 0.044, 0.029, 0.025, 0.024, 0.015
Preview of result: [{"A": "Ky'c^g#~)0", "a": 1850111954, "b": 9318359041, "B": "Oyi:/ xxe2", "C": "sKCSa_^7Gg", "c": 7974777124, "d": 2670309238, "D": "0d_K)HmX!.", "E": ".uM*Z{0EJ_", "e": 6958410336, "f": 8050244728, "F": "1%SG_A!xB\t", "g": 3799657125, "G": "il1^k\\\nat*", "H": {"a": 6079042826, "b": 7292804611, "c"
...
=========================
Performance tests for RemesPath (simple number mutations)
=========================
-Compiling query "@[:].x = ifelse(@ < 0.5, @ + 3, @ - 3)" into took 2.815 +/- 16.955 microseconds over 40 trials
-To run pre-compiled query "@[:].x = ifelse(@ < 0.5, @ + 3, @ - 3)" on JNode from JSON of size 89556 into took 0.09 +/- 0.36 ms over 40 trials
-Query times (ms): 0.044, 0.029, 0.026, 0.024, 0.025, 0.026, 0.051, 0.034, 0.04, 0.027, 0.024, 0.027, 0.026, 0.026, 0.024, 0.023, 0.024, 0.024, 0.034, 0.032, 0.066, 0.044, 0.027, 0.026, 0.026, 0.024, 0.025, 0.025, 0.023, 0.024, 0.023, 0.024, 0.03, 0.088, 2.334, 0.047, 0.034, 0.038, 0.036, 0.035
+Compiling query "@[:].x = ifelse(@ < 0.5, @ + 3, @ - 3)" into took 18.682 +/- 116.032 microseconds over 40 trials
+To run pre-compiled query "@[:].x = ifelse(@ < 0.5, @ + 3, @ - 3)" on JNode from JSON of size 89556 into took 0.027 +/- 0.009 ms over 40 trials
+Query times (ms): 0.036, 0.022, 0.021, 0.021, 0.02, 0.021, 0.021, 0.02, 0.021, 0.02, 0.02, 0.02, 0.029, 0.025, 0.021, 0.042, 0.03, 0.057, 0.047, 0.029, 0.022, 0.021, 0.022, 0.021, 0.028, 0.027, 0.026, 0.021, 0.021, 0.02, 0.021, 0.042, 0.042, 0.038, 0.042, 0.026, 0.025, 0.027, 0.028, 0.025
Preview of result: [{"A": "Ky'c^g#~)0", "a": 1850111954, "b": 9318359041, "B": "Oyi:/ xxe2", "C": "sKCSa_^7Gg", "c": 7974777124, "d": 2670309238, "D": "0d_K)HmX!.", "E": ".uM*Z{0EJ_", "e": 6958410336, "f": 8050244728, "F": "1%SG_A!xB\t", "g": 3799657125, "G": "il1^k\\\nat*", "H": {"a": 6079042826, "b": 7292804611, "c"
...
=========================
@@ -312,12 +312,12 @@ Performance tests for RemesPath (mutations with a for loop)
Compiling query "var xhalf = @[:].x < 0.5;
for lx = zip(@[:].l, xhalf);
lx[0] = ifelse(lx[1], foo, bar);
-end for;" into took 6.272 +/- 38.195 microseconds over 40 trials
+end for;" into took 4.422 +/- 26.802 microseconds over 40 trials
To run pre-compiled query "var xhalf = @[:].x < 0.5;
for lx = zip(@[:].l, xhalf);
lx[0] = ifelse(lx[1], foo, bar);
-end for;" on JNode from JSON of size 89556 into took 0.064 +/- 0.023 ms over 40 trials
-Query times (ms): 0.092, 0.101, 0.116, 0.053, 0.063, 0.059, 0.087, 0.063, 0.062, 0.063, 0.064, 0.058, 0.057, 0.034, 0.038, 0.085, 0.085, 0.067, 0.062, 0.049, 0.044, 0.043, 0.043, 0.043, 0.041, 0.055, 0.07, 0.049, 0.05, 0.051, 0.049, 0.091, 0.068, 0.062, 0.061, 0.102, 0.14, 0.052, 0.038, 0.051
+end for;" on JNode from JSON of size 89556 into took 0.053 +/- 0.023 ms over 40 trials
+Query times (ms): 0.061, 0.038, 0.037, 0.037, 0.036, 0.036, 0.037, 0.035, 0.049, 0.09, 0.081, 0.055, 0.04, 0.039, 0.043, 0.044, 0.04, 0.041, 0.04, 0.04, 0.04, 0.042, 0.089, 0.066, 0.044, 0.043, 0.038, 0.044, 0.037, 0.034, 0.035, 0.141, 0.089, 0.06, 0.053, 0.056, 0.065, 0.071, 0.061, 0.099
Preview of result: [["bar", false], ["bar", false], ["foo", true], ["foo", true], ["foo", true], ["foo", true], ["foo", true], ["bar", false], ["bar", false], ["bar", false], ["foo", true], ["foo", true], ["bar", false], ["bar", false], ["foo", true], ["bar", false], ["bar", false], ["bar", false], ["foo", true], ["ba
...
=========================
@@ -326,18 +326,18 @@ Testing performance of JSON compression and pretty-printing
Preview of json: [{"A": "Ky'c^g#~)0", "a": 1850111954, "b": 9318359041, "B": "Oyi:/ xxe2", "C": "sKCSa_^7Gg", "c": 7974777124, "d": 2670309238, "D": "0d_K)HmX!.", "E": ".uM*Z{0EJ_", "e": 6958410336, "f": 8050244728, "F": "1%SG_A!xB\t", "g": 3799657125, "G": "il1^k\\\nat*", "H": {"a": 6079042826, "b": 7292804611, "c"
...
-To compress JNode from JSON string of 89556 took 3.984 +/- 0.471 ms over 64 trials (minimal whitespace, sort_keys=TRUE)
-To compress JNode from JSON string of 89556 took 2.148 +/- 0.475 ms over 64 trials (minimal whitespace, sort_keys=FALSE)
-To Google-style pretty-print JNode from JSON string of 89556 took 4.269 +/- 0.549 ms over 64 trials (sort_keys=true, indent=4)
-To Whitesmith-style pretty-print JNode from JSON string of 89556 took 4.328 +/- 0.448 ms over 64 trials (sort_keys=true, indent=4)
-To PPrint-style pretty-print JNode from JSON string of 89556 took 7.543 +/- 1.133 ms over 64 trials (sort_keys=true, indent=4)
+To compress JNode from JSON string of 89556 took 3.923 +/- 0.473 ms over 64 trials (minimal whitespace, sort_keys=TRUE)
+To compress JNode from JSON string of 89556 took 2.144 +/- 0.267 ms over 64 trials (minimal whitespace, sort_keys=FALSE)
+To Google-style pretty-print JNode from JSON string of 89556 took 4.823 +/- 0.912 ms over 64 trials (sort_keys=true, indent=4)
+To Whitesmith-style pretty-print JNode from JSON string of 89556 took 4.274 +/- 0.587 ms over 64 trials (sort_keys=true, indent=4)
+To PPrint-style pretty-print JNode from JSON string of 89556 took 6.174 +/- 0.485 ms over 64 trials (sort_keys=true, indent=4)
=========================
Testing performance of JsonSchemaValidator and random JSON creation
=========================
-To create a random set of tweet JSON of size 140467 (15 tweets) based on the matching schema took 6.966 +/- 3.393 ms over 64 trials
-To compile the tweet schema to a validation function took 0.232 +/- 0.057 ms over 64 trials
-To validate tweet JSON of size 140467 (15 tweets) based on the compiled schema took 1.13 +/- 0.276 ms over 64 trials
+To create a random set of tweet JSON of size 169566 (15 tweets) based on the matching schema took 6.868 +/- 3.266 ms over 64 trials
+To compile the tweet schema to a validation function took 0.389 +/- 0.786 ms over 64 trials
+To validate tweet JSON of size 169566 (15 tweets) based on the compiled schema took 1.064 +/- 0.172 ms over 64 trials
=========================
Testing JSON grepper's API request tool
=========================