Skip to content

Commit

Permalink
Merge pull request #828 from obgnail/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
obgnail authored Nov 1, 2024
2 parents 372b4ac + a9dd5f0 commit 19d8743
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 21 deletions.
158 changes: 140 additions & 18 deletions plugin/search_multi.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class searchMultiKeywordPlugin extends BasePlugin {
html = () => `
<div id="plugin-search-multi" class="plugin-common-modal plugin-common-hidden">
<div id="plugin-search-multi-input">
<input type="text" placeholder="多关键字查找">
<input type="text">
<div class="plugin-search-multi-btn-group">
<span class="option-btn" action="searchGrammarModal" ty-hint="查看搜索语法">
<div class="fa fa-info-circle"></div>
Expand Down Expand Up @@ -77,6 +77,7 @@ class searchMultiKeywordPlugin extends BasePlugin {
});
this.entities.buttonGroup.addEventListener("click", ev => {
const btn = ev.target.closest(".option-btn");
if (!btn) return;
const action = btn.getAttribute("action");
this.actionMap[action] && this.actionMap[action](btn);
})
Expand Down Expand Up @@ -172,6 +173,11 @@ class searchMultiKeywordPlugin extends BasePlugin {
}
}

showExplain = ast => {
const title = this.searchHelper.toExplain(ast);
this.entities.input.setAttribute("title", title);
}

getAST = input => {
input = input.trim();
if (!input) return;
Expand All @@ -188,6 +194,7 @@ class searchMultiKeywordPlugin extends BasePlugin {
const ast = this.getAST(input);
if (!ast) return;

this.showExplain(ast);
const checker = dataset => this.searchHelper.check(ast, dataset);
const appendItem = this.appendItemFunc(rootPath, checker);
const verifyExt = filename => {
Expand Down Expand Up @@ -276,9 +283,8 @@ class SearchHelper {
}

const keywordMatch = (scope, operator, operand, queryResult) => {
queryResult = this.config.CASE_SENSITIVE ? queryResult : queryResult.toLowerCase();
// operand 先前已经做了大小写转化处理,这里不再需要做了
return queryResult.includes(operand);
queryResult = this.config.CASE_SENSITIVE ? queryResult : queryResult.toLowerCase(); // operand 先前已经做了大小写转化处理,这里不再需要做了
return (operator === "=" || operator === "==") ? queryResult === operand : queryResult.includes(operand);
}
const regexpMatch = (scope, operator, operand, queryResult) => {
const flag = this.config.CASE_SENSITIVE ? undefined : "i";
Expand All @@ -291,6 +297,9 @@ class SearchHelper {
if (operator !== ":" && operator !== "=" && operator !== "==") {
throw new Error(`${scope}’s operator can only be ":" or "=" or "=="`);
}
if (type === "regexp" && operator !== ":") {
throw new Error(`RegExp’s operator can only be ":"`);
}
}
const numberTest = (scope, operator, operand, type) => {
if (operator === ":") {
Expand Down Expand Up @@ -432,31 +441,144 @@ class SearchHelper {
return evaluate(ast);
}

toMermaid(ast) {
let idx = 0;
const { KEYWORD, PHRASE, REGEXP, OR, AND, NOT } = this.parser.TYPE;

function getName(node) {
if (node._shortName) return node._shortName;
node._shortName = "T" + ++idx;
const prefix = node.negated ? "-" : "";
const value = node.type === REGEXP ? `/${node.value}/` : node.value;
return `${node._shortName}("${prefix}${node.scope}${node.operator} ${value}")`;
}

function link(left, right) {
return left.tail.flatMap(t => right.head.map(h => `${getName(t)} --> ${getName(h)}`));
}

function _eval(node, negated) {
let left, right;
switch (node.type) {
case AND:
left = _eval(node.left, negated);
right = _eval(node.right, negated);
node.head = left.head;
node.tail = right.tail;
node.result = [...left.result, ...link(left, right), ...right.result];
return node
case OR:
left = _eval(node.left, negated);
right = _eval(node.right, negated);
node.head = [...left.head, ...right.head];
node.tail = [...left.tail, ...right.tail];
node.result = [...left.result, ...right.result];
return node
case NOT:
left = node.left ? _eval(node.left, negated) : { result: [], head: [], tail: [] };
right = _eval(node.right, !negated);
node.head = node.left ? left.head : right.head;
node.tail = right.tail;
node.result = [...left.result, ...link(left, right), ...right.result];
return node
case KEYWORD:
case PHRASE:
case REGEXP:
node.negated = negated;
node.head = [node];
node.tail = [node];
node.result = [];
return node
default:
throw new Error(`Unknown node type: ${node.type}`);
}
}

const { head, tail, result } = _eval(ast);
const start = head.map(h => `S --> ${getName(h)}`);
const end = tail.map(t => `${getName(t)} --> E`);
return ["graph LR", "S(Start)", "E(End)", ...result, ...start, ...end].join("\n");
}

toExplain(ast) {
const { KEYWORD, PHRASE, REGEXP, OR, AND, NOT } = this.parser.TYPE;
const scopeMap = { default: "默认范围", file: "文件名", path: "路径", ext: "扩展名", content: "内容", frontmatter: "FrontMatter", size: "体积", time: "修改时间" };
const operatorMap = { ":": "包含", "==": "等于", ">=": "大于等于", "<=": "小于等于", ">": "大于", "<": "小于", "=": "等于" };

function getName(node) {
const scope = scopeMap[node.scope];
const negated = node.negated ? "不" : "";
const operator = node.type === REGEXP ? "匹配正则" : operatorMap[node.operator];
const value = node.type === REGEXP ? `/${node.value}/` : node.value;
return `「${scope}${negated}${operator}${value}」`;
}

function link(left, right) {
return left.result.flatMap(lPath => right.result.map(rPath => [...lPath, ...rPath]));
}

function _eval(node, negated) {
let left, right;
switch (node.type) {
case AND:
left = _eval(node.left, negated);
right = _eval(node.right, negated);
node.result = link(left, right);
return node
case OR:
left = _eval(node.left, negated);
right = _eval(node.right, negated);
node.result = [...left.result, ...right.result];
return node
case NOT:
left = node.left ? _eval(node.left, negated) : { result: [[]], head: [], tail: [] };
right = _eval(node.right, !negated);
node.result = link(left, right);
return node
case KEYWORD:
case PHRASE:
case REGEXP:
node.negated = negated;
node.result = [[node]];
return node
default:
throw new Error(`Unknown node type: ${node.type}`);
}
}

const { result } = _eval(ast);
const content = result
.map(path => path.map(e => getName(e)).join("且"))
.map((path, idx) => `${idx + 1}. ${path}`)
.join("\n")
return "搜索满足如下任意一个要求的文件:\n" + content
}

showGrammar() {
const scope = Array.from(this.qualifiers.keys());
const operator = Array.from(Object.keys(this.operator));
const table1 = `
<table>
<tr><th>关键字</th><th>说明</th></tr>
<tr><td>whitespace</td><td>表示与文档应该同时包含全部关键词</td></tr>
<tr><td>|</td><td>表示或文档应该包含关键词之一,等价于 OR</td></tr>
<tr><td>-</td><td>表示非文档不能包含关键词</td></tr>
<tr><td>""</td><td>词组</td></tr>
<tr><td>qualifier</td><td>限定查找范围:${scope.join(" | ")}<br/>默认值 default = path + content</td></tr>
<tr><td>//</td><td>JavaScript 风格的正则表达式</td></tr>
<tr><td>()</td><td>小括号用于调整运算顺序</td></tr>
<tr><td>whitespace</td><td>表示与文档应该同时包含全部关键词</td></tr>
<tr><td>|</td><td>表示或文档应该包含关键词之一,等价于 OR</td></tr>
<tr><td>-</td><td>表示非文档不能包含关键词</td></tr>
<tr><td>""</td><td>表示词组。双引号里的空格不再视为与,而是词组的一部分</td></tr>
<tr><td>qualifier</td><td>限定查找范围:${scope.join(" | ")}<br/>默认值 default = path + content(将文件内容和文件路径作为查找范围)</td></tr>
<tr><td>/RegExp/</td><td>JavaScript 风格的正则表达式</td></tr>
<tr><td>()</td><td>小括号用于调整运算顺序</td></tr>
</table>`

const table2 = `
<table>
<tr><th>示例</th><th>搜索文档</th></tr>
<tr><td>foo bar</td><td>包含 foobar</td></tr>
<tr><td>foo OR bar</td><td>包含 foobar</td></tr>
<tr><td>foo bar -zoo</td><td>包含 foo 和 bar 但不含 zoo</td></tr>
<tr><td>"foo bar"</td><td>包含 foo bar 这一词组</td></tr>
<tr><td>path:/[a-z]{3}/ content:bar</td><td>路径匹配 [a-z]{3} 且内容包含 bar</td></tr>
<tr><td>file:(info | warn | err) -ext:log</td><td>文件名包含 info 或 warn 或 err,但扩展名不含 log</td></tr>
<tr><td>file:foo size>=100k time>"2024-03-12"</td><td>文件名包含 foo,且体积大于等于 100k,且更新时间大于 2024-03-12</td></tr>
<tr><td>sour pear</td><td>包含 sourpear。等价于 default:sour default:pear</td></tr>
<tr><td>sour OR pear</td><td>包含 sourpear。等价于 default:sour | default:pear</td></tr>
<tr><td>"sour pear"</td><td>包含 sour pear 这一词组。等价于 default:"sour pear"</td></tr>
<tr><td>sour pear -apple</td><td>包含 sour 和 pear,且不含 apple</td></tr>
<tr><td>path:/[a-z]{3}/ content:abc</td><td>路径匹配 [a-z]{3}且内容包含 abc</td></tr>
<tr><td>file:(info | warn | err) -ext:log</td><td>文件名包含 info 或 warn 或 err,且扩展名不含 log</td></tr>
<tr><td>frontmatter:日记 size>=100k time>2024-03-12</td><td>YAML Front Matter 包含日记,且文件尺寸大于等于 100k,且文件更新时间大于 2024-03-12</td></tr>
</table>`

const content = `
Expand Down
6 changes: 3 additions & 3 deletions plugin/test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
class testPlugin extends BasePlugin {
exportVar = () => {
global.require = require;
global.module = module;
global.__require__ = require;
global.__module__ = module;
}

openDevTools = () => {
const objGetter = () => File && File.editor && File.editor.library;
const callback = () => setTimeout(() => ClientCommand.close(), 1000);
const callback = () => setTimeout(() => ClientCommand.close(), 500);
this.utils.decorate(objGetter, "openFileInNewWindow", null, callback);
JSBridge.invoke("window.toggleDevTools");
}
Expand Down

0 comments on commit 19d8743

Please sign in to comment.