From f657d63522928358ce4435dc24c133d0efce6386 Mon Sep 17 00:00:00 2001 From: Siarhei Siniak Date: Thu, 16 Oct 2025 17:13:50 +0300 Subject: [PATCH] [+] update vim, python3 plugin 1. handle escape character; 2. handle items filtering based on entered letters; 3. redraw UI when the filter changes; 4. TODO handle selection changes via arrows, hjkl; 5. TODO handle selection after Enter has been pressed; --- .../.vim/online_fxreader_pr34_vim/beta.py | 218 +++++++++++------- .../.vim/online_fxreader_pr34_vim/main.py | 7 +- .../.vim/online_fxreader_pr34_vim/utils.py | 5 +- python/pyproject.toml | 2 +- 4 files changed, 139 insertions(+), 93 deletions(-) diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py index 266d2a3..604be1f 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/beta.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/beta.py @@ -25,12 +25,14 @@ logger = logging.getLogger(__name__) MODULE_NAME = 'online_fxreader_pr34_vim' + def future_dump_exception(future: Any) -> None: try: future.result() except: logger.exception('') + class FastSelect: _instance: ClassVar[Optional['FastSelect']] = None @@ -41,16 +43,20 @@ class FastSelect: target=self.loop.run_forever, ) - self._buffer_frequency : dict[int, int] = dict() - self._buffer_last_used : dict[int, int] = dict() + self._buffer_frequency: dict[int, int] = dict() + self._buffer_last_used: dict[int, int] = dict() - self._filter_pattern : Optional[str] = None + self._filter_pattern: Optional[str] = None + self._items: Optional[list[tuple[str, int]]] = None + self._filtered_ids: Optional[set[int]] = None - self._queue : collections.deque[Callable[[], None]] = collections.deque() + self._queue: collections.deque[Callable[[], None]] = collections.deque() self._lock = threading.Lock() + self.popup_id: Optional[int] = None + self.thread.start() - self._option_id : asyncio.Future[Optional[int]] = None + self._option_id: asyncio.Future[Optional[int]] = None self._options: list[str] = None auto_group = '{}_{}_{}'.format( @@ -59,24 +65,25 @@ class FastSelect: 'close', ).capitalize() - vim.command(r''' + vim.command(r""" func! UIThread(timer_id) python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().ui_thread() endfunc - ''') - Vim.run_command(r''' + """) + Vim.run_command(r""" call timer_start(100, 'UIThread', {'repeat': -1}) - ''') - Vim.run_command(r''' + """) + Vim.run_command( + r""" augroup {auto_group} autocmd! autocmd VimLeavePre * python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().close() autocmd BufEnter * python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().on_buf_enter() augroup END - '''.format( - auto_group=auto_group, - )) - + """.format( + auto_group=auto_group, + ) + ) def __del__(self) -> None: self.close() @@ -96,28 +103,21 @@ augroup END return cls._instance def pick_option_put_id(self, option_id: int) -> None: - self.loop.call_soon_threadsafe( - lambda: self._option_id.set_result(option_id) - ) + self.loop.call_soon_threadsafe(lambda: self._option_id.set_result(option_id)) async def _switch_buffer(self) -> None: - buffers_future : asyncio.Future[list[tuple[str, int]]] = asyncio.Future() + buffers_future: asyncio.Future[list[tuple[str, int]]] = asyncio.Future() def get_buffers() -> list[tuple[str, int]]: - res = [ - (o.name, o.number) - for o in vim.buffers - ] + res = [(o.name, o.number) for o in vim.buffers] res_sorted = sorted( res, # key=lambda x: -self._buffer_frequency.get(x[1], 0) - key=lambda x: -self._buffer_last_used.get(x[1], 0) + key=lambda x: -self._buffer_last_used.get(x[1], 0), ) - self.loop.call_soon_threadsafe( - lambda: buffers_future.set_result(res_sorted) - ) + self.loop.call_soon_threadsafe(lambda: buffers_future.set_result(res_sorted)) with self._lock: self._queue.append(get_buffers) @@ -126,8 +126,13 @@ augroup END logger.info(dict(buffers=buffers[:3])) + self._items = buffers + + with self._lock: + self._set_filter_pattern('') + selected_id = await self._pick_option_from_popup( - [o[0] for o in buffers] + # [o[0] for o in buffers] ) logger.info(dict(selected_id=selected_id)) @@ -146,25 +151,22 @@ augroup END def switch_buffer(self) -> None: logger.info(dict(msg='before switch_buffer started')) - result = asyncio.run_coroutine_threadsafe( - self._switch_buffer(), - self.loop - ) + result = asyncio.run_coroutine_threadsafe(self._switch_buffer(), self.loop) result.add_done_callback(future_dump_exception) logger.info(dict(msg='after switch_buffer started')) - async def _pick_option_from_popup( self, - options: list[str], + # options: list[str], ) -> Optional[int]: logger.info(dict(msg='started')) self._filter_pattern = '' + self._popup_id = None - self._options = options + # self._options = options self._option_id = asyncio.Future[int]() @@ -186,13 +188,13 @@ augroup END def ui_thread(self): with self._lock: - #Vim.run_command(r''' - # set laststatus=2 - # set statusline={} + # Vim.run_command(r''' + # set laststatus=2 + # set statusline={} #'''.format(datetime.datetime.now().isoformat())) while len(self._queue) > 0: - cmd = self._queue.pop(); + cmd = self._queue.pop() logger.warning(dict(msg='start command', cmd=inspect.getsource(cmd))) try: cmd() @@ -208,7 +210,7 @@ augroup END buf_number=vim.current.buffer.number, buf_name=pathlib.Path(vim.current.buffer.name), ), - self.loop + self.loop, ) result.add_done_callback(future_dump_exception) @@ -216,19 +218,50 @@ augroup END def on_filter_key(self, key: str) -> None: logger.info(dict(msg='got key', key=key)) - try: - key_str = key.decode('utf-8') - except: - return 0 + if key == bytes([27]): + logger.info(dict(msg='closing popup')) - if not key_str.isprintable(): - return 0 + vim.Function('popup_close')(self._popup_id) + return 1 - with self._lock: - self._filter_pattern += key_str + if key == b'\x80kb': + logger.info(dict(msg='backspace')) + + with self._lock: + self._set_filter_pattern(self._filter_pattern[:-1]) + else: + try: + key_str = key.decode('utf-8') + except: + return 0 + + if not key_str.isprintable(): + return 0 + else: + + with self._lock: + self._set_filter_pattern(self._filter_pattern + key_str) + + self._update_popup() return 1 + def _set_filter_pattern(self, filter_pattern: str) -> None: + self._filter_pattern = filter_pattern + + pattern = re.compile(self._filter_pattern) + + self._filtered_ids = [i for i, o in enumerate(self._items) if not pattern.search(o[0]) is None] + + self._options = [self._items[o][0] for o in self._filtered_ids] + + def _update_popup(self) -> None: + vim.Function('popup_settext')( + self._popup_id, + self._options, + ) + vim.Function('popup_setoptions')(self._popup_id, {'title': 'Select a file, [%s]' % self._filter_pattern}) + async def _on_buf_enter( self, buf_number: int, @@ -246,12 +279,14 @@ augroup END self._buffer_last_used[buf_number] = datetime.datetime.now().timestamp() - logger.info(dict( - msg='updated', - buf_path=str(buf_name), - frequency=self._buffer_frequency[buf_number], - buf_number=buf_number, - )) + logger.info( + dict( + msg='updated', + buf_path=str(buf_name), + frequency=self._buffer_frequency[buf_number], + buf_number=buf_number, + ) + ) async def _pick_option_start_popup( self, @@ -271,7 +306,8 @@ augroup END if int(vim.eval('exists("{}")'.format(callback_name))) == 1: logger.warning(dict(msg='callback already defined, %s' % callback_name)) - vim.command(r""" + vim.command( + r""" function! {callback_name}(id, result) if a:result > 0 call py3eval('online_fxreader_pr34_vim.beta.FastSelect.singleton().pick_option_put_id(' . (a:result - 1). ')') @@ -280,55 +316,63 @@ augroup END endif endfunction """.format( - callback_name=callback_name, - )) + callback_name=callback_name, + ) + ) - vim.command(r""" + vim.command( + r""" function! {filter_name}(win_id, key) return py3eval('online_fxreader_pr34_vim.beta.FastSelect.singleton().on_filter_key(key)', #{key: a:key}) endfunction """.replace( - '{filter_name}', filter_name, - )) + '{filter_name}', + filter_name, + ) + ) logger.info(dict(msg='before popup')) popup_menu = vim.Function('popup_menu') + def create_popup(): + self._popup_id = popup_menu( + self._options, + { + 'title': 'Select a file', + 'callback': callback_name, + 'filter': filter_name, + 'wrap': 1, + 'maxwidth': 80, + 'close': 'button', + 'resize': 1, + 'drag': 1, + 'maxheight': '16', + }, + ) + with self._lock: self._queue.append( - lambda : popup_menu( - self._options, - { - 'title': 'Select a file', - 'callback': callback_name, - 'filter': filter_name, - 'wrap': 1, - 'maxwidth': 80, - 'close': 'button', - 'resize': 1, - 'drag': 1, - 'maxheight': '16', - } - ) - #lambda : vim.command( - # "call popup_menu({options}, {'title': '{title}', 'callback': '{callback}'})".replace( - # '{options}', '[%s]' % ','.join([ - # '\'%s\'' % o.replace('\'', '\\\'') - # for o in self._options - # ]), - # ).replace( - # '{title}', 'Select a file', - # ).replace( - # '{callback}', - # callback_name - # ) - #) + create_popup, + # lambda : vim.command( + # "call popup_menu({options}, {'title': '{title}', 'callback': '{callback}'})".replace( + # '{options}', '[%s]' % ','.join([ + # '\'%s\'' % o.replace('\'', '\\\'') + # for o in self._options + # ]), + # ).replace( + # '{title}', 'Select a file', + # ).replace( + # '{callback}', + # callback_name + # ) + # ) ) # logger.info(dict(popup_id=popup_id)) # logger.info(dict(msg='after popup')) + def init(): - FastSelect.singleton() + FastSelect.singleton() diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/main.py b/dotfiles/.vim/online_fxreader_pr34_vim/main.py index d64708f..ceaa21a 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/main.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/main.py @@ -27,6 +27,7 @@ from .utils import Vim MODULE_NAME = 'online_fxreader_pr34_vim' + def f1(): t1 = vim.current.window t2 = t1.width @@ -163,12 +164,12 @@ class EditorConfigModeline: dict[str, str], ] = dict() - Vim.run_command(r''' + Vim.run_command(r""" augroup EditorConfigModeline autocmd! autocmd BufEnter * python3 import online_fxreader_pr34_vim.main; online_fxreader_pr34_vim.main.EditorConfigModeline.singleton().on_buffer() augroup END - ''') + """) @classmethod def singleton(cls) -> Self: @@ -255,7 +256,9 @@ augroup END # raise NotImplementedError + # EditorConfigModeline.singleton() + def init(): EditorConfigModeline.singleton() diff --git a/dotfiles/.vim/online_fxreader_pr34_vim/utils.py b/dotfiles/.vim/online_fxreader_pr34_vim/utils.py index ebe86fb..ca4a712 100644 --- a/dotfiles/.vim/online_fxreader_pr34_vim/utils.py +++ b/dotfiles/.vim/online_fxreader_pr34_vim/utils.py @@ -1,5 +1,6 @@ import vim + class Vim: @classmethod def run_command(cls, cmd) -> list[str]: @@ -9,8 +10,6 @@ class Vim: for line in cmd.splitlines(): if line.strip() == '': continue - output.append( - vim.command(line) - ) + output.append(vim.command(line)) return output diff --git a/python/pyproject.toml b/python/pyproject.toml index c7eb166..8b0e03a 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -81,7 +81,7 @@ include = [ './*.py', 'online/**/*.py', 'online/**/*.pyi', - '../dotfiles/.module.vimrc.py', + '../dotfiles/.vim/**/*.py', ] exclude = [ '.venv',