mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-29 08:58:53 -03:00
fix(autocomplete): make accept key behavior configurable (#863)
This commit is contained in:
@@ -3,6 +3,7 @@ import { app } from "../../scripts/app.js";
|
||||
import { TextAreaCaretHelper } from "./textarea_caret_helper.js";
|
||||
import {
|
||||
getAutocompleteAppendCommaPreference,
|
||||
getAutocompleteAcceptKeyPreference,
|
||||
getPromptTagAutocompletePreference,
|
||||
getTagSpaceReplacementPreference,
|
||||
} from "./settings.js";
|
||||
@@ -121,6 +122,24 @@ function formatAutocompleteInsertion(text = '') {
|
||||
return getAutocompleteAppendCommaPreference() ? `${trimmed},` : `${trimmed} `;
|
||||
}
|
||||
|
||||
function shouldAcceptAutocompleteKey(key) {
|
||||
const mode = getAutocompleteAcceptKeyPreference();
|
||||
|
||||
if (mode === 'tab_only') {
|
||||
return key === 'Tab';
|
||||
}
|
||||
|
||||
if (mode === 'enter_only') {
|
||||
return key === 'Enter';
|
||||
}
|
||||
|
||||
return key === 'Tab' || key === 'Enter';
|
||||
}
|
||||
|
||||
function normalizeAutocompleteMatchText(text = '') {
|
||||
return text.toLowerCase().replace(/[-_\s']/g, '');
|
||||
}
|
||||
|
||||
const AUTOCOMPLETE_METADATA_VERSION = 1;
|
||||
|
||||
function createAutocompleteMetadataBase(textWidgetName = 'text') {
|
||||
@@ -873,6 +892,92 @@ class AutoComplete {
|
||||
return { matched: false, isExactMatch: false };
|
||||
}
|
||||
|
||||
_getLiveSearchTermForAcceptance() {
|
||||
const rawSearchTerm = this.getSearchTerm(this.inputElement.value);
|
||||
|
||||
if (this.modelType === 'embeddings') {
|
||||
const match = rawSearchTerm.match(/^emb:(.*)$/i);
|
||||
return (match?.[1] || '').trim();
|
||||
}
|
||||
|
||||
if (this.modelType === 'prompt') {
|
||||
const embeddingMatch = rawSearchTerm.match(/^emb:(.*)$/i);
|
||||
if (embeddingMatch) {
|
||||
return (embeddingMatch[1] || '').trim();
|
||||
}
|
||||
|
||||
const commandResult = this._parseCommandInput(rawSearchTerm);
|
||||
return commandResult.searchTerm ?? rawSearchTerm;
|
||||
}
|
||||
|
||||
return rawSearchTerm;
|
||||
}
|
||||
|
||||
_getPreferredSelectedIndex(searchTerm = '') {
|
||||
if (!this.items?.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (this.showingCommands) {
|
||||
if (this.selectedIndex >= 0 && this.selectedIndex < this.items.length) {
|
||||
return this.selectedIndex;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const trimmedSearchTerm = searchTerm.trim();
|
||||
if (!trimmedSearchTerm) {
|
||||
if (this.selectedIndex >= 0 && this.selectedIndex < this.items.length) {
|
||||
return this.selectedIndex;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const searchLower = trimmedSearchTerm.toLowerCase();
|
||||
const normalizedSearch = normalizeAutocompleteMatchText(trimmedSearchTerm);
|
||||
let bestIndex = -1;
|
||||
let bestScore = -Infinity;
|
||||
|
||||
this.items.forEach((item, index) => {
|
||||
const displayText = this._getDisplayText(item);
|
||||
const textLower = displayText.toLowerCase();
|
||||
const normalizedText = normalizeAutocompleteMatchText(displayText);
|
||||
let score = -1;
|
||||
|
||||
if (textLower === searchLower) {
|
||||
score = 5000;
|
||||
} else if (normalizedText === normalizedSearch) {
|
||||
score = 4500;
|
||||
} else if (textLower.startsWith(searchLower)) {
|
||||
score = 4000;
|
||||
} else if (normalizedText.startsWith(normalizedSearch)) {
|
||||
score = 3500;
|
||||
} else if (textLower.includes(searchLower)) {
|
||||
score = 3000;
|
||||
} else if (normalizedText.includes(normalizedSearch)) {
|
||||
score = 2500;
|
||||
}
|
||||
|
||||
if (score > -1) {
|
||||
score -= index;
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
bestIndex = index;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (bestIndex !== -1) {
|
||||
return bestIndex;
|
||||
}
|
||||
|
||||
if (this.selectedIndex >= 0 && this.selectedIndex < this.items.length) {
|
||||
return this.selectedIndex;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
async search(term = '', endpoint = null) {
|
||||
try {
|
||||
this.currentSearchTerm = term;
|
||||
@@ -1135,9 +1240,9 @@ class AutoComplete {
|
||||
lastChild.style.borderBottom = 'none';
|
||||
}
|
||||
|
||||
// Auto-select first item
|
||||
// Auto-select immediately so accept keys remain stable.
|
||||
if (this.items.length > 0) {
|
||||
setTimeout(() => this.selectItem(0), 100);
|
||||
this.selectItem(0);
|
||||
}
|
||||
|
||||
// Update virtual scroll height for virtual scrolling mode
|
||||
@@ -1300,11 +1405,10 @@ class AutoComplete {
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-select the first item with a small delay
|
||||
// Auto-select immediately so accept keys do not fall through
|
||||
// to native focus traversal while the dropdown is visible.
|
||||
if (this.items.length > 0) {
|
||||
setTimeout(() => {
|
||||
this.selectItem(0);
|
||||
}, 100);
|
||||
this.selectItem(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1989,8 +2093,20 @@ class AutoComplete {
|
||||
|
||||
case 'Enter':
|
||||
case 'Tab':
|
||||
e.preventDefault();
|
||||
if (!shouldAcceptAutocompleteKey(e.key)) {
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
const liveSearchTerm = this._getLiveSearchTermForAcceptance();
|
||||
const preferredIndex = this._getPreferredSelectedIndex(liveSearchTerm);
|
||||
if (preferredIndex !== -1 && preferredIndex !== this.selectedIndex) {
|
||||
this.selectItem(preferredIndex);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.selectedIndex >= 0 && this.selectedIndex < this.items.length) {
|
||||
e.preventDefault();
|
||||
if (this.showingCommands) {
|
||||
// Insert command
|
||||
this._insertCommand(this.items[this.selectedIndex].command);
|
||||
|
||||
@@ -16,6 +16,12 @@ const PROMPT_TAG_AUTOCOMPLETE_DEFAULT = true;
|
||||
const AUTOCOMPLETE_APPEND_COMMA_SETTING_ID = "loramanager.autocomplete_append_comma";
|
||||
const AUTOCOMPLETE_APPEND_COMMA_DEFAULT = true;
|
||||
|
||||
const AUTOCOMPLETE_ACCEPT_KEY_SETTING_ID = "loramanager.autocomplete_accept_key";
|
||||
const AUTOCOMPLETE_ACCEPT_KEY_DEFAULT = "both";
|
||||
const AUTOCOMPLETE_ACCEPT_KEY_OPTION_BOTH = "Tab or Enter";
|
||||
const AUTOCOMPLETE_ACCEPT_KEY_OPTION_TAB_ONLY = "Tab only";
|
||||
const AUTOCOMPLETE_ACCEPT_KEY_OPTION_ENTER_ONLY = "Enter only";
|
||||
|
||||
const TAG_SPACE_REPLACEMENT_SETTING_ID = "loramanager.tag_space_replacement";
|
||||
const TAG_SPACE_REPLACEMENT_DEFAULT = false;
|
||||
|
||||
@@ -186,6 +192,41 @@ const getAutocompleteAppendCommaPreference = (() => {
|
||||
};
|
||||
})();
|
||||
|
||||
const getAutocompleteAcceptKeyPreference = (() => {
|
||||
let settingsUnavailableLogged = false;
|
||||
|
||||
return () => {
|
||||
const settingManager = app?.extensionManager?.setting;
|
||||
if (!settingManager || typeof settingManager.get !== "function") {
|
||||
if (!settingsUnavailableLogged) {
|
||||
console.warn("LoRA Manager: settings API unavailable, using default autocomplete accept key setting.");
|
||||
settingsUnavailableLogged = true;
|
||||
}
|
||||
return AUTOCOMPLETE_ACCEPT_KEY_DEFAULT;
|
||||
}
|
||||
|
||||
try {
|
||||
const value = settingManager.get(AUTOCOMPLETE_ACCEPT_KEY_SETTING_ID);
|
||||
if (value === AUTOCOMPLETE_ACCEPT_KEY_OPTION_TAB_ONLY) {
|
||||
return "tab_only";
|
||||
}
|
||||
if (value === AUTOCOMPLETE_ACCEPT_KEY_OPTION_ENTER_ONLY) {
|
||||
return "enter_only";
|
||||
}
|
||||
if (value === AUTOCOMPLETE_ACCEPT_KEY_OPTION_BOTH || value == null) {
|
||||
return AUTOCOMPLETE_ACCEPT_KEY_DEFAULT;
|
||||
}
|
||||
return value;
|
||||
} catch (error) {
|
||||
if (!settingsUnavailableLogged) {
|
||||
console.warn("LoRA Manager: unable to read autocomplete accept key setting, using default.", error);
|
||||
settingsUnavailableLogged = true;
|
||||
}
|
||||
return AUTOCOMPLETE_ACCEPT_KEY_DEFAULT;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
const getTagSpaceReplacementPreference = (() => {
|
||||
let settingsUnavailableLogged = false;
|
||||
|
||||
@@ -332,7 +373,20 @@ app.registerExtension({
|
||||
type: "boolean",
|
||||
defaultValue: AUTOCOMPLETE_APPEND_COMMA_DEFAULT,
|
||||
tooltip: "When enabled, accepted autocomplete suggestions append ', ' to the inserted text.",
|
||||
category: ["LoRA Manager", "Autocomplete", "Behavior"],
|
||||
category: ["LoRA Manager", "Autocomplete", "Append comma"],
|
||||
},
|
||||
{
|
||||
id: AUTOCOMPLETE_ACCEPT_KEY_SETTING_ID,
|
||||
name: "Autocomplete accept key",
|
||||
type: "combo",
|
||||
options: [
|
||||
AUTOCOMPLETE_ACCEPT_KEY_OPTION_BOTH,
|
||||
AUTOCOMPLETE_ACCEPT_KEY_OPTION_TAB_ONLY,
|
||||
AUTOCOMPLETE_ACCEPT_KEY_OPTION_ENTER_ONLY,
|
||||
],
|
||||
defaultValue: AUTOCOMPLETE_ACCEPT_KEY_OPTION_BOTH,
|
||||
tooltip: "Choose which key accepts the selected autocomplete suggestion. Keys not selected here keep their normal textarea behavior.",
|
||||
category: ["LoRA Manager", "Autocomplete", "Accept key"],
|
||||
},
|
||||
{
|
||||
id: TAG_SPACE_REPLACEMENT_SETTING_ID,
|
||||
@@ -451,6 +505,7 @@ export {
|
||||
getWheelSensitivity,
|
||||
getAutoPathCorrectionPreference,
|
||||
getAutocompleteAppendCommaPreference,
|
||||
getAutocompleteAcceptKeyPreference,
|
||||
getPromptTagAutocompletePreference,
|
||||
getTagSpaceReplacementPreference,
|
||||
getUsageStatisticsPreference,
|
||||
|
||||
Reference in New Issue
Block a user