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://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,
|
||||||
|
@ -357,7 +373,7 @@ async def fill_results(
|
||||||
search: SearchBar,
|
search: SearchBar,
|
||||||
symsearch: Callable[..., Awaitable],
|
symsearch: Callable[..., Awaitable],
|
||||||
recv_chan: trio.abc.ReceiveChannel,
|
recv_chan: trio.abc.ReceiveChannel,
|
||||||
# cached_symbols: Dict[str,
|
# cached_symbols: Dict[str,
|
||||||
pause_time: float = 0.0616,
|
pause_time: float = 0.0616,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -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_())
|
||||||
|
|
Loading…
Reference in New Issue