def _show_file(nvim: Nvim, *, state: State, settings: Settings, click_type: ClickType) -> None: if click_type is ClickType.tertiary: nvim.api.command("tabnew") win = cur_win(nvim) for key, val in settings.win_actual_opts.items(): win_set_option(nvim, win=win, key=key, val=val) path = state.current if path: hold = click_type is ClickType.secondary mgr = hold_win_pos(nvim) if hold else nullcontext() with mgr: non_fm_windows = tuple( find_non_fm_windows_in_tab(nvim, last_used=state.window_order)) buf = next(find_buffers_with_file(nvim, file=path), None) win = next( chain( find_window_with_file_in_tab( nvim, last_used=state.window_order, file=path), non_fm_windows, ), None, ) or new_window( nvim, last_used=state.window_order, win_local=settings.win_actual_opts, open_left=not settings.open_left, width=None if len(non_fm_windows) else nvim.options["columns"] - state.width - 1, ) set_cur_win(nvim, win=win) non_fm_count = len(non_fm_windows) if click_type is ClickType.v_split and non_fm_count: nvim.api.command("vnew") temp_buf = cur_buf(nvim) buf_set_option(nvim, buf=temp_buf, key="bufhidden", val="wipe") elif click_type is ClickType.h_split and non_fm_count: nvim.api.command("new") temp_buf = cur_buf(nvim) buf_set_option(nvim, buf=temp_buf, key="bufhidden", val="wipe") win = cur_win(nvim) if buf is None: escaped = nvim.funcs.fnameescape(normcase(path)) nvim.command(f"edit! {escaped}") else: win_set_buf(nvim, win=win, buf=buf) resize_fm_windows(nvim, last_used=state.window_order, width=state.width) nvim.api.command("filetype detect")
def _surround(nvim: Nvim) -> None: lhs: str = nvim.vvars["char"] rhs = _CHAR_PAIRS.get(lhs) if rhs: win = cur_win(nvim) buf = win_get_buf(nvim, win=win) row, col = win_get_cursor(nvim, win=win) lines = buf_get_lines(nvim, buf=buf, lo=row, hi=row + 1) line = next(iter(lines)) def cont() -> None: new_col = col + len(lhs.encode()) nvim.api.set_vvar("char", lhs + cast(str, rhs)) set_cur = lambda: win_set_cursor( nvim, win=win, row=row, col=new_col) go(async_call(nvim, set_cur)) if rhs == lhs: is_even = line.count(lhs) % 2 == 0 if is_even: cont() else: counts = Counter(line) if counts[lhs] >= counts[rhs]: cont()
def new_window( nvim: Nvim, *, last_used: Mapping[int, None], win_local: Mapping[str, Union[bool, str]], open_left: bool, width: Optional[int], ) -> Window: split_r = nvim.options["splitright"] wins = tuple( find_windows_in_tab(nvim, last_used=last_used, no_secondary=False)) focus_win = wins[0] if open_left else wins[-1] direction = False if open_left else True nvim.options["splitright"] = direction set_cur_win(nvim, win=focus_win) nvim.command(f"{width}vnew" if width else "vnew") nvim.options["splitright"] = split_r win = cur_win(nvim) buf = win_get_buf(nvim, win) for key, val in win_local.items(): win_set_option(nvim, win=win, key=key, val=val) buf_set_option(nvim, buf=buf, key="bufhidden", val="wipe") return win
def _word(nvim: Nvim, is_inside: bool) -> None: win = cur_win(nvim) buf = win_get_buf(nvim, win=win) row, c = win_get_cursor(nvim, win=win) lines = buf_get_lines(nvim, buf=buf, lo=row, hi=row + 1) line = next(iter(lines)) bline = line.encode() # position under cursor c = min(len(bline) - 1, c) col = len(bline[:c].decode()) + 1 lhs, rhs = line[:col], line[col:] # undo col + 1 offset = len(next(reversed(lhs), "").encode()) ctx = gen_split(lhs, rhs, unifying_chars=UNIFIYING_CHARS) if not (ctx.word_lhs + ctx.word_rhs): words_lhs, words_rhs = ctx.syms_lhs, ctx.syms_rhs else: words_lhs, words_rhs = ctx.word_lhs, ctx.word_rhs c_lhs = c + offset - len(words_lhs.encode()) c_rhs = c + offset + len(words_rhs.encode()) - 1 if is_inside: mark1 = (row, c_lhs) mark2 = (row, c_rhs) else: mark1 = (row, c_lhs - 1) mark2 = (row, c_rhs + 1) set_visual_selection(nvim, win=win, mode="v", mark1=mark1, mark2=mark2)
def trailing_ws(nvim: Nvim) -> None: win = cur_win(nvim) buf = win_get_buf(nvim, win=win) if not writable(nvim, buf=buf): return else: _set_trimmed(nvim, win=win, buf=buf)
def _open_fm_window(nvim: Nvim, settings: Settings, opts: _Args, width: int) -> None: cwin = cur_win(nvim) win = next(find_fm_windows_in_tab(nvim), None) if win: if opts.toggle: wins = list_wins(nvim) if len(wins) > 1: win_close(nvim, win=win) else: set_cur_win(nvim, win=win) else: buf = next(find_fm_buffers(nvim), None) if buf is None: buf = new_fm_buffer(nvim, settings=settings) win = new_window( nvim, win_local=settings.win_actual_opts, open_left=settings.open_left, width=width, ) for key, val in settings.win_local_opts.items(): win_set_option(nvim, win=win, key=key, val=val) win_set_buf(nvim, win=win, buf=buf) _ensure_side_window(nvim, window=win, settings=settings, width=width) if not opts.focus: set_cur_win(nvim, win=cwin)
def _word(nvim: Nvim, is_inside: bool) -> None: win = cur_win(nvim) buf = win_get_buf(nvim, win=win) row, col = win_get_cursor(nvim, win=win) line, *_ = buf_get_lines(nvim, buf=buf, lo=row, hi=row + 1) bline = encode(line) lhs, rhs = decode(bline[:col]), decode(bline[col:]) ctx = gen_split(lhs, rhs, unifying_chars=UNIFIYING_CHARS) if not (ctx.word_lhs + ctx.word_rhs): words_lhs, words_rhs = ctx.syms_lhs, ctx.syms_rhs else: words_lhs, words_rhs = ctx.word_lhs, ctx.word_rhs c_lhs = max(col - len(encode(words_lhs)), 0) c_rhs = max(col + len(encode(words_rhs)) - 1, 0) if is_inside: mark1 = (row, c_lhs) mark2 = (row, c_rhs) else: mark1 = (row, max(0, c_lhs - 1)) mark2 = (row, min(len(bline), c_rhs + 1)) set_visual_selection(nvim, win=win, mode="v", mark1=mark1, mark2=mark2)
def _restore_pos(nvim: Nvim) -> None: win = cur_win(nvim) buf = win_get_buf(nvim, win=win) row, _ = win_get_cursor(nvim, win=win) pos: Optional[int] = buf_get_var(nvim, buf=buf, key=BUF_VAR_NAME) if pos is not None: win_set_cursor(nvim, win=win, row=row, col=pos)
def _entire(nvim: Nvim) -> None: win = cur_win(nvim) buf = win_get_buf(nvim, win=win) count = buf_line_count(nvim, buf=buf) last_line, *_ = buf_get_lines(nvim, buf=buf, lo=-2, hi=-1) mark1 = (0, 0) mark2 = (count - 1, len(encode(last_line))) set_visual_selection(nvim, win=win, mode="V", mark1=mark1, mark2=mark2)
def toggle_floating(nvim: Nvim, *args: str) -> None: curr_win = cur_win(nvim) float_wins = frozenset(list_floatwins(nvim)) if curr_win in float_wins: for win in float_wins: win_close(nvim, win=win) else: _term_open(nvim, *args)
def _new_window(nvim: Nvim, vertical: bool) -> None: nvim.command("vnew" if vertical else "new") win = cur_win(nvim) buf = create_buf(nvim, listed=False, scratch=True, wipe=True, nofile=True, noswap=True) win_set_buf(nvim, win=win, buf=buf)
def _comment_single(nvim: Nvim) -> None: win = cur_win(nvim) buf = win_get_buf(nvim, win=win) if not writable(nvim, buf=buf): return else: row, _ = win_get_cursor(nvim, win=win) lines = buf_get_lines(nvim, buf=buf, lo=row, hi=row + 1) lhs, rhs = buf_commentstr(nvim, buf=buf) new_lines = _toggle_comment(lhs, rhs, lines=lines) buf_set_lines(nvim, buf=buf, lo=row, hi=row + 1, lines=new_lines)
def _resize( nvim: Nvim, state: State, settings: Settings, direction: Callable[[int, int], int] ) -> Optional[Stage]: win = cur_win(nvim) if not is_fm_window(nvim, win=win): return None else: w_width = win.width width = max(direction(w_width, 10), 1) new_state = forward(state, settings=settings, width=width) resize_fm_windows(nvim, width=new_state.width) return Stage(new_state)
def _rename(nvim: Nvim) -> None: win = cur_win(nvim) buf = win_get_buf(nvim, win=win) row, col = win_get_cursor(nvim, win=win) line, *_ = buf_get_lines(nvim, buf=buf, lo=row, hi=row + 1) b_line = encode(line) lhs, rhs = decode(b_line[:col]), decode(b_line[col:]) split = gen_split(lhs=lhs, rhs=rhs, unifying_chars=UNIFIYING_CHARS) word = split.word_lhs + split.word_rhs ans = ask(nvim, question=LANG("rename: "), default=word) if ans: nvim.lua.vim.lsp.buf.rename(ans)
def _line(nvim: Nvim, is_inside: bool) -> None: win = cur_win(nvim) buf = win_get_buf(nvim, win=win) row, _ = win_get_cursor(nvim, win=win) lines = buf_get_lines(nvim, buf=buf, lo=row, hi=row + 1) line = next(iter(lines)) lhs, rhs = (_p_inside if is_inside else _p_around)(line) set_visual_selection(nvim, win=win, mode="v", mark1=(row, lhs), mark2=(row, rhs))
def eval_snips( nvim: Nvim, stack: Stack, visual: bool, maybe_grammar: str = REPL_GRAMMAR, ) -> None: try: grammar = SnippetGrammar[maybe_grammar] except KeyError: grammar = SnippetGrammar.lsp log.warn("%s", "bad snippet grammar -- reverting to LSP") win = cur_win(nvim) buf = win_get_buf(nvim, win=win) line_count = buf_line_count(nvim, buf=buf) path = PurePath(normcase(buf_name(nvim, buf=buf))) comment_str = buf_commentstr(nvim, buf=buf) clipboard = nvim.funcs.getreg() info = ParseInfo(visual="", clipboard=clipboard, comment_str=comment_str) if visual: (lo, _), (hi, _) = operator_marks(nvim, buf=buf, visual_type=None) hi = min(line_count, hi + 1) else: lo, hi = 0, line_count lines = buf_get_lines(nvim, buf=buf, lo=lo, hi=hi) try: compiled = compile_one( stack, grammar=grammar, path=path, info=info, lines=enumerate(lines, start=lo + 1), ) except (LoadError, ParseError) as e: preview = str(e).splitlines() with hold_win_pos(nvim, win=win): set_preview(nvim, syntax="", preview=preview) write(nvim, LANG("snip parse fail")) else: preview = _pprn(compiled).splitlines() with hold_win_pos(nvim, win=win): set_preview(nvim, syntax="yaml", preview=preview) if preview: write(nvim, LANG("snip parse succ")) else: write(nvim, LANG("no snippets found"))
def _toggle_preview(nvim: Nvim) -> None: tab = cur_tab(nvim) wins = tab_list_wins(nvim, tab=tab) closed = False for win in wins: is_preview: bool = win_get_option(nvim, win=win, key="previewwindow") if is_preview: win_close(nvim, win=win) closed = True if not closed: nvim.command("new") win = cur_win(nvim) win_set_option(nvim, win=win, key="previewwindow", val=True) height = nvim.options["previewheight"] nvim.api.win_set_height(win, height)
def _toggle_qf(nvim: Nvim) -> None: tab = cur_tab(nvim) wins = tab_list_wins(nvim, tab=tab) closed = False for win in wins: buf = win_get_buf(nvim, win=win) ft = buf_filetype(nvim, buf=buf) if ft == "qf": win_close(nvim, win=win) closed = True if not closed: nvim.command("copen") win = cur_win(nvim) height = nvim.options["previewheight"] nvim.api.win_set_height(win, height)
def _norm_mv(nvim: Nvim, up: bool) -> None: win = cur_win(nvim) buf = win_get_buf(nvim, win=win) row, _ = win_get_cursor(nvim, win=win) lines = buf_line_count(nvim, buf=buf) if not writable(nvim, buf=buf): return else: if up: if row: nvim.command(f"move -2") else: if row < lines - 1: nvim.command(f"move +1")
def _show_file( nvim: Nvim, *, state: State, settings: Settings, click_type: ClickType ) -> None: if click_type is ClickType.tertiary: nvim.api.command("tabnew") path = state.current if path: hold = click_type is ClickType.secondary mgr = hold_win_pos(nvim) if hold else nullcontext() with mgr: non_fm_windows = tuple(find_non_fm_windows_in_tab(nvim)) buf = next(find_buffers_with_file(nvim, file=path), None) win = next( chain( find_window_with_file_in_tab(nvim, file=path), (win for win in non_fm_windows if _win_nochange(nvim, win=win)), ), None, ) or new_window( nvim, open_left=not settings.open_left, width=None if len(non_fm_windows) else nvim.options["columns"] - state.width - 1, ) set_cur_win(nvim, win=win) non_fm_count = len(non_fm_windows) if click_type is ClickType.v_split and non_fm_count: nvim.api.command("vnew") temp_buf = cur_buf(nvim) buf_set_option(nvim, buf=temp_buf, key="bufhidden", val="wipe") elif click_type is ClickType.h_split and non_fm_count: nvim.api.command("new") temp_buf = cur_buf(nvim) buf_set_option(nvim, buf=temp_buf, key="bufhidden", val="wipe") win = cur_win(nvim) if buf is None: nvim.command(f"edit {path}") else: win_set_buf(nvim, win=win, buf=buf) resize_fm_windows(nvim, state.width) nvim.api.command("filetype detect")
def _toggle_case(nvim: Nvim) -> None: win = cur_win(nvim) row, col = win_get_cursor(nvim, win=win) buf = win_get_buf(nvim, win=win) if writable(nvim, buf=buf): line, *_ = buf_get_lines(nvim, buf=buf, lo=row, hi=row + 1) bline = encode(line) before, after = bline[:col], bline[col:] if after: cur, *post = after pt = decode(bytes((cur, ))) swapped = _swap_case(pt) new = decode(before) + swapped + decode(bytes(post)) pos = len(before) + len(encode(swapped)) buf_set_lines(nvim, buf=buf, lo=row, hi=row + 1, lines=(new, )) win_set_cursor(nvim, win=win, row=row, col=pos)
def new_window(nvim: Nvim, *, open_left: bool, width: Optional[int]) -> Window: split_r = nvim.options["splitright"] wins = tuple(find_windows_in_tab(nvim, no_secondary=False)) focus_win = wins[0] if open_left else wins[-1] direction = False if open_left else True nvim.options["splitright"] = direction set_cur_win(nvim, win=focus_win) nvim.command(f"{width}vnew" if width else "vnew") nvim.options["splitright"] = split_r win = cur_win(nvim) buf = win_get_buf(nvim, win) buf_set_option(nvim, buf=buf, key="bufhidden", val="wipe") return win
def _go_replace_line(nvim: Nvim) -> None: win = cur_win(nvim) buf = win_get_buf(nvim, win=win) if not writable(nvim, buf=buf): return else: linefeed = buf_linefeed(nvim, buf=buf) row, _ = win_get_cursor(nvim, win=win) body: str = nvim.funcs.getreg() new_lines = body.split(linefeed) if new_lines: n = new_lines.pop() if n: new_lines.append(n) buf_set_lines(nvim, buf=buf, lo=row, hi=row + 1, lines=new_lines)
def _toggle_case(nvim: Nvim) -> None: win = cur_win(nvim) row, col = win_get_cursor(nvim, win=win) buf = win_get_buf(nvim, win=win) if not writable(nvim, buf=buf): return else: line, *_ = buf_get_lines(nvim, buf=buf, lo=row, hi=row + 1) bline = line.encode() before, after = bline[:col], bline[col:] cur, *post = after pt = bytes((cur,)).decode() swapped = _swap_case(pt) new = before.decode() + swapped + bytes(post).decode() pos = len(before) + len(swapped.encode()) buf_set_lines(nvim, buf=buf, lo=row, hi=row + 1, lines=(new,)) win_set_cursor(nvim, win=win, row=row, col=pos)
def _indent(nvim: Nvim) -> None: win = cur_win(nvim) buf = win_get_buf(nvim, win) row, _ = win_get_cursor(nvim, win) tabsize: int = buf_get_option(nvim, buf=buf, key="tabstop") lines = buf_get_lines(nvim, buf=buf, lo=0, hi=-1) before, curr, after = lines[:row], lines[row], lines[row + 1:] init_lv = p_indent(curr, tabsize=tabsize) top = row - _p_inside(init_lv, tabsize=tabsize, lines=reversed(before)) btm = row + _p_inside(init_lv, tabsize=tabsize, lines=after) set_visual_selection(nvim, win=win, mode="V", mark1=(top, 0), mark2=(btm, 0))
def nav_mark(nvim: Nvim, stack: Stack) -> None: ns = create_ns(nvim, ns=NS) win = cur_win(nvim) buf = win_get_buf(nvim, win=win) if marks := deque(_marks(nvim, ns=ns, win=win, buf=buf)): mark = marks.popleft() def single() -> None: _single_mark(nvim, mark=mark, marks=marks, ns=ns, win=win, buf=buf) if linked := tuple(m for m in marks if m.idx == mark.idx): edited = _linked_marks(nvim, mark=mark, linked=linked, ns=ns, win=win, buf=buf) if not edited: single()
def _indent(nvim: Nvim) -> None: win = cur_win(nvim) buf = win_get_buf(nvim, win) row, _ = win_get_cursor(nvim, win) tabsize: int = buf_get_option(nvim, buf=buf, key="tabstop") lines = buf_get_lines(nvim, buf=buf, lo=0, hi=-1) before, curr, after = lines[:row], lines[row], lines[row + 1:] init_lv = p_indent(curr, tabsize=tabsize) top = row - _p_inside(init_lv, tabsize=tabsize, lines=reversed(before)) btm = row + _p_inside(init_lv, tabsize=tabsize, lines=after) lines = deque(buf_get_lines(nvim, buf=buf, lo=top, hi=btm + 1)) while lines: if line := lines.popleft(): lines.appendleft(line) break else: top += 1
def _virt_text(nvim: Nvim, ghost: GhostText, text: str) -> None: if ghost.enabled: lhs, rhs = ghost.context overlay, *_ = text.splitlines() or ("", ) virt_text = lhs + overlay + rhs ns = create_ns(nvim, ns=_NS) win = cur_win(nvim) buf = win_get_buf(nvim, win=win) row, col = win_get_cursor(nvim, win=win) mark = ExtMark( idx=1, begin=(row, col), end=(row, col), meta={ "virt_text_pos": "overlay", "hl_mode": "combine", "virt_text": ((virt_text, ghost.highlight_group), ), }, ) clear_ns(nvim, buf=buf, id=ns) buf_set_extmarks(nvim, buf=buf, id=ns, marks=(mark, ))
def indices(nvim: Nvim, state: State, is_visual: bool) -> Iterator[Node]: win = cur_win(nvim) buf = win_get_buf(nvim, win=win) if not is_fm_buffer(nvim, buf=buf): return None else: row, _ = win_get_cursor(nvim, win=win) node = _row_index(state, row) if node: yield node if is_visual: (row1, _), (row2, _) = operator_marks(nvim, buf=buf, visual_type=None) for r in range(row1, row2 + 1): if r != row: node = _row_index(state, r) if node: yield node
def redraw(nvim: Nvim, state: State, focus: Optional[str]) -> None: derived, current = state.derived, state.current focus_row = derived.path_row_lookup.get(focus) if focus else None current_row = derived.path_row_lookup.get(current or "") cwin = cur_win(nvim) ns = nvim.api.create_namespace(FM_NAMESPACE) for win, buf in find_fm_windows(nvim): p_count = buf_line_count(nvim, buf=buf) n_count = len(state.derived.lines) row, col = win_get_cursor(nvim, win=win) (r1, c1), (r2, c2) = operator_marks(nvim, buf=buf, visual_type=None) if focus_row is not None: new_row: Optional[int] = focus_row + 1 elif win != cwin and current_row is not None: new_row = current_row + 1 elif row >= n_count: new_row = n_count elif p_count != n_count: new_row = row + 1 else: new_row = None a1 = Atomic() a1.buf_set_option(buf, "modifiable", True) a2 = _update(nvim, buf=buf, ns=ns, derived=derived) a3 = Atomic() a3.buf_set_option(buf, "modifiable", False) a3.call_function("setpos", ("'<", (buf.number, r1 + 1, c1 + 1, 0))) a3.call_function("setpos", ("'>", (buf.number, r2 + 1, c2 + 1, 0))) if new_row is not None: a3.win_set_cursor(win, (new_row, col)) (a1 + a2 + a3).commit(nvim)