feat: add autocomplete for search engine (#586)

* feat: add autocomplete for mediatype, filetype, path, tag, and tag_id searches

* fix: address issues brought up in review

* fix: fix mypy issue

* fix: fix mypy issues for real this time
This commit is contained in:
python357-1
2024-11-18 12:45:51 -06:00
committed by GitHub
parent 9078feec0c
commit bec513f558
3 changed files with 73 additions and 2 deletions

View File

@@ -395,6 +395,13 @@ class Library:
with Session(self.engine) as session:
return session.query(exists().where(Entry.path == path)).scalar()
def get_paths(self, glob: str | None = None) -> list[str]:
with Session(self.engine) as session:
paths = session.scalars(select(Entry.path)).unique()
path_strings: list[str] = list(map(lambda x: x.as_posix(), paths))
return path_strings
def search_library(
self,
search: FilterState,

View File

@@ -15,13 +15,13 @@
import logging
import typing
from PySide6.QtCore import (QCoreApplication, QMetaObject, QRect,QSize, Qt)
from PySide6.QtCore import (QCoreApplication, QMetaObject, QRect,QSize, Qt, QStringListModel)
from PySide6.QtGui import QFont
from PySide6.QtWidgets import (QComboBox, QFrame, QGridLayout,
QHBoxLayout, QVBoxLayout, QLayout, QLineEdit, QMainWindow,
QPushButton, QScrollArea, QSizePolicy,
QStatusBar, QWidget, QSplitter, QCheckBox,
QSpacerItem)
QSpacerItem, QCompleter)
from src.qt.pagination import Pagination
from src.qt.widgets.landing import LandingWidget
@@ -167,6 +167,11 @@ class Ui_MainWindow(QMainWindow):
font2.setBold(False)
self.searchField.setFont(font2)
self.searchFieldCompletionList = QStringListModel()
self.searchFieldCompleter = QCompleter(self.searchFieldCompletionList, self.searchField)
self.searchFieldCompleter.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
self.searchField.setCompleter(self.searchFieldCompleter)
self.horizontalLayout_2.addWidget(self.searchField)
self.searchButton = QPushButton(self.centralwidget)

View File

@@ -11,6 +11,7 @@ import ctypes
import dataclasses
import math
import os
import re
import sys
import time
import webbrowser
@@ -72,6 +73,7 @@ from src.core.library.alchemy.enums import (
)
from src.core.library.alchemy.fields import _FieldID
from src.core.library.alchemy.library import LibraryStatus
from src.core.media_types import MediaCategories
from src.core.ts_core import TagStudioCore
from src.core.utils.refresh_dir import RefreshDirTracker
from src.core.utils.web import strip_web_protocol
@@ -445,6 +447,8 @@ class QtDriver(DriverMixin, QObject):
menu_bar.addMenu(window_menu)
menu_bar.addMenu(help_menu)
self.main_window.searchField.textChanged.connect(self.update_completions_list)
self.preview_panel = PreviewPanel(self.lib, self)
splitter = self.main_window.splitter
splitter.addWidget(self.preview_panel)
@@ -949,6 +953,61 @@ class QtDriver(DriverMixin, QObject):
def set_macro_menu_viability(self):
self.autofill_action.setDisabled(not self.selected)
def update_completions_list(self, text: str) -> None:
matches = re.search(r"(mediatype|filetype|path|tag):(\"?[A-Za-z0-9\ \t]+\"?)?", text)
completion_list: list[str] = []
if len(text) < 3:
completion_list = ["mediatype:", "filetype:", "path:", "tag:"]
self.main_window.searchFieldCompletionList.setStringList(completion_list)
if not matches:
return
query_type: str
query_value: str | None
query_type, query_value = matches.groups()
if not query_value:
return
if query_type == "tag":
completion_list = list(map(lambda x: "tag:" + x.name, self.lib.tags))
elif query_type == "path":
completion_list = list(map(lambda x: "path:" + x, self.lib.get_paths()))
elif query_type == "mediatype":
single_word_completions = map(
lambda x: "mediatype:" + x.name,
filter(lambda y: " " not in y.name, MediaCategories.ALL_CATEGORIES),
)
single_word_completions_quoted = map(
lambda x: 'mediatype:"' + x.name + '"',
filter(lambda y: " " not in y.name, MediaCategories.ALL_CATEGORIES),
)
multi_word_completions = map(
lambda x: 'mediatype:"' + x.name + '"',
filter(lambda y: " " in y.name, MediaCategories.ALL_CATEGORIES),
)
all_completions = [
single_word_completions,
single_word_completions_quoted,
multi_word_completions,
]
completion_list = [j for i in all_completions for j in i]
elif query_type == "filetype":
extensions_list: set[str] = set()
for media_cat in MediaCategories.ALL_CATEGORIES:
extensions_list = extensions_list | media_cat.extensions
completion_list = list(map(lambda x: "filetype:" + x.replace(".", ""), extensions_list))
update_completion_list: bool = (
completion_list != self.main_window.searchFieldCompletionList.stringList()
or self.main_window.searchFieldCompletionList == []
)
if update_completion_list:
self.main_window.searchFieldCompletionList.setStringList(completion_list)
def update_thumbs(self):
"""Update search thumbnails."""
# start_time = time.time()