Move search machinery to ui module, add fast cached chart selection

symbol_search
Tyler Goodlet 2021-05-16 20:52:22 -04:00
parent 82ece83d33
commit 0163a582a5
1 changed files with 144 additions and 26 deletions

View File

@ -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://github.com/qutebrowser/qutebrowser/blob/master/qutebrowser/completion/completiondelegate.py#L243
# https://forum.qt.io/topic/61343/highlight-matched-substrings-in-qstyleditemdelegate # https://forum.qt.io/topic/61343/highlight-matched-substrings-in-qstyleditemdelegate
import sys from contextlib import asynccontextmanager
from functools import partial from functools import partial
from typing import ( from typing import (
List, Optional, Callable, List, Optional, Callable,
Awaitable, Sequence, Dict, Awaitable, Sequence, Dict,
Any, AsyncIterator, Tuple,
) )
# from pprint import pformat # from pprint import pformat
@ -69,7 +70,6 @@ from ._style import (
DpiAwareFont, DpiAwareFont,
# hcolor, # hcolor,
) )
from ..data import feed
log = get_logger(__name__) log = get_logger(__name__)
@ -217,6 +217,17 @@ class CompleterView(QTreeView):
self.expandAll() 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: def show_matches(self) -> None:
# print(f"SHOWING {self}") # print(f"SHOWING {self}")
self.show() self.show()
@ -259,9 +270,12 @@ class CompleterView(QTreeView):
return nidx return nidx
def select_from_idx( def select_from_idx(
self, self,
idx: QModelIndex, idx: QModelIndex,
) -> None:
) -> Tuple[QModelIndex, QStandardItem]:
sel = self.selectionModel() sel = self.selectionModel()
model = self.model() model = self.model()
@ -275,6 +289,8 @@ class CompleterView(QTreeView):
QItemSelectionModel.Rows QItemSelectionModel.Rows
) )
return idx, model.itemFromIndex(idx)
# def find_matches( # def find_matches(
# self, # self,
# field: str, # field: str,
@ -427,15 +443,19 @@ async def fill_results(
# XXX: these 2 lines MUST be in sequence in order # XXX: these 2 lines MUST be in sequence in order
# to get the view to show right after typing input. # to get the view to show right after typing input.
sel.setCurrentIndex( # ensure we select first indented entry
model.index(0, 0, QModelIndex()), # view.select_from_idx(model.index(0, 0, QModelIndex()))
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Rows # sel.setCurrentIndex(
) # model.index(0, 0, QModelIndex()),
# QItemSelectionModel.ClearAndSelect |
# QItemSelectionModel.Rows
# )
bar.show() bar.show()
# ensure we select first indented entry # # ensure we select first indented entry
view.select_from_idx(sel.currentIndex()) # view.select_from_idx(sel.currentIndex())
class SearchWidget(QtGui.QWidget): class SearchWidget(QtGui.QWidget):
@ -476,6 +496,13 @@ class SearchWidget(QtGui.QWidget):
self.vbox.setAlignment(self.view, Qt.AlignTop | Qt.AlignLeft) 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( async def handle_keyboard_input(
# chart: 'ChartSpace', # type: igore # noqa # chart: 'ChartSpace', # type: igore # noqa
@ -495,7 +522,7 @@ async def handle_keyboard_input(
nidx = cidx = view.currentIndex() nidx = cidx = view.currentIndex()
sel = view.selectionModel() sel = view.selectionModel()
symsearch = feed.get_multi_search() symsearch = get_multi_search()
send, recv = trio.open_memory_channel(16) send, recv = trio.open_memory_channel(16)
async with trio.open_nursery() as n: async with trio.open_nursery() as n:
@ -577,7 +604,21 @@ async def handle_keyboard_input(
# select row without selecting.. :eye_rollzz: # select row without selecting.. :eye_rollzz:
# https://doc.qt.io/qt-5/qabstractitemview.html#setCurrentIndex # https://doc.qt.io/qt-5/qabstractitemview.html#setCurrentIndex
if nidx.isValid(): 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: else:
# relay to completer task # relay to completer task
_search_enabled = True _search_enabled = True
@ -585,20 +626,97 @@ async def handle_keyboard_input(
_search_active.set() _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 # local testing of **just** the search UI
app = QtWidgets.QApplication(sys.argv) # app = QtWidgets.QApplication(sys.argv)
syms = [ # syms = [
'XMRUSD', # 'XMRUSD',
'XBTUSD', # 'XBTUSD',
'ETHUSD', # 'ETHUSD',
'XMRXBT', # 'XMRXBT',
'XDGUSD', # 'XDGUSD',
'ADAUSD', # 'ADAUSD',
] # ]
# TODO: need to qtracor.run() here to make it work now... # # search.show()
# search.show()
sys.exit(app.exec_()) # sys.exit(app.exec_())