1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-01-22 10:54:46 +00:00

Compare commits

...

60 Commits

Author SHA1 Message Date
Jeremy Ruston
09193d49c4 Force a rebuild
I don't have time right now to resolve the merge conflicts with master
2025-09-17 14:06:58 +01:00
Jeremy Ruston
356e8e2f23 Fix failing test 2025-05-08 18:08:34 +01:00
Jeremy Ruston
26d9d9fb62 Fix jittery scrolling on lists 2025-05-08 18:05:21 +01:00
Jeremy Ruston
c0d9c4f9ec Make the paginated list more reusable 2025-05-08 18:05:08 +01:00
Jeremy Ruston
4351a3d906 Add timing information to filter traces 2025-05-08 15:32:45 +01:00
Jeremy Ruston
728243a66a Add global setting to enable filter observation 2025-05-07 20:49:57 +01:00
Jeremy Ruston
3f733796e5 Include shortcut prefix in traces 2025-05-07 20:49:28 +01:00
Jeremy Ruston
8711726ecd Useful to give the start method access to the widget 2025-05-02 10:29:53 +01:00
Jeremy Ruston
6eddffd7c5 Enable wikitext tests 2025-05-02 10:12:04 +01:00
Jeremy Ruston
29a5a1c571 Merge branch 'master' into filter-inspection 2025-04-30 17:02:26 +01:00
Jeremy Ruston
83da90d4cd Remove unneeded input boxes 2025-04-29 12:40:07 +01:00
Jeremy Ruston
6b2b1df9ad Consolidate identical adjacent lists
Makes things much clearer, but still some formatting and CSS improvements needed
2025-04-29 12:31:14 +01:00
Jeremy Ruston
d995e1a87d Fix tricky bug with a dodgy closure 2025-04-29 10:32:25 +01:00
Jeremy Ruston
60165678e4 Fix scrolling of observations 2025-04-29 09:03:58 +01:00
Jeremy Ruston
0b03e79339 New UI for observing filters 2025-04-25 17:40:25 +01:00
Jeremy Ruston
93546c5511 Get rid of reveal widget 2025-04-25 14:50:09 +01:00
Jeremy Ruston
e2e14db1f0 Better example filter 2025-04-25 14:36:04 +01:00
Jeremy Ruston
6c59263070 Typo 2025-04-25 14:35:43 +01:00
Jeremy Ruston
628a2a45e6 Make variable usage a bit more logical 2025-04-25 14:34:01 +01:00
Jeremy Ruston
51d79fc5ac Make build-site script more configurable to avoid Netlify timeouts 2025-04-25 14:19:30 +01:00
Jeremy Ruston
b4e48bb2b1 Full width buttons are easier to click 2025-04-21 12:26:56 +01:00
Jeremy Ruston
210311eccf Placeholder for advanced search edit box 2025-04-21 11:42:34 +01:00
Jeremy Ruston
2f767cdb28 Docs update 2025-04-21 11:42:18 +01:00
Jeremy Ruston
0a0838e753 Merge branch 'master' into filter-inspection 2025-04-16 14:31:11 +01:00
Jeremy Ruston
66196d5c2b More docs tweaks 2025-04-14 16:25:56 +01:00
Jeremy Ruston
483522ea09 Docs tweaks 2025-04-14 15:44:30 +01:00
Jeremy Ruston
3801e2536c Reorganise tiddlers
Some of the titles were not consistent
2025-04-14 10:58:40 +01:00
Jeremy Ruston
77aec1f8f8 Merge branch 'master' into filter-inspection 2025-04-13 17:56:33 +01:00
Jeremy Ruston
df529d7d7b Fix RSOD on invalid filter expressions 2025-04-13 17:51:39 +01:00
Jeremy Ruston
8dbb41ba2a Startings of a UI for inspectable filters 2025-04-11 15:42:54 +01:00
Jeremy Ruston
b632b75f70 Fix default tab 2025-04-11 14:43:44 +01:00
Jeremy Ruston
ef92b899ed Add remote inspection of preconfigured filters
There is no user interface yet, and it is currently hardcoded to inspect the filter "[all[shadows+tiddlers]tag[$:/tags/ViewTemplate]!is[draft]]" which is used in tiddler rendering. You can see the results in the sidebar More -> System starting with "$:/temp/filter-inspection/"
2025-04-11 13:33:09 +01:00
Jeremy Ruston
dcb8fa2f86 Only display advanced search tabs if there are more than one 2025-04-10 14:11:55 +01:00
Jeremy Ruston
c5894c64b9 Missed some styles from refactoring 2025-04-10 14:11:39 +01:00
Jeremy Ruston
04ad642be7 Refactor most of this PR into the "Internals" plugin 2025-04-09 22:01:43 +01:00
Jeremy Ruston
c8f17511f9 Improve positioning of the list "more" button 2025-04-07 18:51:31 +01:00
Jeremy Ruston
845b4ba3b5 Rename test 2025-04-07 18:41:40 +01:00
Jeremy Ruston
8ea00a05d3 Docs update 2025-04-07 17:31:13 +01:00
Jeremy Ruston
28698690b3 Use scrollable widget to maintain scroll position 2025-04-07 16:35:26 +01:00
Jeremy Ruston
3c6ec3f9bb Include separate entries for each evaluation of a run 2025-04-07 16:13:18 +01:00
Jeremy Ruston
3a2c81192e Docs for the inspect-filter macro 2025-04-07 10:42:37 +01:00
Jeremy Ruston
d29199ffa8 Fix failing test 2025-04-06 22:13:47 +01:00
Jeremy Ruston
e741816a70 Use parse tree to render source text of filter 2025-04-06 22:06:58 +01:00
Jeremy Ruston
002c319518 Remove unnecessary text 2025-04-04 10:09:32 +01:00
Jeremy Ruston
33964d460d Add expand/collapse buttons to lists 2025-04-04 10:07:08 +01:00
Jeremy Ruston
9b3e61ef10 Avoid scrolling filter heading 2025-04-03 22:09:28 +01:00
Jeremy Ruston
967e882040 Truncate lists with an "expand" button 2025-04-03 22:03:43 +01:00
Jeremy Ruston
06dfe365be Better docs example 2025-04-03 22:03:12 +01:00
Jeremy Ruston
3e1286013f Fix bug with operators that return an iterator 2025-04-03 21:46:36 +01:00
Jeremy Ruston
1972e8b5f3 Stripy backgrounds for lists 2025-04-03 21:22:33 +01:00
Jeremy Ruston
3cb6712ccb Minimum width for lists 2025-04-03 21:22:18 +01:00
Jeremy Ruston
8b3fbe3134 Fix typo 2025-04-03 14:35:37 +01:00
Jeremy Ruston
f6b39d1a40 Add horizontal presentation 2025-04-03 09:26:07 +01:00
Jeremy Ruston
1e10496fd6 Introduce filter visualisation 2025-04-02 22:05:43 +01:00
Jeremy Ruston
5226ca1f75 Comment out logging 2025-04-02 22:04:59 +01:00
Jeremy Ruston
037b4aa227 Add filter output list to JSON 2025-04-02 18:01:15 +01:00
Jeremy Ruston
ddeb4fd6e6 Fix filter output 2025-04-02 08:56:28 +01:00
Jeremy Ruston
8ff0cb8650 Fix typo 2025-04-01 17:53:53 +01:00
Jeremy Ruston
27075acbc6 Add "inspect" tab to advanced search and make the search box into a textarea 2025-04-01 12:43:13 +01:00
Jeremy Ruston
d4043fc1f4 Add inspect operator 2025-04-01 12:06:10 +01:00
30 changed files with 1081 additions and 145 deletions

View File

