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 _go_replace(nvim: Nvim, visual: VisualTypes) -> None: buf = cur_buf(nvim) if not writable(nvim, buf=buf): return else: linefeed = buf_linefeed(nvim, buf=buf) (r1, c1), (r2, c2) = operator_marks(nvim, buf=buf, visual_type=visual) lines = buf_get_lines(nvim, buf=buf, lo=r1, hi=r2 + 1) if len(lines) > 1: h, *_, t = lines else: h, *_ = t, *_ = lines begin = (r1, min(c1, max(0, len(encode(h)) - 1))) end = (r2, min(len(encode(t)), c2 + 1)) text: str = nvim.funcs.getreg() new_lines = text.split(linefeed) if new_lines: n = new_lines.pop() if n: new_lines.append(n) nvim.options["undolevels"] = nvim.options["undolevels"] buf_set_text(nvim, buf=buf, begin=begin, end=end, text=new_lines)
def cont() -> Iterator[Mark]: row, _ = pos len8 = tuple(accumulate(len(encode(line)) + _NL for line in new_lines)) for r_idx, region in regions: r1, c1, r2, c2 = None, None, None, None last_len = 0 for idx, l8 in enumerate(len8): x_shift = 0 if idx else len(encode(l0_before)) if r1 is None: if l8 > region.begin: r1, c1 = idx + row, region.begin - last_len + x_shift elif l8 == region.begin: r1, c1 = idx + row + 1, x_shift if r2 is None: if l8 > region.end: r2, c2 = idx + row, region.end - last_len + x_shift elif l8 == region.end: r2, c2 = idx + row + 1, x_shift if r1 is not None and r2 is not None: break last_len = l8 assert (r1 is not None and c1 is not None) and ( r2 is not None and c2 is not None ), pformat((region, new_lines)) begin, end = (r1, c1), (r2, c2) mark = Mark(idx=r_idx, begin=begin, end=end, text=region.text) yield mark
def gen_highlights(node: Node, pre: str, icon: str, name: str, ignored: bool) -> Iterator[Highlight]: icon_begin = len(encode(pre)) icon_end = icon_begin + len(encode(icon)) text_begin = icon_end text_end = len(encode(name)) + text_begin if icon_group := search_icon_hl(node, ignored=ignored): hl = Highlight(group=icon_group, begin=icon_begin, end=icon_end) yield hl
def token_parser(context: ParserCtx, stream: TokenStream) -> Parsed: idx = 0 raw_regions: MutableMapping[int, MutableSequence[Region]] = {} slices: MutableSequence[str] = [] begins: MutableSequence[Tuple[int, Union[Begin, DummyBegin]]] = [] bad_tokens: MutableSequence[Tuple[int, Token]] = [] for token in stream: if isinstance(token, Unparsed): token = token bad_tokens.append((idx, token)) elif isinstance(token, str): idx += len(encode(token)) slices.append(token) elif isinstance(token, Begin): begins.append((idx, token)) elif isinstance(token, DummyBegin): begins.append((idx, token)) elif isinstance(token, End): if begins: pos, begin = begins.pop() if isinstance(begin, Begin): acc = raw_regions.setdefault(begin.idx, []) acc.append(Region(begin=pos, end=idx, text="")) else: bad_tokens.append((idx, token)) else: never(token) bad_tokens.extend(begins) text = "".join(slices) min_key = min(raw_regions.keys(), key=lambda i: (i == 0, i)) if raw_regions else -1 cursor = next( iter(raw_regions.get(min_key, ())), Region(begin=len(encode(text)), end=0, text=""), ).begin if bad_tokens: tpl = """ Bad tokens :: Most likely unbalanced `{…}` - ${bad_tokens} Parsed: |- ${text} Original: |- ${ctx} """ msg = Template(dedent(tpl)).substitute(bad_tokens=bad_tokens, text=text, ctx=context.text) raise ParseError(msg) regions = tuple(_consolidate(text, regions=raw_regions)) parsed = Parsed(text=text, cursor=cursor, regions=regions) return parsed
def _range_edit_trans( unifying_chars: AbstractSet[str], smart: bool, ctx: Context, primary: bool, lines: _Lines, edit: RangeEdit, ) -> EditInstruction: new_lines = edit.new_text.split(ctx.linefeed) if (primary and not isinstance(edit, ParsedEdit) and len(new_lines) <= 1 and edit.begin == edit.end): return _edit_trans(unifying_chars, smart=smart, ctx=ctx, lines=lines, edit=edit) else: (r1, ec1), (r2, ec2) = sorted((edit.begin, edit.end)) if edit.encoding == UTF16: c1 = len( encode(decode(lines.b_lines16[r1][:ec1 * 2], encoding=UTF16))) c2 = len( encode(decode(lines.b_lines16[r2][:ec2 * 2], encoding=UTF16))) elif edit.encoding == UTF8: c1 = len(lines.b_lines8[r1][:ec1]) c2 = len(lines.b_lines8[r2][:ec2]) else: raise ValueError(f"Unknown encoding -- {edit.encoding}") begin = r1, c1 end = r2, c2 lines_before = (edit.new_prefix.split(ctx.linefeed) if isinstance( edit, ParsedEdit) else new_lines) cursor_yoffset = (r2 - r1) + (len(lines_before) - 1) cursor_xpos = ((len(encode(lines_before[-1])) if len(lines_before) > 1 else len(lines.b_lines8[r2][:c1]) + len(encode(lines_before[0]))) if primary else -1) inst = EditInstruction( primary=primary, begin=begin, end=end, cursor_yoffset=cursor_yoffset, cursor_xpos=cursor_xpos, new_lines=new_lines, ) return inst
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 _restore(nvim: Nvim, win: Window, buf: Buffer, pos: NvimPos) -> Tuple[str, Optional[int]]: row, _ = pos ns = create_ns(nvim, ns=NS) marks = tuple(buf_get_extmarks(nvim, buf=buf, id=ns)) if len(marks) != 2: return "", 0 else: m1, m2 = marks after, *_ = buf_get_lines(nvim, buf=buf, lo=row, hi=row + 1) cur_row, cur_col = win_get_cursor(nvim, win=win) (_, lo), (_, hi) = m1.end, m2.begin binserted = encode(after)[lo:hi] inserted = decode(binserted) movement = cur_col - lo if cur_row == row and lo <= cur_col <= hi else None if inserted: buf_set_text(nvim, buf=buf, begin=m1.end, end=m2.begin, text=("", )) return inserted, movement
def _parse(nvim: Nvim, buf: Buffer, stack: Stack, state: State, comp: Completion) -> Tuple[Edit, Sequence[Mark]]: if isinstance(comp.primary_edit, SnippetEdit): comment_str = buf_commentstr(nvim, buf=buf) clipboard = nvim.funcs.getreg() info = ParseInfo(visual="", clipboard=clipboard, comment_str=comment_str) if isinstance(comp.primary_edit, SnippetRangeEdit): row, col = comp.primary_edit.begin line, *_ = buf_get_lines(nvim, buf=buf, lo=row, hi=row + 1) line_before = decode( encode(line, encoding=comp.primary_edit.encoding)[:col]) edit, marks = parse_range( context=state.context, snippet=comp.primary_edit, info=info, line_before=line_before, ) else: edit, marks = parse_norm( stack.settings.match.unifying_chars, smart=stack.settings.completion.smart, context=state.context, snippet=comp.primary_edit, info=info, ) else: edit, marks = comp.primary_edit, () return edit, marks
def _shift( instructions: Iterable[EditInstruction], ) -> Tuple[Sequence[EditInstruction], _MarkShift]: row_shift = 0 cols_shift: MutableMapping[int, int] = {} m_shift = _MarkShift(row=0) new_insts: MutableSequence[EditInstruction] = [] for inst in instructions: (r1, c1), (r2, c2) = inst.begin, inst.end new_inst = EditInstruction( primary=inst.primary, begin=(r1 + row_shift, c1 + cols_shift.get(r1, 0)), end=(r2 + row_shift, c2 + cols_shift.get(r2, 0)), cursor_yoffset=inst.cursor_yoffset, cursor_xpos=inst.cursor_xpos, new_lines=inst.new_lines, ) if new_inst.primary: m_shift = _MarkShift(row=row_shift) row_shift += (r2 - r1) + len(inst.new_lines) - 1 f_length = len(encode(inst.new_lines[-1])) if inst.new_lines else 0 cols_shift[r2] = -(c2 - c1) + f_length if r1 == r2 else -c2 + f_length new_insts.append(new_inst) return new_insts, m_shift
def _lines(lines: Sequence[str]) -> _Lines: b_lines8 = tuple(map(encode, lines)) return _Lines( lines=lines, b_lines8=b_lines8, b_lines16=tuple(encode(line, encoding=UTF16) for line in lines), len8=tuple(len(line) for line in b_lines8), )
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 _set_trimmed(nvim: Nvim, win: Window, buf: Buffer) -> None: row, col = win_get_cursor(nvim, win=win) lines = buf_get_lines(nvim, buf=buf, lo=0, hi=-1) new_lines = [ decode(encode(line)[:col]) + decode(encode(line)[col:]).rstrip() if r == row else line.rstrip() for r, line in enumerate(lines) ] while new_lines: line = new_lines.pop() if line or len(new_lines) <= row: new_lines.append(line) break if len(new_lines) < len(lines): new_lines.append("") if new_lines != lines: buf_set_lines(nvim, buf=buf, lo=0, hi=-1, lines=new_lines) win_set_cursor(nvim, win=win, row=row, col=col)
async def _run( nvim: Nvim, ctx: BufContext, attrs: Iterable[FmtAttrs], cwd: PurePath ) -> None: body = encode(ctx.linefeed.join(ctx.lines)) path = Path(ctx.filename) with make_temp(path) as temp: temp.write_bytes(body) errs = [ err async for err in aiterify( _fmt_output(attr, ctx=ctx, cwd=cwd, temp=temp) for attr in attrs ) if err ] errors = (ctx.linefeed * 2).join(errs) if errors: def c1() -> None: set_preview_content(nvim, text=errors) write(nvim, LANG("prettier failed")) await async_call(nvim, c1) else: def c2() -> None: 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) saved = {win: pos for win, pos in it()} lines = temp.read_text().split(ctx.linefeed) if lines: l = lines.pop() if l: lines.append(l) buf_set_lines(nvim, buf=ctx.buf, lo=0, hi=-1, lines=lines) for win, (row, col) in saved.items(): new_row = min(row, len(lines) - 1) with suppress(NvimError): win_set_cursor(nvim, win=win, row=new_row, col=col) detect_tabs(nvim, buf=ctx.buf) prettiers = LANG("step join sep").join(attr.bin for attr in attrs) nice = LANG("prettier succeeded", prettiers=prettiers) write(nvim, nice) await async_call(nvim, c2)
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 now(nvim: Nvim, stack: Stack, args: Sequence[str]) -> None: try: ns = _parse_args(args) except ArgparseError as e: write(nvim, e, error=True) else: if not ns.shut_up: lo, hi = _HELO.chars chars = choice(range(lo, hi)) star = (choice(_HELO.stars),) birds = " ".join(chain(star, sample(_HELO.cocks, k=chars), star)) helo = choice(_HELO.helo) msg = f"{birds} {helo}{linesep}" encoded = encode(msg) stdout.buffer.write(encoded) stdout.buffer.flush()
async def cont() -> Optional[str]: async with self._lock: if self._bin and not self._proc: self._proc = await _proc(self._bin, cwd=cwd) if self._proc: self._cwd = cwd if not self._proc: return None else: assert self._proc.stdin and self._proc.stdout try: self._proc.stdin.write(encode(json)) self._proc.stdin.write(b"\n") await self._proc.stdin.drain() out = await self._proc.stdout.readline() except (ConnectionError, LimitOverrunError, ValueError): return await self._clean() else: return decode(out)
async def comp_lsp( nvim: Nvim, short_name: str, weight_adjust: float, context: Context, clients: AbstractSet[str], ) -> AsyncIterator[LSPcomp]: row, c = context.position col = len(encode(context.line_before[:c], encoding=UTF16)) // 2 async for client, reply in async_request(nvim, "lsp_comp", clients, (row, col)): resp = cast(CompletionResponse, reply) yield parse( ExternLSP, client=client, short_name=short_name, weight_adjust=weight_adjust, resp=resp, )
def _contextual_edit_trans(ctx: Context, lines: _Lines, edit: ContextualEdit) -> EditInstruction: row, col = ctx.position old_prefix_lines = edit.old_prefix.split(ctx.linefeed) old_suffix_lines = edit.old_suffix.split(ctx.linefeed) r1 = row - (len(old_prefix_lines) - 1) r2 = row + (len(old_suffix_lines) - 1) c1 = (lines.len8[r1] - len(encode(old_prefix_lines[0])) if len(old_prefix_lines) > 1 else col - len(encode(old_prefix_lines[0]))) c2 = (len(encode(old_suffix_lines[-1])) if len(old_prefix_lines) > 1 else col + len(encode(old_suffix_lines[0]))) begin = r1, c1 end = r2, c2 new_lines = edit.new_text.split(ctx.linefeed) new_prefix_lines = edit.new_prefix.split(ctx.linefeed) cursor_yoffset = -len(old_prefix_lines) + len(new_prefix_lines) cursor_xpos = (len(encode(new_prefix_lines[-1])) if len(new_prefix_lines) > 1 else len(encode(ctx.line_before)) - len(encode(old_prefix_lines[-1])) + len(encode(new_prefix_lines[0]))) inst = EditInstruction( primary=True, begin=begin, end=end, cursor_yoffset=cursor_yoffset, cursor_xpos=cursor_xpos, new_lines=new_lines, ) return inst
def _p_inside(line: str) -> Tuple[int, int]: lhs = len(encode(line)) - len(encode(line.lstrip())) rhs = len(encode(line.rstrip())) - 1 return lhs, rhs
def context( nvim: Nvim, db: BDB, options: MatchOptions, state: State, manual: bool ) -> Context: with Atomic() as (atomic, ns): ns.scr_col = atomic.call_function("screencol", ()) ns.buf = atomic.get_current_buf() ns.name = atomic.buf_get_name(0) ns.line_count = atomic.buf_line_count(0) ns.filetype = atomic.buf_get_option(0, "filetype") ns.commentstring = atomic.buf_get_option(0, "commentstring") ns.fileformat = atomic.buf_get_option(0, "fileformat") ns.tabstop = atomic.buf_get_option(0, "tabstop") ns.expandtab = atomic.buf_get_option(0, "expandtab") ns.cursor = atomic.win_get_cursor(0) atomic.commit(nvim) scr_col = ns.scr_col buf = cast(Buffer, ns.buf) (r, col) = cast(Tuple[int, int], ns.cursor) row = r - 1 pos = (row, col) buf_line_count = ns.line_count filename = normcase(cast(str, ns.name)) filetype = cast(str, ns.filetype) comment_str = cast(str, ns.commentstring) tabstop = ns.tabstop expandtab = cast(bool, ns.expandtab) linefeed = cast(Literal["\n", "\r", "\r\n"], LFfmt[cast(str, ns.fileformat)].value) lo = max(0, row - options.proximate_lines) hi = min(buf_line_count, row + options.proximate_lines + 1) lines = buf_get_lines(nvim, buf=buf, lo=lo, hi=hi) if DEBUG: db_line_count, db_lit = db.lines(buf.number, lo=lo, hi=hi) db_lines = tuple(db_lit) assert db_line_count in { buf_line_count - 1, buf_line_count, buf_line_count + 1, }, (db_line_count, buf_line_count) assert tuple( "" if idx == row else line for idx, line in enumerate(db_lines, start=lo) ) == tuple( "" if idx == row else line for idx, line in enumerate(lines, start=lo) ), linesep.join( unified_diff(lines, db_lines) ) r = row - lo line = lines[r] lines_before, lines_after = lines[:r], lines[r + 1 :] lhs, _, rhs = comment_str.partition("%s") b_line = encode(line) before, after = decode(b_line[:col]), decode(b_line[col:]) split = gen_split(lhs=before, rhs=after, unifying_chars=options.unifying_chars) ctx = Context( manual=manual, change_id=state.change_id, commit_id=state.commit_id, cwd=state.cwd, buf_id=buf.number, filename=filename, filetype=filetype, line_count=buf_line_count, linefeed=linefeed, tabstop=tabstop, expandtab=expandtab, comment=(lhs, rhs), position=pos, scr_col=scr_col, line=split.lhs + split.rhs, line_before=split.lhs, line_after=split.rhs, lines=lines, lines_before=lines_before, lines_after=lines_after, words=split.word_lhs + split.word_rhs, words_before=split.word_lhs, words_after=split.word_rhs, syms=split.syms_lhs + split.syms_rhs, syms_before=split.syms_lhs, syms_after=split.syms_rhs, ws_before=split.ws_lhs, ws_after=split.ws_rhs, ) return ctx
def _p_around(line: str) -> Tuple[int, int]: return 0, len(encode(line))
SnippetEdit, SnippetGrammar, SnippetRangeEdit, ) from .consts import SNIP_LINE_SEP from .parsers.lsp import tokenizer as lsp_tokenizer from .parsers.snu import tokenizer as snu_tokenizer from .parsers.types import Parsed, ParseInfo, Region @dataclass(frozen=True) class ParsedEdit(BaseRangeEdit): new_prefix: str _NL = len(encode(SNIP_LINE_SEP)) def _indent(context: Context, line_before: str, snippet: str) -> str: indent = indent_to_line(context, line_before=line_before) expanded = expand_tabs(context, text=snippet) lines = tuple( lhs + rhs for lhs, rhs in zip(chain(("",), repeat(indent)), expanded.splitlines(True)) ) indented = "".join(lines) return indented def _marks( pos: NvimPos,
def _comp_done(nvim: Nvim, stack: Stack, event: Mapping[str, Any]) -> None: data = event.get("user_data") if data: try: uid = _UDECODER(data) except DecodeError: pass else: s = state() if metric := stack.metrics.get(uid): row, col = s.context.position buf = cur_buf(nvim) ns = create_ns(nvim, ns=NS) clear_ns(nvim, buf=buf, id=ns) before, *_ = buf_get_lines(nvim, buf=buf, lo=row, hi=row + 1) e1 = ExtMark( idx=1, begin=(row, 0), end=(row, col), meta={}, ) e2 = ExtMark( idx=2, begin=(row, col), end=(row, len(encode(before))), meta={}, ) buf_set_extmarks(nvim, buf=buf, id=ns, marks=(e1, e2)) async def cont() -> None: if metric: new_metric = await _resolve(nvim, stack=stack, metric=metric) async def c2() -> None: if isinstance((extern := new_metric.comp.extern), ExternLSP): await cmd(nvim, extern=extern) def c1() -> None: if new_metric.comp.uid in stack.metrics: inserted_at = edit( nvim, stack=stack, state=s, metric=new_metric, synthetic=False, ) ins_pos = inserted_at or (-1, -1) state( inserted_pos=ins_pos, last_edit=new_metric, commit_id=uuid4(), ) go(nvim, aw=c2()) else: log.warn("%s", "delayed completion") await async_call(nvim, c1) go(nvim, aw=cont())
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 while lines: if line := lines.pop(): lines.append(line) break else: btm -= 1 if lines: *_, btm_line = lines mark1, mark2 = (top, 0), (btm, len(encode(btm_line))) else: mark1, mark2 = (row, 0), (row, len(encode(curr))) set_visual_selection(nvim, win=win, mode="V", mark1=mark1, mark2=mark2) keymap.o("ii") << f"<cmd>lua {NAMESPACE}.{_indent.name}()<cr>" keymap.v("ii") << rf"<c-\><c-n><cmd>lua {NAMESPACE}.{_indent.name}()<cr>"