Move search machinery to ui module, add fast cached chart selection
parent
82ece83d33
commit
0163a582a5
|
@ -31,11 +31,12 @@ qompleterz: embeddable search and complete using trio, Qt and fuzzywuzzy.
|
|||
# https://github.com/qutebrowser/qutebrowser/blob/master/qutebrowser/completion/completiondelegate.py#L243
|
||||
# https://forum.qt.io/topic/61343/highlight-matched-substrings-in-qstyleditemdelegate
|
||||
|
||||
import sys
|
||||
from contextlib import asynccontextmanager
|
||||
from functools import partial
|
||||
from typing import (
|
||||
List, Optional, Callable,
|
||||
Awaitable, Sequence, Dict,
|
||||
Any, AsyncIterator, Tuple,
|
||||
)
|
||||
# from pprint import pformat
|
||||
|
||||
|
@ -69,7 +70,6 @@ from ._style import (
|
|||
DpiAwareFont,
|
||||
# hcolor,
|
||||
)
|
||||
from ..data import feed
|
||||
|
||||
|
||||
log = get_logger(__name__)
|
||||
|
@ -217,6 +217,17 @@ class CompleterView(QTreeView):
|
|||
|
||||
self.expandAll()
|
||||
|
||||
# XXX: these 2 lines MUST be in sequence in order
|
||||
# to get the view to show right after typing input.
|
||||
sel = self.selectionModel()
|
||||
sel.setCurrentIndex(
|
||||
model.index(0, 0, QModelIndex()),
|
||||
QItemSelectionModel.ClearAndSelect |
|
||||
QItemSelectionModel.Rows
|
||||
)
|
||||
self.select_from_idx(model.index(0, 0, QModelIndex()))
|
||||
|
||||
|
||||
def show_matches(self) -> None:
|
||||
# print(f"SHOWING {self}")
|
||||
self.show()
|
||||
|
@ -259,9 +270,12 @@ class CompleterView(QTreeView):
|
|||
return nidx
|
||||
|
||||
def select_from_idx(
|
||||
|
||||
self,
|
||||
idx: QModelIndex,
|
||||
) -> None:
|
||||
|
||||
) -> Tuple[QModelIndex, QStandardItem]:
|
||||
|
||||
sel = self.selectionModel()
|
||||
model = self.model()
|
||||
|
||||
|
@ -275,6 +289,8 @@ class CompleterView(QTreeView):
|
|||
QItemSelectionModel.Rows
|
||||
)
|
||||
|
||||
return idx, model.itemFromIndex(idx)
|
||||
|
||||
# def find_matches(
|
||||
# self,
|
||||
# field: str,
|
||||
|
@ -357,7 +373,7 @@ async def fill_results(
|
|||
search: SearchBar,
|
||||
symsearch: Callable[..., Awaitable],
|
||||
recv_chan: trio.abc.ReceiveChannel,
|
||||
# cached_symbols: Dict[str,
|
||||
# cached_symbols: Dict[str,
|
||||
pause_time: float = 0.0616,
|
||||
|
||||
) -> None:
|
||||
|
@ -427,15 +443,19 @@ async def fill_results(
|
|||
|
||||
# XXX: these 2 lines MUST be in sequence in order
|
||||
# to get the view to show right after typing input.
|
||||
sel.setCurrentIndex(
|
||||
model.index(0, 0, QModelIndex()),
|
||||
QItemSelectionModel.ClearAndSelect |
|
||||
QItemSelectionModel.Rows
|
||||
)
|
||||
# ensure we select first indented entry
|
||||
# view.select_from_idx(model.index(0, 0, QModelIndex()))
|
||||
|
||||
# sel.setCurrentIndex(
|
||||
# model.index(0, 0, QModelIndex()),
|
||||
# QItemSelectionModel.ClearAndSelect |
|
||||
# QItemSelectionModel.Rows
|
||||
# )
|
||||
|
||||
bar.show()
|
||||
|
||||
# ensure we select first indented entry
|
||||
view.select_from_idx(sel.currentIndex())
|
||||
# # ensure we select first indented entry
|
||||
# view.select_from_idx(sel.currentIndex())
|
||||
|
||||
|
||||
class SearchWidget(QtGui.QWidget):
|
||||
|
@ -476,6 +496,13 @@ class SearchWidget(QtGui.QWidget):
|
|||
self.vbox.setAlignment(self.view, Qt.AlignTop | Qt.AlignLeft)
|
||||
|
||||
|
||||
def focus(self) -> None:
|
||||
# fill cache list
|
||||
self.view.set_results({'cache': list(self.chart_app._chart_cache)})
|
||||
self.bar.focus()
|
||||
|
||||
|
||||
|
||||
async def handle_keyboard_input(
|
||||
|
||||
# chart: 'ChartSpace', # type: igore # noqa
|
||||
|
@ -495,7 +522,7 @@ async def handle_keyboard_input(
|
|||
nidx = cidx = view.currentIndex()
|
||||
sel = view.selectionModel()
|
||||
|
||||
symsearch = feed.get_multi_search()
|
||||
symsearch = get_multi_search()
|
||||
send, recv = trio.open_memory_channel(16)
|
||||
|
||||
async with trio.open_nursery() as n:
|
||||
|
@ -577,7 +604,21 @@ async def handle_keyboard_input(
|
|||
# select row without selecting.. :eye_rollzz:
|
||||
# https://doc.qt.io/qt-5/qabstractitemview.html#setCurrentIndex
|
||||
if nidx.isValid():
|
||||
view.select_from_idx(nidx)
|
||||
i, item = view.select_from_idx(nidx)
|
||||
|
||||
if item:
|
||||
parent_item = item.parent()
|
||||
if parent_item and parent_item.text() == 'cache':
|
||||
node = model.itemFromIndex(i.siblingAtColumn(1))
|
||||
if node:
|
||||
value = node.text()
|
||||
print(f'cache selection')
|
||||
search.chart_app.load_symbol(
|
||||
app.linkedcharts.symbol.brokers[0],
|
||||
value,
|
||||
'info',
|
||||
)
|
||||
|
||||
else:
|
||||
# relay to completer task
|
||||
_search_enabled = True
|
||||
|
@ -585,20 +626,97 @@ async def handle_keyboard_input(
|
|||
_search_active.set()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
async def search_simple_dict(
|
||||
text: str,
|
||||
source: dict,
|
||||
) -> Dict[str, Any]:
|
||||
|
||||
# matches_per_src = {}
|
||||
|
||||
# for source, data in source.items():
|
||||
|
||||
# search routine can be specified as a function such
|
||||
# as in the case of the current app's local symbol cache
|
||||
matches = fuzzy.extractBests(
|
||||
text,
|
||||
source.keys(),
|
||||
score_cutoff=90,
|
||||
)
|
||||
|
||||
return [item[0] for item in matches]
|
||||
|
||||
|
||||
# cache of provider names to async search routines
|
||||
_searcher_cache: Dict[str, Callable[..., Awaitable]] = {}
|
||||
|
||||
|
||||
def get_multi_search() -> Callable[..., Awaitable]:
|
||||
|
||||
global _searcher_cache
|
||||
|
||||
async def multisearcher(
|
||||
pattern: str,
|
||||
) -> dict:
|
||||
|
||||
matches = {}
|
||||
|
||||
async def pack_matches(
|
||||
provider: str,
|
||||
pattern: str,
|
||||
search: Callable[..., Awaitable[dict]],
|
||||
) -> None:
|
||||
log.debug(f'Searching {provider} for "{pattern}"')
|
||||
results = await search(pattern)
|
||||
if results:
|
||||
matches[provider] = results
|
||||
|
||||
# TODO: make this an async stream?
|
||||
async with trio.open_nursery() as n:
|
||||
|
||||
for brokername, search in _searcher_cache.items():
|
||||
n.start_soon(pack_matches, brokername, pattern, search)
|
||||
|
||||
return matches
|
||||
|
||||
return multisearcher
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def register_symbol_search(
|
||||
|
||||
provider_name: str,
|
||||
search_routine: Callable,
|
||||
|
||||
) -> AsyncIterator[dict]:
|
||||
|
||||
global _searcher_cache
|
||||
|
||||
# deliver search func to consumer
|
||||
try:
|
||||
_searcher_cache[provider_name] = search_routine
|
||||
yield search_routine
|
||||
finally:
|
||||
_searcher_cache.pop(provider_name)
|
||||
|
||||
|
||||
# if __name__ == '__main__':
|
||||
|
||||
# TODO: simple standalone widget testing script (moreso
|
||||
# for if/when we decide to expose this module as a standalone
|
||||
# repo/project).
|
||||
|
||||
# import sys
|
||||
# local testing of **just** the search UI
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
# app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
syms = [
|
||||
'XMRUSD',
|
||||
'XBTUSD',
|
||||
'ETHUSD',
|
||||
'XMRXBT',
|
||||
'XDGUSD',
|
||||
'ADAUSD',
|
||||
]
|
||||
# TODO: need to qtracor.run() here to make it work now...
|
||||
# search.show()
|
||||
# syms = [
|
||||
# 'XMRUSD',
|
||||
# 'XBTUSD',
|
||||
# 'ETHUSD',
|
||||
# 'XMRXBT',
|
||||
# 'XDGUSD',
|
||||
# 'ADAUSD',
|
||||
# ]
|
||||
# # search.show()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
# sys.exit(app.exec_())
|
||||
|
|
Loading…
Reference in New Issue