[+] 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;
This commit is contained in:
parent
f59e0bbe6c
commit
f657d63522
@ -25,12 +25,14 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
MODULE_NAME = 'online_fxreader_pr34_vim'
|
MODULE_NAME = 'online_fxreader_pr34_vim'
|
||||||
|
|
||||||
|
|
||||||
def future_dump_exception(future: Any) -> None:
|
def future_dump_exception(future: Any) -> None:
|
||||||
try:
|
try:
|
||||||
future.result()
|
future.result()
|
||||||
except:
|
except:
|
||||||
logger.exception('')
|
logger.exception('')
|
||||||
|
|
||||||
|
|
||||||
class FastSelect:
|
class FastSelect:
|
||||||
_instance: ClassVar[Optional['FastSelect']] = None
|
_instance: ClassVar[Optional['FastSelect']] = None
|
||||||
|
|
||||||
@ -41,16 +43,20 @@ class FastSelect:
|
|||||||
target=self.loop.run_forever,
|
target=self.loop.run_forever,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._buffer_frequency : dict[int, int] = dict()
|
self._buffer_frequency: dict[int, int] = dict()
|
||||||
self._buffer_last_used : 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._lock = threading.Lock()
|
||||||
|
|
||||||
|
self.popup_id: Optional[int] = None
|
||||||
|
|
||||||
self.thread.start()
|
self.thread.start()
|
||||||
self._option_id : asyncio.Future[Optional[int]] = None
|
self._option_id: asyncio.Future[Optional[int]] = None
|
||||||
self._options: list[str] = None
|
self._options: list[str] = None
|
||||||
|
|
||||||
auto_group = '{}_{}_{}'.format(
|
auto_group = '{}_{}_{}'.format(
|
||||||
@ -59,24 +65,25 @@ class FastSelect:
|
|||||||
'close',
|
'close',
|
||||||
).capitalize()
|
).capitalize()
|
||||||
|
|
||||||
vim.command(r'''
|
vim.command(r"""
|
||||||
func! UIThread(timer_id)
|
func! UIThread(timer_id)
|
||||||
python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().ui_thread()
|
python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().ui_thread()
|
||||||
endfunc
|
endfunc
|
||||||
''')
|
""")
|
||||||
Vim.run_command(r'''
|
Vim.run_command(r"""
|
||||||
call timer_start(100, 'UIThread', {'repeat': -1})
|
call timer_start(100, 'UIThread', {'repeat': -1})
|
||||||
''')
|
""")
|
||||||
Vim.run_command(r'''
|
Vim.run_command(
|
||||||
|
r"""
|
||||||
augroup {auto_group}
|
augroup {auto_group}
|
||||||
autocmd!
|
autocmd!
|
||||||
autocmd VimLeavePre * python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().close()
|
autocmd VimLeavePre * python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().close()
|
||||||
autocmd BufEnter * python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().on_buf_enter()
|
autocmd BufEnter * python3 online_fxreader_pr34_vim.beta.FastSelect.singleton().on_buf_enter()
|
||||||
augroup END
|
augroup END
|
||||||
'''.format(
|
""".format(
|
||||||
auto_group=auto_group,
|
auto_group=auto_group,
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def __del__(self) -> None:
|
def __del__(self) -> None:
|
||||||
self.close()
|
self.close()
|
||||||
@ -96,28 +103,21 @@ augroup END
|
|||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def pick_option_put_id(self, option_id: int) -> None:
|
def pick_option_put_id(self, option_id: int) -> None:
|
||||||
self.loop.call_soon_threadsafe(
|
self.loop.call_soon_threadsafe(lambda: self._option_id.set_result(option_id))
|
||||||
lambda: self._option_id.set_result(option_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _switch_buffer(self) -> None:
|
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]]:
|
def get_buffers() -> list[tuple[str, int]]:
|
||||||
res = [
|
res = [(o.name, o.number) for o in vim.buffers]
|
||||||
(o.name, o.number)
|
|
||||||
for o in vim.buffers
|
|
||||||
]
|
|
||||||
|
|
||||||
res_sorted = sorted(
|
res_sorted = sorted(
|
||||||
res,
|
res,
|
||||||
# key=lambda x: -self._buffer_frequency.get(x[1], 0)
|
# 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(
|
self.loop.call_soon_threadsafe(lambda: buffers_future.set_result(res_sorted))
|
||||||
lambda: buffers_future.set_result(res_sorted)
|
|
||||||
)
|
|
||||||
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self._queue.append(get_buffers)
|
self._queue.append(get_buffers)
|
||||||
@ -126,8 +126,13 @@ augroup END
|
|||||||
|
|
||||||
logger.info(dict(buffers=buffers[:3]))
|
logger.info(dict(buffers=buffers[:3]))
|
||||||
|
|
||||||
|
self._items = buffers
|
||||||
|
|
||||||
|
with self._lock:
|
||||||
|
self._set_filter_pattern('')
|
||||||
|
|
||||||
selected_id = await self._pick_option_from_popup(
|
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))
|
logger.info(dict(selected_id=selected_id))
|
||||||
@ -146,25 +151,22 @@ augroup END
|
|||||||
|
|
||||||
def switch_buffer(self) -> None:
|
def switch_buffer(self) -> None:
|
||||||
logger.info(dict(msg='before switch_buffer started'))
|
logger.info(dict(msg='before switch_buffer started'))
|
||||||
result = asyncio.run_coroutine_threadsafe(
|
result = asyncio.run_coroutine_threadsafe(self._switch_buffer(), self.loop)
|
||||||
self._switch_buffer(),
|
|
||||||
self.loop
|
|
||||||
)
|
|
||||||
|
|
||||||
result.add_done_callback(future_dump_exception)
|
result.add_done_callback(future_dump_exception)
|
||||||
|
|
||||||
logger.info(dict(msg='after switch_buffer started'))
|
logger.info(dict(msg='after switch_buffer started'))
|
||||||
|
|
||||||
|
|
||||||
async def _pick_option_from_popup(
|
async def _pick_option_from_popup(
|
||||||
self,
|
self,
|
||||||
options: list[str],
|
# options: list[str],
|
||||||
) -> Optional[int]:
|
) -> Optional[int]:
|
||||||
logger.info(dict(msg='started'))
|
logger.info(dict(msg='started'))
|
||||||
|
|
||||||
self._filter_pattern = ''
|
self._filter_pattern = ''
|
||||||
|
self._popup_id = None
|
||||||
|
|
||||||
self._options = options
|
# self._options = options
|
||||||
|
|
||||||
self._option_id = asyncio.Future[int]()
|
self._option_id = asyncio.Future[int]()
|
||||||
|
|
||||||
@ -186,13 +188,13 @@ augroup END
|
|||||||
|
|
||||||
def ui_thread(self):
|
def ui_thread(self):
|
||||||
with self._lock:
|
with self._lock:
|
||||||
#Vim.run_command(r'''
|
# Vim.run_command(r'''
|
||||||
# set laststatus=2
|
# set laststatus=2
|
||||||
# set statusline={}
|
# set statusline={}
|
||||||
#'''.format(datetime.datetime.now().isoformat()))
|
#'''.format(datetime.datetime.now().isoformat()))
|
||||||
|
|
||||||
while len(self._queue) > 0:
|
while len(self._queue) > 0:
|
||||||
cmd = self._queue.pop();
|
cmd = self._queue.pop()
|
||||||
logger.warning(dict(msg='start command', cmd=inspect.getsource(cmd)))
|
logger.warning(dict(msg='start command', cmd=inspect.getsource(cmd)))
|
||||||
try:
|
try:
|
||||||
cmd()
|
cmd()
|
||||||
@ -208,7 +210,7 @@ augroup END
|
|||||||
buf_number=vim.current.buffer.number,
|
buf_number=vim.current.buffer.number,
|
||||||
buf_name=pathlib.Path(vim.current.buffer.name),
|
buf_name=pathlib.Path(vim.current.buffer.name),
|
||||||
),
|
),
|
||||||
self.loop
|
self.loop,
|
||||||
)
|
)
|
||||||
|
|
||||||
result.add_done_callback(future_dump_exception)
|
result.add_done_callback(future_dump_exception)
|
||||||
@ -216,19 +218,50 @@ augroup END
|
|||||||
def on_filter_key(self, key: str) -> None:
|
def on_filter_key(self, key: str) -> None:
|
||||||
logger.info(dict(msg='got key', key=key))
|
logger.info(dict(msg='got key', key=key))
|
||||||
|
|
||||||
try:
|
if key == bytes([27]):
|
||||||
key_str = key.decode('utf-8')
|
logger.info(dict(msg='closing popup'))
|
||||||
except:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if not key_str.isprintable():
|
vim.Function('popup_close')(self._popup_id)
|
||||||
return 0
|
return 1
|
||||||
|
|
||||||
with self._lock:
|
if key == b'\x80kb':
|
||||||
self._filter_pattern += key_str
|
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
|
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(
|
async def _on_buf_enter(
|
||||||
self,
|
self,
|
||||||
buf_number: int,
|
buf_number: int,
|
||||||
@ -246,12 +279,14 @@ augroup END
|
|||||||
|
|
||||||
self._buffer_last_used[buf_number] = datetime.datetime.now().timestamp()
|
self._buffer_last_used[buf_number] = datetime.datetime.now().timestamp()
|
||||||
|
|
||||||
logger.info(dict(
|
logger.info(
|
||||||
msg='updated',
|
dict(
|
||||||
buf_path=str(buf_name),
|
msg='updated',
|
||||||
frequency=self._buffer_frequency[buf_number],
|
buf_path=str(buf_name),
|
||||||
buf_number=buf_number,
|
frequency=self._buffer_frequency[buf_number],
|
||||||
))
|
buf_number=buf_number,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
async def _pick_option_start_popup(
|
async def _pick_option_start_popup(
|
||||||
self,
|
self,
|
||||||
@ -271,7 +306,8 @@ augroup END
|
|||||||
if int(vim.eval('exists("{}")'.format(callback_name))) == 1:
|
if int(vim.eval('exists("{}")'.format(callback_name))) == 1:
|
||||||
logger.warning(dict(msg='callback already defined, %s' % callback_name))
|
logger.warning(dict(msg='callback already defined, %s' % callback_name))
|
||||||
|
|
||||||
vim.command(r"""
|
vim.command(
|
||||||
|
r"""
|
||||||
function! {callback_name}(id, result)
|
function! {callback_name}(id, result)
|
||||||
if a:result > 0
|
if a:result > 0
|
||||||
call py3eval('online_fxreader_pr34_vim.beta.FastSelect.singleton().pick_option_put_id(' . (a:result - 1). ')')
|
call py3eval('online_fxreader_pr34_vim.beta.FastSelect.singleton().pick_option_put_id(' . (a:result - 1). ')')
|
||||||
@ -280,55 +316,63 @@ augroup END
|
|||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
""".format(
|
""".format(
|
||||||
callback_name=callback_name,
|
callback_name=callback_name,
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
vim.command(r"""
|
vim.command(
|
||||||
|
r"""
|
||||||
function! {filter_name}(win_id, key)
|
function! {filter_name}(win_id, key)
|
||||||
return py3eval('online_fxreader_pr34_vim.beta.FastSelect.singleton().on_filter_key(key)', #{key: a:key})
|
return py3eval('online_fxreader_pr34_vim.beta.FastSelect.singleton().on_filter_key(key)', #{key: a:key})
|
||||||
endfunction
|
endfunction
|
||||||
""".replace(
|
""".replace(
|
||||||
'{filter_name}', filter_name,
|
'{filter_name}',
|
||||||
))
|
filter_name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(dict(msg='before popup'))
|
logger.info(dict(msg='before popup'))
|
||||||
|
|
||||||
popup_menu = vim.Function('popup_menu')
|
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:
|
with self._lock:
|
||||||
self._queue.append(
|
self._queue.append(
|
||||||
lambda : popup_menu(
|
create_popup,
|
||||||
self._options,
|
# lambda : vim.command(
|
||||||
{
|
# "call popup_menu({options}, {'title': '{title}', 'callback': '{callback}'})".replace(
|
||||||
'title': 'Select a file',
|
# '{options}', '[%s]' % ','.join([
|
||||||
'callback': callback_name,
|
# '\'%s\'' % o.replace('\'', '\\\'')
|
||||||
'filter': filter_name,
|
# for o in self._options
|
||||||
'wrap': 1,
|
# ]),
|
||||||
'maxwidth': 80,
|
# ).replace(
|
||||||
'close': 'button',
|
# '{title}', 'Select a file',
|
||||||
'resize': 1,
|
# ).replace(
|
||||||
'drag': 1,
|
# '{callback}',
|
||||||
'maxheight': '16',
|
# callback_name
|
||||||
}
|
# )
|
||||||
)
|
# )
|
||||||
#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(popup_id=popup_id))
|
||||||
|
|
||||||
# logger.info(dict(msg='after popup'))
|
# logger.info(dict(msg='after popup'))
|
||||||
|
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
FastSelect.singleton()
|
FastSelect.singleton()
|
||||||
|
@ -27,6 +27,7 @@ from .utils import Vim
|
|||||||
|
|
||||||
MODULE_NAME = 'online_fxreader_pr34_vim'
|
MODULE_NAME = 'online_fxreader_pr34_vim'
|
||||||
|
|
||||||
|
|
||||||
def f1():
|
def f1():
|
||||||
t1 = vim.current.window
|
t1 = vim.current.window
|
||||||
t2 = t1.width
|
t2 = t1.width
|
||||||
@ -163,12 +164,12 @@ class EditorConfigModeline:
|
|||||||
dict[str, str],
|
dict[str, str],
|
||||||
] = dict()
|
] = dict()
|
||||||
|
|
||||||
Vim.run_command(r'''
|
Vim.run_command(r"""
|
||||||
augroup EditorConfigModeline
|
augroup EditorConfigModeline
|
||||||
autocmd!
|
autocmd!
|
||||||
autocmd BufEnter * python3 import online_fxreader_pr34_vim.main; online_fxreader_pr34_vim.main.EditorConfigModeline.singleton().on_buffer()
|
autocmd BufEnter * python3 import online_fxreader_pr34_vim.main; online_fxreader_pr34_vim.main.EditorConfigModeline.singleton().on_buffer()
|
||||||
augroup END
|
augroup END
|
||||||
''')
|
""")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def singleton(cls) -> Self:
|
def singleton(cls) -> Self:
|
||||||
@ -255,7 +256,9 @@ augroup END
|
|||||||
|
|
||||||
# raise NotImplementedError
|
# raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
# EditorConfigModeline.singleton()
|
# EditorConfigModeline.singleton()
|
||||||
|
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
EditorConfigModeline.singleton()
|
EditorConfigModeline.singleton()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import vim
|
import vim
|
||||||
|
|
||||||
|
|
||||||
class Vim:
|
class Vim:
|
||||||
@classmethod
|
@classmethod
|
||||||
def run_command(cls, cmd) -> list[str]:
|
def run_command(cls, cmd) -> list[str]:
|
||||||
@ -9,8 +10,6 @@ class Vim:
|
|||||||
for line in cmd.splitlines():
|
for line in cmd.splitlines():
|
||||||
if line.strip() == '':
|
if line.strip() == '':
|
||||||
continue
|
continue
|
||||||
output.append(
|
output.append(vim.command(line))
|
||||||
vim.command(line)
|
|
||||||
)
|
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
@ -81,7 +81,7 @@ include = [
|
|||||||
'./*.py',
|
'./*.py',
|
||||||
'online/**/*.py',
|
'online/**/*.py',
|
||||||
'online/**/*.pyi',
|
'online/**/*.pyi',
|
||||||
'../dotfiles/.module.vimrc.py',
|
'../dotfiles/.vim/**/*.py',
|
||||||
]
|
]
|
||||||
exclude = [
|
exclude = [
|
||||||
'.venv',
|
'.venv',
|
||||||
|
Loading…
Reference in New Issue
Block a user