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 it() -> Iterator[Tuple[Window, Tuple[int, int]]]: wins = list_wins(nvim) for win in wins: buf = win_get_buf(nvim, win=win) if buf == ctx.buf: row, col = win_get_cursor(nvim, win) yield win, (row, col)
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 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, 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 kill_buffers( nvim: Nvim, last_used: Mapping[int, None], paths: AbstractSet[PurePath], reopen: Mapping[PurePath, PurePath], ) -> None: active = ({ win_get_buf(nvim, win=win): win for win in find_non_fm_windows_in_tab( nvim, last_used=last_used, ) } if reopen else {}) for buf in list_bufs(nvim, listed=True): name = PurePath(buf_name(nvim, buf=buf)) buf_paths = ancestors(name) | {name} if not buf_paths.isdisjoint(paths): win = active.get(buf) new_path = reopen.get(name) if reopen and win and new_path: p = Path(name) p.touch() with hold_win_pos(nvim): set_cur_win(nvim, win=win) escaped = nvim.funcs.fnameescape(normcase(new_path)) nvim.command(f"edit! {escaped}") p.unlink(missing_ok=True) buf_close(nvim, buf=buf)
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 _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 find_non_fm_windows_in_tab( nvim: Nvim, last_used: Mapping[int, None]) -> Iterator[Window]: for win in find_windows_in_tab(nvim, last_used=last_used, no_secondary=True): buf = win_get_buf(nvim, win=win) if not is_fm_buffer(nvim, buf=buf): yield win
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 find_window_with_file_in_tab(nvim: Nvim, last_used: Mapping[int, None], file: PurePath) -> Iterator[Window]: for win in find_windows_in_tab(nvim, last_used=last_used, no_secondary=True): buf = win_get_buf(nvim, win=win) name = PurePath(buf_name(nvim, buf=buf)) if name == file: yield win
def _resize_secondary(nvim: Nvim) -> None: tab = cur_tab(nvim) wins = tab_list_wins(nvim, tab=tab) height = nvim.options["previewheight"] for win in wins: is_preview: bool = win_get_option(nvim, win=win, key="previewwindow") buf = win_get_buf(nvim, win=win) ft = buf_filetype(nvim, buf=buf) if is_preview or ft == "qf": nvim.api.win_set_height(win, height)
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 preview_preview(nvim: Nvim, stack: Stack, *_: str) -> str: win = next(_ls(nvim), None) if win: buf = win_get_buf(nvim, win=win) syntax = buf_get_option(nvim, buf=buf, key="syntax") lines = buf_get_lines(nvim, buf=buf, lo=0, hi=-1) nvim.exec_lua(f"{NAMESPACE}.{_bigger_preview.name}(...)", (syntax, lines)) escaped: str = nvim.api.replace_termcodes("<c-e>", True, False, True) return escaped
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 _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 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_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 _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 _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 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 _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 find_windows_in_tab(nvim: Nvim, no_secondary: bool) -> Iterator[Window]: def key_by(win: Window) -> Tuple[int, int]: """ -> sort by row, then col """ row, col = nvim.funcs.win_screenpos(win.number) return col, row tab = cur_tab(nvim) wins = tab_list_wins(nvim, tab=tab) for win in sorted(wins, key=key_by): is_preview: bool = win_get_option(nvim, win=win, key="previewwindow") buf = win_get_buf(nvim, win) ft = buf_filetype(nvim, buf=buf) is_secondary = is_preview or ft == "qf" if not is_secondary or not no_secondary: yield win
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 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 _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 _visual_mv(nvim: Nvim, up: bool) -> None: win = cur_win(nvim) buf = win_get_buf(nvim, win=win) (row1, col1), (row2, col2) = operator_marks(nvim, buf=buf, visual_type=None) lines = buf_line_count(nvim, buf=buf) if not writable(nvim, buf=buf): return else: if up: if row1 <= 0: nvim.command("norm! gv") else: nvim.command(f"{row1+1},{row2+1}move {row1-1}") set_visual_selection( nvim, win=win, mode="v", mark1=(row1 - 1, col1), mark2=(row2 - 1, col2), reverse=True, ) else: if row2 >= lines - 1: nvim.command("norm! gv") else: nvim.command(f"{row1+1},{row2+1}move {row2+2}") set_visual_selection( nvim, win=win, mode="v", mark1=(row1 + 1, col1), mark2=(row2 + 1, col2), )