@@ -107,20 +107,33 @@ fi
# /index.html Main site
# /external-(version).html External core version of main site
# /favicon.ico Favicon for main site
# /static.html Static rendering of default tiddlers
# /alltiddlers.html Static rendering of all tiddlers
# /static/* Static single tiddlers
# /static/static.css Static stylesheet
# /static/favicon.ico Favicon for static pages
node $TW5_BUILD_TIDDLYWIKI \
$TW5_BUILD_MAIN_EDITION \
--version \
--load $TW5_BUILD_OUTPUT/build.tid \
--output $TW5_BUILD_OUTPUT \
--build favicon static index external-js \
--build favicon index external-js \
|| exit 1
# /static.html Static rendering of default tiddlers
# /alltiddlers.html Static rendering of all tiddlers
# /static/* Static single tiddlers
# /static/static.css Static stylesheet
# /static/favicon.ico Favicon for static pages
# Conditionally build static files if $TW5_BUILD_STATIC variable is not set or is set to 0
if [ -z "$TW5_BUILD_STATIC" ] || [ "$TW5_BUILD_STATIC" = "0" ]; then
node $TW5_BUILD_TIDDLYWIKI \
$TW5_BUILD_MAIN_EDITION \
--version \
--load $TW5_BUILD_OUTPUT/build.tid \
--output $TW5_BUILD_OUTPUT \
--build static \
|| exit 1
fi
# /empty.html Empty
# /empty.hta For Internet Explorer
# /empty-external-core.html External core empty
@@ -190,6 +203,9 @@ node $TW5_BUILD_TIDDLYWIKI \
#
######################################################
# Conditionally build editions if $TW5_BUILD_EDITIONS variable is not set or is set to 0
if [ -z "$TW5_BUILD_EDITIONS" ] || [ "$TW5_BUILD_EDITIONS" = "0" ]; then
# /editions/xlsx-utils/index.html xlsx-utils edition
node $TW5_BUILD_TIDDLYWIKI \
./editions/xlsx-utils \
@@ -254,12 +270,18 @@ node $TW5_BUILD_TIDDLYWIKI \
--build index \
|| exit 1
fi
######################################################
#
# Plugin demos
#
######################################################
# Conditionally build plugin demos if $TW5_BUILD_PLUGIN_DEMOS variable is not set
if [ -z "$TW5_BUILD_PLUGIN_DEMOS" ] || [ "$TW5_BUILD_PLUGIN_DEMOS" = "0" ]; then
# /plugins/tiddlywiki/innerwiki/index.html Demo wiki with Innerwiki plugin
node $TW5_BUILD_TIDDLYWIKI \
@@ -364,12 +386,17 @@ node $TW5_BUILD_TIDDLYWIKI \
--rendertiddler $:/core/save/empty plugins/tiddlywiki/geospatial/empty.html text/plain \
|| exit 1
fi
######################################################
#
# Language editions
#
######################################################
# Conditionally build language editions if $TW5_BUILD_LANGUAGE_DEMOS variable is not set
if [ -z "$TW5_BUILD_LANGUAGE_DEMOS" ] || [ "$TW5_BUILD_LANGUAGE_DEMOS" = "0" ]; then
# Delete any existing static content
rm -rf $TW5_BUILD_OUTPUT/languages/de-AT/static/*
@@ -453,12 +480,17 @@ node $TW5_BUILD_TIDDLYWIKI \
--build empty index \
|| exit 1
fi
######################################################
#
# Plugin library
#
######################################################
# Conditionally build plugin library if $TW5_BUILD_PLUGIN_LIBRARY variable is not set
if [ -z "$TW5_BUILD_PLUGIN_LIBRARY" ] || [ "$TW5_BUILD_PLUGIN_LIBRARY" = "0" ]; then
node $TW5_BUILD_TIDDLYWIKI \
./editions/pluginlibrary \
--load $TW5_BUILD_OUTPUT/build.tid \
@@ -466,6 +498,8 @@ node $TW5_BUILD_TIDDLYWIKI \
--build library\
|| exit 1
fi
# Delete the temporary build tiddler
rm $TW5_BUILD_OUTPUT/build.tid || exit 1

View File

@@ -2732,14 +2732,15 @@ $tw.hooks.removeHook = function(hookName,definition) {
/*
Invoke the hook by key
*/
$tw.hooks.invokeHook = function(hookName /*, value,... */) {
var args = Array.prototype.slice.call(arguments,1);
if($tw.utils.hop($tw.hooks.names,hookName)) {
$tw.hooks.invokeHook = function(hookName, firstArgument /*, value,... */) {
if(Object.prototype.hasOwnProperty.call($tw.hooks.names,hookName)) {
var args = Array.prototype.slice.call(arguments,1);
for(var i = 0; i < $tw.hooks.names[hookName].length; i++) {
args[0] = $tw.hooks.names[hookName][i].apply(null,args);
}
return args[0];
}
return args[0];
return firstArgument;
};
/////////////////////////// Main boot function to decrypt tiddlers and then startup

View File

@@ -3,7 +3,9 @@ title: $:/language/Search/
DefaultResults/Caption: List
Filter/Caption: Filter
Filter/Hint: Search via a [[filter expression|https://tiddlywiki.com/static/Filters.html]]
Filter/Placeholder: Filter expression
Filter/Matches: //<small><<resultCount>> matches</small>//
Filter/FilterResults/Results/Caption: Results
Matches: //<small><<resultCount>> matches</small>//
Matches/All: All matches:
Matches/NoMatch: //No match//

View File

@@ -220,13 +220,25 @@ exports.filterTiddlers = function(filterString,widget,source) {
Compile a filter into a function with the signature fn(source,widget) where:
source: an iterator function for the source tiddlers, called source(iterator), where iterator is called as iterator(tiddler,title)
widget: an optional widget node for retrieving the current tiddler etc.
Parameters:
filterString: the filter string to compile
options: includes:
wrappers: a hashmap of wrapper functions to apply to the compiled filter function
*/
exports.compileFilter = function(filterString) {
exports.compileFilter = function(filterString,options) {
options = options || {};
var self = this;
var wrappers = options.wrappers || {};
// Invoke the hook to allow the filter to be inspected
wrappers = $tw.hooks.invokeHook("th-filter-evaluation",filterString,wrappers) || wrappers;
// Get the result from the cache if we can
if(!this.filterCache) {
this.filterCache = Object.create(null);
this.filterCacheCount = 0;
}
if(this.filterCache[filterString] !== undefined) {
if(this.filterCache[filterString] !== undefined && !wrappers.prefix && !wrappers.operation && !wrappers.operator && !wrappers.start && !wrappers.done) {
return this.filterCache[filterString];
}
var filterParseTree;
@@ -235,7 +247,14 @@ exports.compileFilter = function(filterString) {
} catch(e) {
// We do not cache this result, so it adjusts along with localization changes
return function(source,widget) {
return [$tw.language.getString("Error/Filter") + ": " + e];
if(wrappers.start) {
wrappers.start(source,widget);
}
var resultsArray = [$tw.language.getString("Error/Filter") + ": " + e];
if(wrappers.done) {
wrappers.done(resultsArray);
}
return resultsArray;
};
}
// Get the hashmap of filter operator functions
@@ -249,52 +268,64 @@ exports.compileFilter = function(filterString) {
var operationSubFunction = function(source,widget) {
var accumulator = source,
results = [],
currTiddlerTitle = widget && widget.getVariable("currentTiddler");
$tw.utils.each(operation.operators,function(operator) {
var operands = [],
operatorFunction;
if(!operator.operator) {
// Use the "title" operator if no operator is specified
operatorFunction = filterOperators.title;
} else if(!filterOperators[operator.operator]) {
// Unknown operators treated as "[unknown]" - at run time we can distinguish between a custom operator and falling back to the default "field" operator
operatorFunction = filterOperators["[unknown]"];
} else {
// Use the operator function
operatorFunction = filterOperators[operator.operator];
}
$tw.utils.each(operator.operands,function(operand) {
if(operand.indirect) {
operand.value = self.getTextReference(operand.text,"",currTiddlerTitle);
} else if(operand.variable) {
var varTree = $tw.utils.parseFilterVariable(operand.text);
operand.value = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source})[0] || "";
} else {
operand.value = operand.text;
}
operands.push(operand.value);
});
// Invoke the appropriate filteroperator module
results = operatorFunction(accumulator,{
operator: operator.operator,
operand: operands.length > 0 ? operands[0] : undefined,
operands: operands,
prefix: operator.prefix,
suffix: operator.suffix,
suffixes: operator.suffixes,
regexp: operator.regexp
},{
wiki: self,
widget: widget
currTiddlerTitle = widget && widget.getVariable("currentTiddler"),
handleOperation = function() {
$tw.utils.each(operation.operators,function(operator) {
var operands = [],
operatorName,operatorFunction;
if(!operator.operator) {
// Use the "title" operator if no operator is specified
operatorName = "title";
} else if(!filterOperators[operator.operator]) {
// Unknown operators treated as "[unknown]" - at run time we can distinguish between a custom operator and falling back to the default "field" operator
operatorName = "[unknown]";
} else {
// Use the operator function
operatorName = operator.operator;
}
operatorFunction = filterOperators[operatorName];
$tw.utils.each(operator.operands,function(operand) {
if(operand.indirect) {
operand.value = self.getTextReference(operand.text,"",currTiddlerTitle);
} else if(operand.variable) {
var varTree = $tw.utils.parseFilterVariable(operand.text);
operand.value = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source})[0] || "";
} else {
operand.value = operand.text;
}
operands.push(operand.value);
});
if($tw.utils.isArray(results)) {
accumulator = self.makeTiddlerIterator(results);
} else {
accumulator = results;
}
});
if($tw.utils.isArray(results)) {
// Wrap the filter operator module if required
if(wrappers.operator) {
operatorFunction = wrappers.operator.bind(self,operatorFunction);
}
// Invoke the appropriate filteroperator module
results = operatorFunction(accumulator,{
parseTree: operator,
operator: operator.operator,
operatorName: operatorName,
operand: operands.length > 0 ? operands[0] : undefined,
operands: operands,
prefix: operator.prefix,
suffix: operator.suffix,
suffixes: operator.suffixes,
regexp: operator.regexp
},{
wiki: self,
widget: widget
});
if($tw.utils.isArray(results)) {
accumulator = self.makeTiddlerIterator(results);
} else {
accumulator = results;
}
});
};
if(wrappers.operation) {
handleOperation = wrappers.operation.bind(self,handleOperation,operation);
}
handleOperation();
if($tw.utils.isArray(results)) {
return results;
} else {
var resultArray = [];
@@ -307,27 +338,45 @@ exports.compileFilter = function(filterString) {
var filterRunPrefixes = self.getFilterRunPrefixes();
// Wrap the operator functions in a wrapper function that depends on the prefix
operationFunctions.push((function() {
var options = {wiki: self, suffixes: operation.suffixes || []};
var prefixName;
switch(operation.prefix || "") {
case "": // No prefix means that the operation is unioned into the result
return filterRunPrefixes["or"](operationSubFunction, options);
prefixName = "or";
break;
case "=": // The results of the operation are pushed into the result without deduplication
return filterRunPrefixes["all"](operationSubFunction, options);
prefixName = "all";
break;
case "-": // The results of this operation are removed from the main result
return filterRunPrefixes["except"](operationSubFunction, options);
prefixName = "except";
break;
case "+": // This operation is applied to the main results so far
return filterRunPrefixes["and"](operationSubFunction, options);
prefixName = "and";
break;
case "~": // This operation is unioned into the result only if the main result so far is empty
return filterRunPrefixes["else"](operationSubFunction, options);
default:
if(operation.namedPrefix && filterRunPrefixes[operation.namedPrefix]) {
return filterRunPrefixes[operation.namedPrefix](operationSubFunction, options);
} else {
return function(results,source,widget) {
results.clear();
results.push($tw.language.getString("Error/FilterRunPrefix"));
};
}
prefixName = "else";
break;
default:
prefixName = operation.namedPrefix;
break;
}
if(prefixName && filterRunPrefixes[prefixName]) {
var options = {
wiki: self,
suffixes: operation.suffixes || [],
prefixName: prefixName,
prefix: operation.prefix
},
filterRunPrefixFunction = filterRunPrefixes[prefixName];
// Wrap the filter operator module if required
if(wrappers.prefix) {
filterRunPrefixFunction = wrappers.prefix.bind(self,filterRunPrefixFunction);
}
return filterRunPrefixFunction(operationSubFunction,options);
} else {
return function(results,source,widget) {
results.clear();
results.push($tw.language.getString("Error/FilterRunPrefix"));
};
}
})());
});
@@ -341,6 +390,9 @@ exports.compileFilter = function(filterString) {
if(!widget) {
widget = $tw.rootWidget;
}
if(wrappers.start) {
wrappers.start(source,widget);
}
var results = new $tw.utils.LinkedList();
self.filterRecursionCount = (self.filterRecursionCount || 0) + 1;
if(self.filterRecursionCount < MAX_FILTER_DEPTH) {
@@ -351,7 +403,11 @@ exports.compileFilter = function(filterString) {
results.push("/**-- Excessive filter recursion --**/");
}
self.filterRecursionCount = self.filterRecursionCount - 1;
return results.toArray();
var resultsArray = results.toArray();
if(wrappers.done) {
wrappers.done(resultsArray);
}
return resultsArray;
});
if(this.filterCacheCount >= 2000) {
// To prevent memory leak, we maintain an upper limit for cache size.
@@ -360,7 +416,9 @@ exports.compileFilter = function(filterString) {
this.filterCache = Object.create(null);
this.filterCacheCount = 0;
}
this.filterCache[filterString] = fnMeasured;
this.filterCacheCount++;
if(!wrappers.prefix && !wrappers.operator) {
this.filterCache[filterString] = fnMeasured;
this.filterCacheCount++;
}
return fnMeasured;
};

View File

@@ -34,39 +34,11 @@ caption: {{$:/language/Search/Filter/Caption}}
</$list>
\end
\procedure input-accept-actions()
\whitespace trim
<$list filter="[{$:/config/Search/NavigateOnEnter/enable}match[yes]]">
<$list-empty>
<$list filter="[<tiddler>get[text]!is[missing]] :else[<tiddler>get[text]is[shadow]]">
<$action-navigate $to={{{ [<tiddler>get[text]] }}}/>
</$list>
<$/list-empty>
<$action-navigate $to={{{ [<tiddler>get[text]] }}}/>
</$list>
\end
\procedure input-accept-variant-actions()
\whitespace trim
<$list filter="[{$:/config/Search/NavigateOnEnter/enable}match[yes]]">
<$list-empty>
<$list filter="[<tiddler>get[text]!is[missing]] :else[<tiddler>get[text]is[shadow]]">
<$list filter="[<__tiddler__>get[text]minlength[1]]">
<$action-sendmessage $message="tm-edit-tiddler" $param={{{ [<tiddler>get[text]] }}}/>
</$list>
</$list>
</$list-empty>
<$list filter="[<tiddler>get[text]minlength[1]]">
<$action-sendmessage $message="tm-edit-tiddler" $param={{{ [<tiddler>get[text]] }}}/>
</$list>
</$list>
\end
\whitespace trim
<<lingo Filter/Hint>>
<div class="tc-search tc-advanced-search">
<div class="tc-search tc-advanced-search tc-edit-max-width">
<$keyboard key="((input-tab-right))" actions=<<set-next-input-tab>> class="tc-small-gap-right">
<$keyboard key="((input-tab-left))" actions=<<set-previous-input-tab>>>
<$transclude $variable="keyboard-driven-input"
@@ -75,13 +47,16 @@ caption: {{$:/language/Search/Filter/Caption}}
refreshTitle="$:/temp/advancedsearch/refresh"
selectionStateTitle="$:/temp/advancedsearch/selected-item"
type="search"
tag="input"
tag="textarea"
focus={{$:/config/Search/AutoFocus}}
configTiddlerFilter="[[$:/temp/advancedsearch]]"
firstSearchFilterField="text"
inputAcceptActions=<<input-accept-actions>>
inputAcceptVariantActions=<<input-accept-variant-actions>>
inputAcceptActions=""
inputAcceptVariantActions=""
inputCancelActions=<<cancel-search-actions>>
minHeight="2em"
autoHeight="yes"
placeholder={{$:/language/Search/Filter/Placeholder}}
/>
</$keyboard>
</$keyboard>
@@ -90,13 +65,18 @@ caption: {{$:/language/Search/Filter/Caption}}
</$list>
</div>
<$reveal state="$:/temp/advancedsearch" type="nomatch" text="" tag="div" class="tc-search-results">
<$set name="resultCount" value="<$count filter={{$:/temp/advancedsearch}}/>">
<p><<lingo Filter/Matches>></p>
<$list filter={{$:/temp/advancedsearch}}>
<span class={{{[<currentTiddler>addsuffix[-primaryList]] -[[$:/temp/advancedsearch/selected-item]get[text]] :and[then[]else[tc-list-item-selected]] }}}>
<$transclude tiddler="$:/core/ui/ListItemTemplate"/>
</span>
</$list>
</$set>
</$reveal>
<%if [{$:/temp/advancedsearch}trim[]!match[]] %>
<div class="tc-search-results">
<%if [all[shadows+tiddlers]tag[$:/tags/AdvancedSearch/FilterResults]!has[draft.of]count[]compare:number:gt[1]] %>
<$macrocall
$name="tabs"
tabsList="[all[shadows+tiddlers]tag[$:/tags/AdvancedSearch/FilterResults]!has[draft.of]]"
default="$:/core/ui/AdvancedSearch/Filter/FilterResults/Results"
/>
<%else%>
<$list filter="[all[shadows+tiddlers]tag[$:/tags/AdvancedSearch/FilterResults]!has[draft.of]]">
<$transclude/>
</$list>
<%endif%>
</div>
<%endif%>

View File

@@ -0,0 +1,13 @@
title: $:/core/ui/AdvancedSearch/Filter/FilterResults/Results
tags: $:/tags/AdvancedSearch/FilterResults
caption: {{$:/language/Search/Filter/FilterResults/Results/Caption}}
\procedure lingo-base() $:/language/Search/
<$set name="resultCount" value="<$count filter={{$:/temp/advancedsearch}}/>">
<p><<lingo Filter/Matches>></p>
<$list filter={{$:/temp/advancedsearch}}>
<span class={{{[<currentTiddler>addsuffix[-primaryList]] -[[$:/temp/advancedsearch/selected-item]get[text]] :and[then[]else[tc-list-item-selected]] }}}>
<$transclude tiddler="$:/core/ui/ListItemTemplate"/>
</span>
</$list>
</$set>

View File

@@ -2,11 +2,8 @@ created: 20131127215321439
modified: 20140912135951542
title: $:/DefaultTiddlers
[[TiddlyWiki Pre-release]]
HelloThere
[[Quick Start]]
[[Find Out More]]
[[TiddlyWiki on the Web]]
[[Testimonials and Reviews]]
GettingStarted
Community
[[$:/plugins/tiddlywiki/internals]]
[[$:/AdvancedSearch]]
[[$:/plugins/tiddlywiki/internals/filterinspection/docs/inspectoperator]]
[[$:/plugins/tiddlywiki/internals/filterinspection/docs/InspectFilterMacro]]
[[$:/plugins/tiddlywiki/internals/filterinspection/docs/inspectfiltermacro/examples]]

View File

@@ -2,7 +2,8 @@
"description": "TiddlyWiki core tests",
"plugins": [
"tiddlywiki/jasmine",
"tiddlywiki/geospatial"
"tiddlywiki/geospatial",
"tiddlywiki/internals"
],
"themes": [
"tiddlywiki/vanilla",

View File

@@ -3,10 +3,8 @@ modified: 20140912135951542
title: $:/DefaultTiddlers
type: text/vnd.tiddlywiki
HelloThere
[[Quick Start]]
[[Find Out More]]
[[TiddlyWiki on the Web]]
[[Testimonials and Reviews]]
GettingStarted
Community
[[$:/plugins/tiddlywiki/internals]]
[[$:/AdvancedSearch]]
[[$:/plugins/tiddlywiki/internals/filterinspection/docs/inspectoperator]]
[[$:/plugins/tiddlywiki/internals/filterinspection/docs/InspectFilterMacro]]
[[$:/plugins/tiddlywiki/internals/filterinspection/docs/inspectfiltermacro/examples]]

View File

@@ -0,0 +1,13 @@
title: $:/plugins/tiddlywiki/internals/docs
!! `<<inspect-filter>>` Procedure
{{$:/plugins/tiddlywiki/internals/filterinspection/docs/InspectFilterMacro}}
!!! `<<inspect-filter>>` Procedure Examples
{{$:/plugins/tiddlywiki/internals/filterinspection/docs/inspectfiltermacro/examples}}
!! `inspect` Operator
{{$:/plugins/tiddlywiki/internals/filterinspection/docs/inspectoperator}}

View File

@@ -0,0 +1,8 @@
title: $:/plugins/tiddlywiki/internals/filterinspection/InspectResultsTab
tags: $:/tags/AdvancedSearch/FilterResults
caption: Inspect
list-after: $:/core/ui/AdvancedSearch/Filter/FilterResults/Results
Filter evaluation trace:
<$transclude $variable="inspect-filter" filter={{$:/temp/advancedsearch}} />

View File

@@ -0,0 +1,44 @@
title: $:/plugins/tiddlywiki/internals/filterinspection/ObserveResultsTab
tags: $:/tags/AdvancedSearch/FilterResults
caption: Observe
list-after: $:/plugins/tiddlywiki/internals/filterinspection/InspectResultsTab
Log traces of this filter expression in the background. A trace will be generated every time the filter is evaluated, regardless of the context. The trace is only saved if the results are different from previous traces.
<$checkbox tiddler="$:/config/FilterObservationionEnabled" field="text" checked="yes" unchecked="no" default="no">
Enable background filter observation
</$checkbox>
<$button>
<%if [all[shadows+tiddlers]tag[$:/tags/InspectableFilter]get[text]match{$:/temp/advancedsearch}count[]match[0]] %>
<$action-createtiddler $basetitle="$:/config/inspectable-filters/filter" tags="$:/tags/InspectableFilter" type="text/plain" text={{$:/temp/advancedsearch}}/>
<%endif%>
Enable logging for this filter
</$button>
<$list filter="[all[shadows+tiddlers]tag[$:/tags/InspectableFilter]!has[draft.of]sort[]]" variable="inspectableFilter">
<div class="tc-box tc-inspectable-filter-box">
<div class="tc-box-header">
<$text text={{{ [<inspectableFilter>get[text]] }}}/> <$link to=<<inspectableFilter>>>{{$:/core/images/open-window}}</$link>
</div>
<div class="tc-box-content">
<ol>
<$list filter="[all[shadows+tiddlers]tag[$:/tags/FilterInspectionOutput]!has[draft.of]!sort[modified]] :filter[<inspectableFilter>get[text]match{!!filter}]" variable="inspectionOutput">
<$let transclusion=<<inspectionOutput>>>
<li>
<div class="tc-box tc-inspectable-filter-trace-box">
<div class="tc-box-header">
<$text text={{{ [<inspectionOutput>get[modified]format:date[DDth mmm YYYY 0hh:0mm:0ss]] }}}/><$link to=<<inspectionOutput>>>{{$:/core/images/open-window}}</$link>
</div>
<div class="tc-box-content">
<$transclude $variable="inspect-filter-output-unframed" jsonOutput={{{ [<inspectionOutput>get[text]] }}} />
</div>
</div>
</li>
</$let>
</$list>
</ol>
</div>
</div>
</$list>

View File

@@ -0,0 +1,17 @@
caption: inspect-filter
title: $:/plugins/tiddlywiki/internals/filterinspection/docs/InspectFilterMacro
type: text/vnd.tiddlywiki
The <<.def inspect-filter>> procedure displays a schematic representation of the filter evaluation process, including the input, output and intermediate results of evaluating the specified filter. It is based on the [[inspect operator|$:/plugins/tiddlywiki/internals/filterinspection/docs/inspectoperator]].
By default the tabs are arranged horizontally above the content. To get vertical tabs, set the `orientation` parameter to `vertical`. This is useful for narrow windows or when the content is too wide to fit in a horizontal tab layout.
!! Parameters
;filter
: The filter to be inspected
;inputFilter
: Optionally, a filter defining the input titles for the filter to be inspected, defaulting to `[all[tiddlers]]`
;orientation
: Optionally, the orientation of the schematic representation, defaulting to `horizontal`. Set to `vertical` to display the tabs vertically

View File

@@ -0,0 +1,27 @@
caption: inspect
tags: [[Filter Operators]]
title: $:/plugins/tiddlywiki/internals/filterinspection/docs/inspectoperator
type: text/vnd.tiddlywiki
The ''inspect'' operator evaluates a filter with the specified input titles and returns a JSON object containing the input, output and intermediate results of evaluating the specified filter.
|Operator Purpose |Inspect the evaluation of a filter to aid debugging |
|Operator Input |A selection of titles |
|Operator Output |A JSON object containing the input, output and intermediate results of evaluating the specified filter |
|Operator Parameter |The filter to be inspected |
The output JSON object contains the following properties:
* `input`: the input titles passed to the filter
* `inputFilter`: the filter being inspected
* `output`: the output titles resulting from evaluating the filter
* `evaluationTime`: the time taken to evaluate the filter, in milliseconds
* `runs`: an array of objects, each of which represents a single run of the filter. Each object contains the following properties:
** `prefixName`: the name of the prefix operator that was used in this run. Shortcut prefixes like `+` and `-` are expanded to their full names, e.g. `and` and `except`
** `input`: the input titles passed to the prefix operator
** `operations`: an array of objects, each of which represents the evaluation of a single operation that was performed in this run. Each object contains the following properties:
*** `operators`: an array of objects, each of which represents a single operator that was used in this run. Each object contains the following properties:
**** `operatorName`: the name of the operator
**** `operands`: an array of string operands passed to the operator
**** `input`: the input titles passed to the operator
**** `output`: the output titles resulting from evaluating the operator

View File

@@ -0,0 +1,38 @@
title: $:/plugins/tiddlywiki/internals/filterinspection/docs/inspectfiltermacro/examples
tags: [[Macro Examples]]
type: text/vnd.tiddlywiki
\procedure .example(n,eg,egvar)
<$let eg={{{ [<egvar>!is[blank]getvariable[]] :else[<eg>] }}}>
<div class="doc-example">
<$macrocall $name="copy-to-clipboard-above-right" src=<<eg>>/>
<$codeblock code=<<eg>>/>
<$list filter=`[title<.state-prefix>addsuffix{!!title}addsuffix[/]addsuffix[$(n)$]]` variable=".state">
<$reveal state=<<.state>> type="nomatch" text="show">
<dl>
<dd><$button set=<<.state>> setTo="show">Try it</$button></dd>
</dl>
</$reveal>
<$reveal state=<<.state>> type="match" text="show">
<dl>
<dd><$button set=<<.state>> setTo="">Hide</$button></dd>
</dl>
<blockquote class="doc-example-result">
<$transclude $variable="eg" $mode="block"/>
</blockquote>
</$reveal>
</$list>
</div>
</$let>
\end .example
<$macrocall $name=".example" eg="""<$transclude $variable="inspect-filter" filter="1 2 3" inputFilter="[all[tiddlers]]"/>"""/>
<$macrocall $name=".example" eg="""<$transclude $variable="inspect-filter" filter="1 2 3" inputFilter="[all[tiddlers]]" orientation="vertical"/>"""/>
<$macrocall $name=".example" eg="""<$transclude $variable="inspect-filter" filter="[title<currentTiddler>] [{$:/palette}length[]] [[marker]]" inputFilter="[all[tiddlers]]"/>"""/>
<$macrocall $name=".example" eg="""<$transclude $variable="inspect-filter" filter="[all[shadows+tiddlers]tag[$:/tags/MenuBar]!has[draft.of]] -[all[tiddlers+shadows]tag[$:/tags/TopLeftBarlimit[1]then[]else[$:/plugins/tiddlywiki/menubar/items/topleftbar]]" inputFilter="[all[tiddlers]]"/>
"""/>
<$macrocall $name=".example" eg="""<$transclude $variable="inspect-filter" filter="[tags[]prefix[$:/]] :sort[length[]add[1]] +[first[2]tagging[]]" inputFilter="[all[tiddlers]]"/>"""/>

View File

@@ -0,0 +1,5 @@
title: $:/plugins/tiddlywiki/internals/filterinspection/InspectFilterProcedures/Filters/SlowFilter
tags: $:/tags/Filter
filter: [all[tiddlers+shadows]limit[500]] :filter[<currentTiddler>length[]reverse[]multiply[3.14]]
description: [Internals Plugin] A filter that takes a long time to process

View File

@@ -0,0 +1,214 @@
title: $:/plugins/tiddlywiki/internals/filterinspection/InspectFilterProcedures
tags: $:/tags/Global
\whitespace trim
\procedure inspect-list(jsonList)
<$let
transclusion={{{ [<jsonList>indexes[]count[]addprefix[-list-]] }}}
state=<<qualify "$:/temp/filter-inspector/list">>
stateMaxRows={{{ [<state>addsuffix[max-rows]] }}}
maxRows={{{ [<stateMaxRows>get[text]!match[]else[10]] }}}
stateScroll={{{ [<state>addsuffix[scroll]] }}}
>
<$text text=<<stateScroll>>/>
<$scrollable class="tc-box-content-list-scollable" bind=<<stateScroll>>>
<div class="tc-box-content-list">
<$list filter="[<jsonList>jsonindexes[]nsort[]limit<maxRows>]" variable="indexList">
<$list-template>
<div class="tc-box-content-list-item">
<$slot $name="list-item"/>
</div>
</$list-template>
<$list-empty>
<div class="tc-box-content-list-empty">
(No items)
</div>
</$list-empty>
</$list>
<%if [<jsonList>jsonindexes[]count[]compare:number:gt<maxRows>] %>
<div class="tc-box-content-list-more">
<$button class="tc-btn-invisible tc-box-content-full-width-button" style.fill="inherit">
<$action-setfield $tiddler=<<stateMaxRows>> text={{{ [<maxRows>add[10]] }}}/>
{{$:/core/images/chevron-down}}
<$text text="more"/>
</$button>
</div>
<%endif%>
</div>
</$scrollable>
\end inspect-list
\procedure inspect-foldable-text-list(jsonList,class:"tc-box")
<$let
transclusion={{{ [[link-list-]addsuffix<class>] }}}
>
<div class=<<class>>>
<$let
state=<<qualify "$:/temp/filter-inspector/list">>
stateFolded={{{ [<state>addsuffix[folded]] }}}
folded={{{ [<stateFolded>get[text]else[no]match[yes]] }}}
>
<div class="tc-box-header">
<$button class="tc-btn-invisible tc-box-content-full-width-button" style.fill="inherit">
<%if [<folded>match[yes]] %>
<$action-setfield $tiddler=<<stateFolded>> text="no"/>
<%else%>
<$action-setfield $tiddler=<<stateFolded>> text="yes"/>
<%endif%>
<$text text={{{ [<jsonList>jsonindexes[]count[]] }}}/>
{{$:/core/images/right-arrow}}
</$button>
</div>
<%if [<folded>!match[yes]] %>
<div class="tc-box-content">
<$transclude $variable="inspect-list" jsonList=<<jsonList>>>
<$fill $name="list-item">
<$text text={{{ [<jsonList>jsonget<indexList>] }}} />
</$fill>
</$transclude>
</div>
<%endif%>
</$let>
</div>
</$let>
\end inspect-foldable-text-list
\procedure inspect-operator(jsonOperator)
<div class="tc-box tc-inspect-operator-box">
<div class="tc-box-header">
<span class="">
<$text text={{{ [<jsonOperator>jsonget[prefix]] }}} />
<$text text={{{ [<jsonOperator>jsonget[operatorName]] }}} />
</span>
<%if [<jsonOperator>jsonindexes[suffixes]length[]compare:number:gt[0]] %>
<$list filter="[<jsonOperator>jsonindexes[suffixes]nsort[]]" variable="indexSuffix">
<span class="tc-pill">
:<$text text={{{ [<jsonOperator>jsonget[suffixes],<indexSuffix>] }}} />
</span>
</$list>
<%endif%>
<$list filter="[<jsonOperator>jsonindexes[operands]nsort[]]" variable="indexOperand">
<div class="tc-split-pill">
<div class="tc-split-pill-top">
<%if [<jsonOperator>jsonget[parseTree],[operands],<indexOperand>,[variable]match[true]] %>
<$text text="<"/><$text text={{{ [<jsonOperator>jsonget[parseTree],[operands],<indexOperand>,[text]] }}}/><$text text=">"/>
<%elseif [<jsonOperator>jsonget[parseTree],[operands],<indexOperand>,[indirect]match[true]] %>
<$text text="{"/><$text text={{{ [<jsonOperator>jsonget[parseTree],[operands],<indexOperand>,[text]] }}}/><$text text="}"/>
<%else%>
<$text text="["/><$text text={{{ [<jsonOperator>jsonget[parseTree],[operands],<indexOperand>,[text]] }}}/><$text text="]"/>
<%endif%>
</div>
<div class="tc-split-pill-bottom">
<$text text={{{ [<jsonOperator>jsonget[operands],<indexOperand>] }}} />
</div>
</div>
</$list>
</div>
<div class="tc-box-content">
Evaluation time <span class="tc-pill"><$text text={{{ [<jsonOperator>jsonget[evaluationTime]fixed[8]] }}}/></span> milliseconds
</div>
</div>
\end inspect-operator
\procedure inspect-operation(jsonOperation,indexOperation)
<div class="tc-box tc-inspect-operation-box">
<div class="tc-box-header">
<span class="">Evaluation</span>
<span class="tc-pill"><$text text=<<indexOperation>> /></span>
Evaluation time <span class="tc-pill"><$text text={{{ [<jsonOperation>jsonget[evaluationTime]fixed[8]] }}}/></span> milliseconds
</div>
<div class="tc-box-content">
<$list filter="[<jsonOperation>jsonindexes[operators]nsort[]]" variable="indexOperator">
<$let
transclusion={{{ [[operator-]addsuffix<indexOperator>] }}}
jsonOperator={{{ [<jsonOperation>jsonextract[operators],<indexOperator>] }}}
>
<$transclude $variable="inspect-foldable-text-list" jsonList={{{ [<jsonOperator>jsonextract[input]] }}} class="tc-box tc-inspect-input-box"/>
<$transclude $variable="inspect-operator" jsonOperator=<<jsonOperator>>/>
</$let>
</$list>
<$list filter="[<jsonOperation>jsonindexes[operators]nsort[]last[1]]" variable="indexOperator">
<$let
transclusion={{{ [[operator-]addsuffix<indexOperator>] }}}
jsonOperator={{{ [<jsonOperation>jsonextract[operators],<indexOperator>] }}}
>
<$transclude $variable="inspect-foldable-text-list" jsonList={{{ [<jsonOperator>jsonextract[output]] }}} class="tc-box tc-inspect-output-box"/>
</$let>
</$list>
</div>
</div>
\end inspect-operation
\procedure inspect-run(jsonRun)
<$transclude $variable="inspect-foldable-text-list" jsonList={{{ [<jsonRun>jsonextract[input]] }}} class="tc-box tc-inspect-input-box"/>
<div class="tc-box tc-inspect-run-box">
<div class="tc-box-header">
<span class="">
<%if [<jsonRun>jsonget[prefix]!match[]] %>
<$text text={{{ [<jsonRun>jsonget[prefix]] }}} />
<$text text=" shortcut for "/>
<$text text={{{ [<jsonRun>jsonget[prefixName]addprefix[:]] }}} />
<%else%>
<$text text={{{ [<jsonRun>jsonget[prefixName]addprefix[:]] }}} />
<%endif%>
</span>
<%if [<jsonRun>jsonindexes[suffixes]length[]compare:number:gt[0]] %>
<$list filter="[<jsonRun>jsonindexes[suffixes]nsort[]]" variable="indexSuffix">
<span class="tc-pill">
:<$text text={{{ [<jsonRun>jsonget[suffixes],<indexSuffix>] }}} />
</span>
</$list>
<%endif%>
Evaluation time <span class="tc-pill"><$text text={{{ [<jsonRun>jsonget[evaluationTime]fixed[8]] }}}/></span> milliseconds
</div>
<div class="tc-box-content">
<div class="tc-inspect-operations-wrapper">
<$list filter="[<jsonRun>jsonindexes[operations]nsort[]]" variable="indexOperation">
<$let transclusion={{{ [[operation-]addsuffix<indexOperation>] }}}>
<$transclude $variable="inspect-operation" jsonOperation={{{ [<jsonRun>jsonextract[operations],<indexOperation>] }}} indexOperation=<<indexOperation>>/>
</$let>
</$list>
</div>
</div>
</div>
\end inspect-run
\procedure inspect-filter-output-unframed(jsonOutput,orientation:"horizontal")
<$scrollable bind={{{ [<qualify "$:/temp/filter-inspector/">addsuffix<filter>] }}}>
<div class={{{ tc-inspect-filter-box tc-inspect-filter-box-unframed [<orientation>match[horizontal]then[tc-inspect-filter-box-horizontal]] +[join[ ]] }}}>
<$list filter="[<jsonOutput>jsonindexes[runs]nsort[]]" variable="indexRun">
<$let transclusion={{{ [[run-]addsuffix<indexRun>] }}}>
<$transclude $variable="inspect-run" jsonRun={{{ [<jsonOutput>jsonextract[runs],<indexRun>] }}}/>
</$let>
</$list>
<$transclude $variable="inspect-foldable-text-list" jsonList={{{ [<jsonOutput>jsonextract[output]] }}} class="tc-box tc-inspect-output-box"/>
</div>
</$scrollable>
\end inspect-filter-output-unframed
\procedure inspect-filter-output(jsonOutput,orientation:"horizontal")
<$scrollable bind={{{ [<qualify "$:/temp/filter-inspector/">addsuffix<filter>] }}}>
<div class={{{ tc-inspect-filter-box [<orientation>match[horizontal]then[tc-inspect-filter-box-horizontal]] +[join[ ]] }}}>
<div class="tc-box">
<div class="tc-box-header">
Filter
<span class="tc-pill"><$text text={{{ [<jsonOutput>jsonget[inputFilter]] }}}/></span>
Evaluation time <span class="tc-pill"><$text text={{{ [<jsonOutput>jsonget[evaluationTime]fixed[8]] }}}/></span> milliseconds
</div>
<div class="tc-box-content">
<$list filter="[<jsonOutput>jsonindexes[runs]nsort[]]" variable="indexRun">
<$let transclusion={{{ [[run-]addsuffix<indexRun>] }}}>
<$transclude $variable="inspect-run" jsonRun={{{ [<jsonOutput>jsonextract[runs],<indexRun>] }}}/>
</$let>
</$list>
<$transclude $variable="inspect-foldable-text-list" jsonList={{{ [<jsonOutput>jsonextract[output]] }}} class="tc-box tc-inspect-output-box"/>
</div>
</div>
</div>
</$scrollable>
\end inspect-filter-output
\procedure inspect-filter(filter,inputFilter:"[all[tiddlers]]",orientation:"horizontal")
<$transclude $variable="inspect-filter-output" jsonOutput={{{ [subfilter<inputFilter>inspect<filter>] }}} orientation=<<orientation>> />
\end inspect-filter

View File

@@ -0,0 +1,29 @@
/*\
title: $:/plugins/tiddlywiki/internals/filterinspection/modules/inspect.js
type: application/javascript
module-type: filteroperator
Filter operator for inspecting the evaluation of a filter
\*/
"use strict";
var getWrappers = $tw.plugins.internals.getWrappers;
/*
Export our filter function
*/
exports.inspect = function(source,operator,options) {
var inputFilter = operator.operands[0] || "",
results,
wrappers = getWrappers(function(output) {
results = output;
},inputFilter);
// Compile the filter with wrapper functions to log the details
var compiledFilter = options.wiki.compileFilter(inputFilter,{
wrappers: wrappers
});
compiledFilter.call(options.wiki,source,options.widget);
return [JSON.stringify(results)];
};

View File

@@ -0,0 +1,212 @@
/*\
title: $:/plugins/tiddlywiki/internals/filterinspection/modules/startup.js
type: application/javascript
module-type: startup
Install hooks
\*/
"use strict";
// Export name and synchronous status
exports.name = "internals-plugin";
exports.before = ["startup"];
exports.synchronous = true;
exports.startup = function() {
// Publish public methods
$tw.plugins = $tw.plugins || {};
$tw.plugins.internals = {
getWrappers: getWrappers
};
// We accumulate the output of the filter inspection into an array. We can't directly write to the wiki since we might be in
// the middle of the refresh cycle, when writes to the wiki are not allowed
var accumulator = [];
// Add our hook for each filter evaluation
$tw.hooks.addHook("th-filter-evaluation",function(filterString,wrappers) {
// Check that filter observation is enabled
if($tw.wiki.getTiddlerText("$:/config/FilterObservationionEnabled","no") !== "yes") {
return wrappers;
}
// Get the list of filters to be inspected
var inspectedFilters = $tw.wiki.getTiddlersWithTag("$:/tags/InspectableFilter");
// Check whether this is a filter we want to inspect
if(inspectedFilters.indexOf(filterString) === -1) {
return wrappers;
}
// Don't do anything if there are already wrappers
if(wrappers.prefix || wrappers.operation || wrappers.operator) {
return wrappers;
}
// Flush the accumulator, making each filter inspection output record into a separate tiddler
var flushAccumulator = function() {
if(accumulator.length) {
$tw.utils.each(accumulator,function(jsonInspectionOutput) {
// Get the output as a string
var stringInspectionOutput = JSON.stringify(jsonInspectionOutput);
// Compute the log prefix and its length
var logPrefix = `$:/temp/filter-inspection/${ + $tw.utils.stringifyDate(new Date())}: `,
logPrefixLength = logPrefix.length;
// Check if the the output is the same as the last log with the same filter
var logTitles = [];
$tw.wiki.each(function(tiddler,title) {
if(title.substring(logPrefixLength) === jsonInspectionOutput.inputFilter) {
logTitles.push(title);
}
});
var alreadyLogged = false;
$tw.utils.each(logTitles,function(title) {
var jsonLog = $tw.wiki.getTiddlerData(title);
if($tw.utils.isArrayEqual(jsonLog.output,jsonInspectionOutput.output)) {
alreadyLogged = true;
}
});
if(alreadyLogged) {
return;
}
// Add the log tiddler
var tiddlerFields = {
title: `${logPrefix}${jsonInspectionOutput.inputFilter}`,
tags: ["$:/tags/FilterInspectionOutput"],
text: stringInspectionOutput,
filter: jsonInspectionOutput.inputFilter,
type: "application/json"
}
// console.log("Adding " + JSON.stringify(tiddlerFields));
$tw.wiki.addTiddler(new $tw.Tiddler($tw.wiki.getCreationFields(),$tw.wiki.getModificationFields(),tiddlerFields));
});
accumulator = [];
}
};
// Get our wrappers
return getWrappers(function(output) {
// Schedule a flush
if(accumulator.length === 0) {
$tw.utils.nextTick(function() {
flushAccumulator();
});
}
accumulator.push(output);
},filterString);
});
};
/*
Return the wrappers for evaluating a given filter
fnDone is a function that will be called with the output of the filter evaluation as the single argument
inputFilter is the filter to be evaluated
*/
function getWrappers(fnDone,inputFilter) {
// Skeleton output record
var output = {
inputFilter: inputFilter,
input: [],
runs: []
};
// Keep track of where the current run and the current operation are being written
var currentRun,currentOperation;
// The starting evaluation time stamp
var filterStartTime;
// Compile the filter with wrapper functions to log the details
return {
start: function(source) {
// Save the input so that we have it in the output record
source(function(tiddler,title) {
output.input.push(title);
});
// Start the timer
filterStartTime = $tw.utils.timer();
},
prefix: function(filterRunPrefixFunction,operationFunction,innerOptions) {
// Function to be called at the start of each filter run
return function(results,innerSource,innerWidget) {
// Save the details of this filte run
var details ={
input: results.toArray(),
prefixName: innerOptions.prefixName,
prefix: innerOptions.prefix,
suffixes: innerOptions.suffixes,
operations: []
};
// Save the current run so that we can add operations to it
currentRun = details.operations;
// Save the start time
var startTime = $tw.utils.timer();
// Get the filter run prefix function
var innerResults = filterRunPrefixFunction.call(null,operationFunction,innerOptions);
// Invoke the filter run
innerResults(results,innerSource,innerWidget);
// Save the end time
details.evaluationTime = $tw.utils.timer(startTime);
// Save the results of the filter run
details.output = results.toArray();
output.runs.push(details);
};
},
operation: function(operationFunction,operation) {
// Record the operation
var details = {
operators: []
}
// Save the start time
var startTime = $tw.utils.timer();
// Keep track of where the current operation should be being written
currentOperation = details.operators;
// Invoke the operation
operationFunction();
// Save the end time
details.evaluationTime = $tw.utils.timer(startTime);
// Save the results of the operation
currentRun.push(details);
},
operator: function(operatorFunction,innerSource,innerOperator,innerOptions) {
// Record the operator
var details = {
operatorName: innerOperator.operatorName,
operands: innerOperator.operands,
parseTree: innerOperator.parseTree,
prefix: innerOperator.prefix,
suffix: innerOperator.suffix,
suffixes: innerOperator.suffixes,
regexp: innerOperator.regexp,
input: []
};
// Copy the input
innerSource(function(tiddler,title) {
details.input.push(title);
});
// Save this operation
currentOperation.push(details);
// Save the start time
var startTime = $tw.utils.timer();
// Invoke the operator
var innerResults = operatorFunction.apply(null,Array.prototype.slice.call(arguments,1));
// Save the end time
details.evaluationTime = $tw.utils.timer(startTime);
// Make sure the results are an array so that we can store them
if(!$tw.utils.isArray(innerResults)) {
var resultArray = [];
innerResults(function(tiddler,title) {
resultArray.push(title);
});
innerResults = resultArray;
}
// Store the results in the output
details.output = innerResults;
// Return the results
return innerResults;
},
done: function(results) {
// Store evaluation time
output.evaluationTime = $tw.utils.timer(filterStartTime);
// Save the results of the filter evaluation
output.output = results;
// console.log(`Inspected ${JSON.stringify(output,null,4)}`);
// Invoke the done function
if(fnDone) {
fnDone(output);
}
}
};
}

View File

@@ -0,0 +1,188 @@
title: $:/plugins/tiddlywiki/internals/filterinspection/styles
tags: $:/tags/Stylesheet
\rules only filteredtranscludeinline transcludeinline macrodef macrocallinline macrocallblock
/*
** Headed box and pills
*/
.tc-box {
border: 1px solid var(--box-foreground-color););
border-radius: 4px;
margin: 0 0 0.5em 0;
}
.tc-box .tc-box {
margin: 0.5em;
}
.tc-box-header {
padding: 0.25em;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
color: var(--box-background-color);
fill: var(--box-background-color);
background-color: var(--box-foreground-color);
display: flex;
align-items: center;
}
.tc-box-header svg {
width: 1em;
height: 1em;
margin-left: 0.25em;
}
.tc-box-header button {
color: var(--box-background-color);
background-color: var(--box-foreground-color);
}
.tc-box-content {
min-width: 4em;
padding: 0.25em;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
color: var(--box-foreground-color);
fill: var(--box-foreground-color);
background-color: var(--box-background-color);
}
.tc-box-content-list-scollable {
margin: -0.25em;
max-height: 25vh;
width: 12em;
}
.tc-box-content-list {
}
.tc-box-content-list-item {
font-size: 0.7em;
line-height: 1.1;
padding: 0.25em;
}
.tc-box-content-list-more {
font-size: 0.7em;
line-height: 1.1;
padding: 0.25em;
}
.tc-box-content-full-width-button {
display: block;
width: 100%;
text-align: left;
}
.tc-box-content-list-more,
.tc-box-content-list-more .tc-btn-invisible {
color: var(--box-background-color);
fill: var(--box-background-color);
background-color: var(--box-foreground-color);
}
.tc-box-content-list-item:nth-child(even) {
background: rgb(255, 255, 255, 0.5);
}
.tc-box-content-list-empty {
}
.tc-pill {
padding: 0.125em 0.25em;
margin: 0 0.25em;
border-radius: 6px;
color: var(--box-foreground-color);
background-color: var(--box-background-color);
}
.tc-split-pill {
display: inline-block;
padding: 0.125em 0.25em;
margin: 0 0.25em;
border-radius: 6px;
color: var(--box-foreground-color);
background-color: var(--box-background-color);
}
.tc-split-pill-top {
border-bottom: 1px solid var(--box-foreground-color);
}
.tc-split-pill-bottom {
}
/*
** Filter Inspection
*/
.tc-inspect-filter-box {
--box-background-color: <<colour code-border>>;
--box-foreground-color: <<colour code-foreground>>;
}
.tc-inspectable-filter-box {
--box-background-color: #ffebe1;
--box-foreground-color: #ff3f00;
}
.tc-inspectable-filter-trace-box {
--box-background-color: #ffdec1;
--box-foreground-color: #ff7a00;
}
.tc-inspect-filter-box.tc-inspect-filter-box-horizontal {
display: flex;
}
.tc-inspect-filter-box.tc-inspect-filter-box-horizontal > .tc-box > .tc-box-content,
.tc-inspect-filter-box.tc-inspect-filter-box-horizontal.tc-inspect-filter-box-unframed,
.tc-inspect-filter-box.tc-inspect-filter-box-horizontal .tc-inspect-run-box > .tc-box-content,
.tc-inspect-filter-box.tc-inspect-filter-box-horizontal .tc-inspect-operation-box > .tc-box-content,
.tc-inspect-filter-box.tc-inspect-filter-box-horizontal .tc-inspect-operator-box > .tc-box-content {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
}
.tc-inspectable-filter-box > .tc-box-content > ol {
list-style: none;
padding-left: 0;
}
.tc-inspect-operations-wrapper {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
}
.tc-inspect-run-box {
--box-background-color: #ffffcc;
--box-foreground-color: #444400;
}
.tc-inspect-operation-box {
--box-background-color: #ccffcc;
--box-foreground-color: #004400;
}
.tc-inspect-operator-box {
--box-background-color: #ffcccc;
--box-foreground-color: #440000;
}
.tc-box.tc-inspect-input-box {
--box-background-color: #ffccff;
--box-foreground-color: #440044;
}
.tc-box.tc-inspect-output-box {
--box-background-color: #4fd3d3;
--box-foreground-color: #004444;
}

View File

@@ -0,0 +1,28 @@
title: $:/plugins/tiddlywiki/internals/filterinspection/tests/wikitext/Simple
description: Test filter inspection
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
\procedure test-filter()
1 2 3 :sort[length[]add[1]]
\end
\function test-filter-wrapper()
[inspect<test-filter>]
\end
<$text text=<<test-filter>>/>
-
<$text text={{{ [<test-filter-wrapper>jsonindexes[]join[,]] }}}/>
-
<$text text={{{ [<test-filter-wrapper>jsonindexes[inputFilter]join[,]] }}}/>
-
<$text text={{{ [<test-filter-wrapper>jsonindexes[runs]join[,]] }}}/>
+
title: ExpectedResult
<p>1 2 3 :sort[length[]add[1]]-evaluationTime,input,inputFilter,output,runs--0,1,2,3</p>

View File

@@ -0,0 +1,3 @@
title: $:/plugins/tiddlywiki/internals/filterinspection/viewtemplatebody
<$transclude $variable="inspect-filter-output" jsonOutput={{!!text}} />

View File

@@ -0,0 +1,5 @@
title: $:/plugins/tiddlywiki/internals/filterinspection/cascades/viewtemplatebody
tags: $:/tags/ViewTemplateBodyFilter
list-before:
[tag[$:/tags/FilterInspectionOutput]type[application/json]then[$:/plugins/tiddlywiki/internals/filterinspection/viewtemplatebody]]

View File

@@ -0,0 +1,2 @@
title: $:/state/tab--1498284803
text: $:/core/ui/AdvancedSearch/Filter

View File

@@ -0,0 +1,2 @@
title: $:/state/tab--251342953
text: $:/plugins/tiddlywiki/internals/filterinspection/InspectResultsTab

View File

@@ -0,0 +1,2 @@
title: $:/config/FilterObservationionEnabled
text: yes

View File

@@ -0,0 +1,5 @@
title: $:/plugins/tiddlywiki/internals/testfilter
tags: $:/tags/InspectableFilter
type: text/plain
[list[$:/StoryList]]

View File

@@ -2,6 +2,6 @@
"title": "$:/plugins/tiddlywiki/internals",
"name": "Internals",
"description": "Tools for exploring the internals of TiddlyWiki",
"list": "readme",
"list": "readme docs",
"stability": "STABILITY_2_STABLE"
}

View File

@@ -1,10 +1,24 @@
title: $:/plugins/tiddlywiki/internals/readme
This plugin adds features to help explore the internals of TiddlyWiki:
This plugin adds several features to help explore the internals of TiddlyWiki, and to debug wikitext and filters.
* New preview panes showing:
** the parse tree
** the widget tree
** the raw HTML output
!! Inspecting Filter Traces
The first two include a dropdown for choosing block vs. inline parsing mode.
Filter inspection is based on a schematic visualisation that traces all the steps involved in evaluating a filter. These traces can be generated and accessed in several ways:
* Via two new tabs under the Advanced Search filter results:
** The ''Inspect'' tab shows the schematic trace for the current filter
** The ''Observe'' tab allows the current filter to be logged in the background. A new trace is generated every time the filter is evaluated, regardless of the context, if the results are different from previous evaluations
* Directly using the `<<inspect-filter>>` procedure, or the underlying `inspect[]` operator
Note that observing a filter is not the same as logging it. Observing a filter means that the filter is evaluated in the background, and a new trace is generated every time the filter is evaluated, regardless of the context. Logging a filter means that the filter is evaluated in the background, but only if the results are different from previous evaluations.
Filter observation has a performance impact, and disables certain optimisations such as caching of compiled filters.
!! Inspecting Parse Trees and Widget Trees
New preview panes for the tiddler editor that show
* the parse tree
* the widget tree
* the raw HTML output

View File

@@ -1,4 +0,0 @@
title: $:/plugins/tiddlywiki/internals/styles
tags: $:/tags/Stylesheet
\rules only filteredtranscludeinline transcludeinline macrodef macrocallinline macrocallblock