From 4923c8a177bd396f708c2a7d6138d5bf3022567b Mon Sep 17 00:00:00 2001 From: DominikDoom Date: Tue, 20 Jun 2023 18:22:08 +0200 Subject: [PATCH] Add first-difference based wildcard completion along with an option to choose the wanted mode --- javascript/tagAutocomplete.js | 48 +++++++++++++++++++++++------- scripts/tag_autocomplete_helper.py | 1 + 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/javascript/tagAutocomplete.js b/javascript/tagAutocomplete.js index 64d1be5..ddd63fc 100644 --- a/javascript/tagAutocomplete.js +++ b/javascript/tagAutocomplete.js @@ -199,6 +199,7 @@ async function syncOptions() { replaceUnderscores: opts["tac_replaceUnderscores"], escapeParentheses: opts["tac_escapeParentheses"], appendComma: opts["tac_appendComma"], + wildcardCompletionMode: opts["tac_wildcardCompletionMode"], // Alias settings alias: { searchByAlias: opts["tac_alias.searchByAlias"], @@ -347,7 +348,7 @@ const RUBY_TAG_REGEX = /[\w\d<][\w\d' \-?!/$%]{2,}>?/g; const TAG_REGEX = new RegExp(`${POINTY_REGEX.source}|${COMPLETED_WILDCARD_REGEX.source}|${NORMAL_TAG_REGEX.source}`, "g"); // On click, insert the tag into the prompt textbox with respect to the cursor position -async function insertTextAtCursor(textArea, result, tagword, tabCompleted = false) { +async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithoutChoice = false) { let text = result.text; let tagType = result.type; @@ -371,16 +372,37 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompleted = fals } } - if (tagType === ResultType.wildcardFile && tabCompleted && sanitizedText.includes("/")) { - let regexMatch = sanitizedText.match(new RegExp(`${escapeRegExp(tagword)}([^/]*\\/?)`, "i")); - - if (regexMatch) { - let pathPart = regexMatch[0]; - // In case the completion would have just added a slash, try again one level deeper - if (pathPart === `${tagword}/`) { - pathPart = sanitizedText.match(new RegExp(`${escapeRegExp(tagword)}\\/([^/]*\\/?)`, "i"))[0]; + if (tagType === ResultType.wildcardFile + && tabCompletedWithoutChoice + && TAC_CFG.wildcardCompletionMode !== "Always fully" + && sanitizedText.includes("/")) { + if (TAC_CFG.wildcardCompletionMode === "To next folder level") { + let regexMatch = sanitizedText.match(new RegExp(`${escapeRegExp(tagword)}([^/]*\\/?)`, "i")); + if (regexMatch) { + let pathPart = regexMatch[0]; + // In case the completion would have just added a slash, try again one level deeper + if (pathPart === `${tagword}/`) { + pathPart = sanitizedText.match(new RegExp(`${escapeRegExp(tagword)}\\/([^/]*\\/?)`, "i"))[0]; + } + sanitizedText = pathPart; + } + } else if (TAC_CFG.wildcardCompletionMode === "To first difference") { + let firstDifference = 0; + let longestResult = results.map(x => x.text.length).reduce((a, b) => Math.max(a, b)); + // Compare the results to each other to find the first point where they differ + for (let i = 0; i < longestResult; i++) { + let char = results[0].text[i]; + if (results.every(x => x.text[i] === char)) { + firstDifference++; + } else { + break; + } + } + // Don't cut off the __ at the end if it is already the full path + if (firstDifference < longestResult) { + // +2 because the sanitized text already has the __ at the start but the matched text doesn't + sanitizedText = sanitizedText.substring(0, firstDifference + 2); } - sanitizedText = pathPart; } } @@ -968,10 +990,14 @@ function navigateInList(textArea, event) { } break; case keys["ChooseFirstOrSelected"]: + let withoutChoice = false; if (selectedTag === null) { selectedTag = 0; + withoutChoice = true; + } else if (TAC_CFG.wildcardCompletionMode === "To next folder level") { + withoutChoice = true; } - insertTextAtCursor(textArea, results[selectedTag], tagword, true); + insertTextAtCursor(textArea, results[selectedTag], tagword, withoutChoice); break; case keys["Close"]: hideResults(textArea); diff --git a/scripts/tag_autocomplete_helper.py b/scripts/tag_autocomplete_helper.py index 5e00d11..a191dd2 100644 --- a/scripts/tag_autocomplete_helper.py +++ b/scripts/tag_autocomplete_helper.py @@ -318,6 +318,7 @@ def on_ui_settings(): shared.opts.add_option("tac_replaceUnderscores", shared.OptionInfo(True, "Replace underscores with spaces on insertion", section=TAC_SECTION)) shared.opts.add_option("tac_escapeParentheses", shared.OptionInfo(True, "Escape parentheses on insertion", section=TAC_SECTION)) shared.opts.add_option("tac_appendComma", shared.OptionInfo(True, "Append comma on tag autocompletion", section=TAC_SECTION)) + shared.opts.add_option("tac_wildcardCompletionMode", shared.OptionInfo("To next folder level", "How to complete nested wildcard paths (e.g. \"hair/colours/light/...\")", gr.Dropdown, lambda: {"choices": ["To next folder level","To first difference","Always fully"]}, section=TAC_SECTION)) # Alias settings shared.opts.add_option("tac_alias.searchByAlias", shared.OptionInfo(True, "Search by alias", section=TAC_SECTION)) shared.opts.add_option("tac_alias.onlyShowAlias", shared.OptionInfo(False, "Only show alias", section=TAC_SECTION))