Add parameters for visible and total items of autocomplete dropdown for @ and #

This commit is contained in:
dimitrievgs 2025-09-27 23:51:05 +03:00
parent bc9424877c
commit 29cddbcb57
2 changed files with 76 additions and 68 deletions

View File

@ -671,13 +671,17 @@
/* Reference Autocomplete */ /* Reference Autocomplete */
.reference-autocomplete { .reference-autocomplete {
/* 200px для 5 элементов, возможно, нужно заменить на расчёт с помощью padding и размером шрифта */
--item-height: 33px; /* высота одного элемента */
--visible-items: 10; /* количество видимых элементов */
position: absolute; position: absolute;
background: var(--background-primary); background: var(--background-primary);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: 8px; border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000; z-index: 1000;
max-height: 200px; max-height: calc(var(--item-height) * var(--visible-items));
overflow-y: auto; overflow-y: auto;
min-width: 250px; min-width: 250px;
max-width: 400px; max-width: 400px;

View File

@ -25,6 +25,11 @@ export class AutocompleteManager {
private lastCacheUpdate = 0; private lastCacheUpdate = 0;
private readonly CACHE_TTL = 30000; // 30 секунд private readonly CACHE_TTL = 30000; // 30 секунд
/**
* @property {number} MAX_TOTAL_ITEMS Максимальное количество элементов для поиска и кэширования.
*/
static MAX_TOTAL_ITEMS = 20;
constructor(plugin: LLMAgentPlugin) { constructor(plugin: LLMAgentPlugin) {
this.plugin = plugin; this.plugin = plugin;
this.setupFileWatcher(); this.setupFileWatcher();
@ -49,13 +54,13 @@ export class AutocompleteManager {
const allFiles = this.fileCache.get('all') || []; const allFiles = this.fileCache.get('all') || [];
if (!query.trim()) { if (!query.trim()) {
return allFiles.slice(0, 10); // Показываем первые 10 файлов return allFiles.slice(0, AutocompleteManager.MAX_TOTAL_ITEMS); // Показываем первые 10 файлов
} }
const lowercaseQuery = query.toLowerCase(); const lowercaseQuery = query.toLowerCase();
return allFiles return allFiles
.filter((item) => item.name.toLowerCase().includes(lowercaseQuery) || item.path.toLowerCase().includes(lowercaseQuery)) .filter((item) => item.name.toLowerCase().includes(lowercaseQuery) || item.path.toLowerCase().includes(lowercaseQuery))
.slice(0, 10); .slice(0, AutocompleteManager.MAX_TOTAL_ITEMS);
} }
/** /**
@ -73,13 +78,13 @@ export class AutocompleteManager {
const allPrompts = this.promptCache.get('all') || []; const allPrompts = this.promptCache.get('all') || [];
if (!query.trim()) { if (!query.trim()) {
return allPrompts.slice(0, 10); return allPrompts.slice(0, AutocompleteManager.MAX_TOTAL_ITEMS);
} }
const lowercaseQuery = query.toLowerCase(); const lowercaseQuery = query.toLowerCase();
return allPrompts return allPrompts
.filter((item) => item.name.toLowerCase().includes(lowercaseQuery) || item.path.toLowerCase().includes(lowercaseQuery)) .filter((item) => item.name.toLowerCase().includes(lowercaseQuery) || item.path.toLowerCase().includes(lowercaseQuery))
.slice(0, 10); .slice(0, AutocompleteManager.MAX_TOTAL_ITEMS);
} }
/** /**
@ -107,59 +112,58 @@ export class AutocompleteManager {
/** /**
* Обновляет кеш файлов и промптов * Обновляет кеш файлов и промптов
*/ */
private async updateCache(): Promise<void> { private async updateCache(): Promise<void> {
const files = this.plugin.app.vault.getMarkdownFiles(); const files = this.plugin.app.vault.getMarkdownFiles();
const fileItems: AutocompleteItem[] = []; const fileItems: AutocompleteItem[] = [];
const promptItems: AutocompleteItem[] = []; const promptItems: AutocompleteItem[] = [];
// Нормализуем пути исключенных папок // Нормализуем пути исключенных папок
const normalizedExcludedFolders = this.plugin.settings.excludedFolders const normalizedExcludedFolders = this.plugin.settings.excludedFolders
.filter(folder => folder.trim() !== '') .filter((folder) => folder.trim() !== '')
.map(folder => folder.endsWith('/') ? folder : folder + '/') // Убедимся, что заканчивается на слэш .map((folder) => (folder.endsWith('/') ? folder : folder + '/')) // Убедимся, что заканчивается на слэш
.map(folder => folder.startsWith('/') ? folder.substring(1) : folder); // Удаляем начальный слэш .map((folder) => (folder.startsWith('/') ? folder.substring(1) : folder)); // Удаляем начальный слэш
for (const file of files) { for (const file of files) {
// Пропускаем файлы из исключенных папок // Пропускаем файлы из исключенных папок
if (this.isFileInExcludedFolder(file, normalizedExcludedFolders)) { if (this.isFileInExcludedFolder(file, normalizedExcludedFolders)) {
continue; continue;
} }
const item: AutocompleteItem = { const item: AutocompleteItem = {
name: file.basename, name: file.basename,
path: file.path, path: file.path,
type: this.isPromptFile(file) ? 'prompt' : 'file', type: this.isPromptFile(file) ? 'prompt' : 'file',
icon: this.getFileIcon(file), icon: this.getFileIcon(file),
file: file file: file
}; };
if (item.type === 'prompt') { if (item.type === 'prompt') {
promptItems.push(item); promptItems.push(item);
} else { } else {
fileItems.push(item); fileItems.push(item);
} }
} }
// Сортируем по имени // Сортируем по имени
fileItems.sort((a, b) => a.name.localeCompare(b.name)); fileItems.sort((a, b) => a.name.localeCompare(b.name));
promptItems.sort((a, b) => a.name.localeCompare(b.name)); promptItems.sort((a, b) => a.name.localeCompare(b.name));
this.fileCache.set('all', fileItems); this.fileCache.set('all', fileItems);
this.promptCache.set('all', promptItems); this.promptCache.set('all', promptItems);
} }
/** /**
* Проверяет, является ли файл промптом * Проверяет, является ли файл промптом
*/ */
private isPromptFile(file: TFile): boolean { private isPromptFile(file: TFile): boolean {
const promptsFolder = this.plugin.settings.promptsFolder; const promptsFolder = this.plugin.settings.promptsFolder;
if (!promptsFolder) return false; if (!promptsFolder) return false;
// Нормализуем путь к папке промптов // Нормализуем путь к папке промптов
const normalizedPromptsPath = promptsFolder.replace(/^\/+|\/+$/g, ''); const normalizedPromptsPath = promptsFolder.replace(/^\/+|\/+$/g, '');
return file.path.startsWith(normalizedPromptsPath + '/') || return file.path.startsWith(normalizedPromptsPath + '/') || file.path === normalizedPromptsPath;
file.path === normalizedPromptsPath; }
}
/** /**
* Возвращает иконку для файла на основе Obsidian иконок * Возвращает иконку для файла на основе Obsidian иконок
@ -214,23 +218,23 @@ export class AutocompleteManager {
); );
} }
/** /**
* Проверяет, находится ли файл в одной из исключенных папок. * Проверяет, находится ли файл в одной из исключенных папок.
* @param file - файл Obsidian. * @param file - файл Obsidian.
* @param excludedFolders - массив нормализованных путей исключенных папок. * @param excludedFolders - массив нормализованных путей исключенных папок.
* @returns true, если файл находится в исключенной папке. * @returns true, если файл находится в исключенной папке.
*/ */
private isFileInExcludedFolder(file: TFile, excludedFolders: string[]): boolean { private isFileInExcludedFolder(file: TFile, excludedFolders: string[]): boolean {
const filePath = file.path.toLowerCase(); // Для регистронезависимого сравнения const filePath = file.path.toLowerCase(); // Для регистронезависимого сравнения
for (const folder of excludedFolders) { for (const folder of excludedFolders) {
const normalizedFolder = folder.toLowerCase(); const normalizedFolder = folder.toLowerCase();
// Проверяем, начинается ли путь файла с пути исключенной папки // Проверяем, начинается ли путь файла с пути исключенной папки
// Или если файл находится прямо в корне исключенной папки (н.п. "folder/file.md") // Или если файл находится прямо в корне исключенной папки (н.п. "folder/file.md")
if (filePath.startsWith(normalizedFolder) ) { if (filePath.startsWith(normalizedFolder)) {
return true; return true;
} }
} }
return false; return false;
} }
} }