Example #1
0
def _detect_tabs(nvim: Nvim) -> None:
    buf = cur_buf(nvim)
    count = buf_line_count(nvim, buf=buf)
    rows = min(count, 100)
    lines = buf_get_lines(nvim, buf=buf, lo=0, hi=rows)
    _set_tabsize(nvim, buf=buf, lines=lines)
    _set_usetab(nvim, buf=buf, lines=lines)
Example #2
0
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)
Example #3
0
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)
Example #4
0
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()
Example #5
0
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)
Example #6
0
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
Example #7
0
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
Example #8
0
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)
Example #9
0
def current_ctx(nvim: Nvim) -> Tuple[str, BufContext]:
    cwd = get_cwd(nvim)
    buf = cur_buf(nvim)
    filename = buf_name(nvim, buf=buf)
    filetype = buf_filetype(nvim, buf=buf)
    tabsize: int = buf_get_option(nvim, buf=buf, key="tabstop")
    lines: Sequence[str] = buf_get_lines(nvim, buf=buf, lo=0, hi=-1)
    return cwd, BufContext(
        buf=buf, filename=filename, filetype=filetype, tabsize=tabsize, lines=lines
    )
Example #10
0
def _sort_lines(nvim: Nvim, visual: VisualTypes) -> None:
    buf = cur_buf(nvim)
    if not writable(nvim, buf=buf):
        return
    else:
        (row1, _), (row2, _) = operator_marks(nvim,
                                              buf=buf,
                                              visual_type=visual)
        lines = buf_get_lines(nvim, buf=buf, lo=row1, hi=row2 + 1)
        new_lines = sorted(lines, key=strxfrm)
        buf_set_lines(nvim, buf=buf, lo=row1, hi=row2 + 1, lines=new_lines)
Example #11
0
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
Example #12
0
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)
Example #13
0
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
Example #14
0
def _comment(nvim: Nvim, visual: VisualTypes) -> None:
    buf = cur_buf(nvim)
    if not writable(nvim, buf=buf):
        return
    else:
        (row1, _), (row2, _) = operator_marks(nvim,
                                              buf=buf,
                                              visual_type=visual)
        lines = buf_get_lines(nvim, buf=buf, lo=row1, hi=row2 + 1)
        lhs, rhs = buf_commentstr(nvim, buf=buf)
        new_lines = _toggle_comment(lhs, rhs, lines=lines)
        buf_set_lines(nvim, buf=buf, lo=row1, hi=row2 + 1, lines=new_lines)
Example #15
0
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))
Example #16
0
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)
Example #17
0
def _get_selected(nvim: Nvim, buf: Buffer, visual_type: VisualTypes) -> str:
    (row1, col1), (row2, col2) = operator_marks(nvim,
                                                buf=buf,
                                                visual_type=visual_type)
    lines = buf_get_lines(nvim, buf=buf, lo=row1, hi=row2 + 1)

    if len(lines) == 1:
        return lines[0].encode()[col1:col2 + 1].decode()
    else:
        head = lines[0].encode()[col1:].decode()
        body = lines[1:-1]
        tail = lines[-1].encode()[:col2 + 1].decode()
        return linesep.join(chain((head, ), body, (tail, )))
Example #18
0
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"))
Example #19
0
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)
Example #20
0
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)
Example #21
0
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))
Example #22
0
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)
Example #23
0
def edit(
    nvim: Nvim,
    stack: Stack,
    state: State,
    metric: Metric,
    synthetic: bool,
) -> Optional[NvimPos]:
    win = cur_win(nvim)
    buf = win_get_buf(nvim, win=win)
    if buf.number != state.context.buf_id:
        log.warn("%s", "stale buffer")
        return None
    else:
        nvim.options["undolevels"] = nvim.options["undolevels"]

        if synthetic:
            inserted, movement = "", None
        else:
            inserted, movement = _restore(nvim,
                                          win=win,
                                          buf=buf,
                                          pos=state.context.position)

        try:
            primary, marks = _parse(nvim,
                                    buf=buf,
                                    stack=stack,
                                    state=state,
                                    comp=metric.comp)
        except (NvimError, ParseError) as e:
            primary, marks = metric.comp.primary_edit, ()
            write(nvim, LANG("failed to parse snippet"))
            log.info("%s", e)

        lo, hi = _rows_to_fetch(
            state.context,
            primary,
            *metric.comp.secondary_edits,
        )
        if lo < 0 or hi > state.context.line_count:
            log.warn("%s", pformat(("OUT OF BOUNDS", (lo, hi), metric)))
            return None
        else:
            limited_lines = buf_get_lines(nvim, buf=buf, lo=lo, hi=hi)
            lines = [*chain(repeat("", times=lo), limited_lines)]
            view = _lines(lines)

            instructions = _consolidate(*_instructions(
                state.context,
                unifying_chars=stack.settings.match.unifying_chars,
                smart=stack.settings.completion.smart,
                lines=view,
                primary=primary,
                secondary=metric.comp.secondary_edits,
            ))
            n_row, n_col = _cursor(
                state.context.position,
                instructions=instructions,
            )

            if not synthetic:
                stack.idb.inserted(metric.instance.bytes,
                                   sort_by=metric.comp.sort_by)

            m_shift = apply(nvim, buf=buf, instructions=instructions)
            if inserted:
                try:
                    buf_set_text(
                        nvim,
                        buf=buf,
                        begin=(n_row, n_col),
                        end=(n_row, n_col),
                        text=(inserted, ),
                    )
                except NvimError as e:
                    log.warn("%s", e)

            if movement is not None:
                try:
                    win_set_cursor(nvim,
                                   win=win,
                                   row=n_row,
                                   col=n_col + movement)
                except NvimError as e:
                    log.warn("%s", e)

            if marks:
                new_marks = tuple(_shift_marks(m_shift, marks=marks))
                mark(nvim, settings=stack.settings, buf=buf, marks=new_marks)

            if DEBUG:
                log.debug(
                    "%s",
                    pformat((
                        (metric.comp.primary_edit,
                         *metric.comp.secondary_edits),
                        instructions,
                    )),
                )

            return n_row, n_col
Example #24
0
def detect_tabs(nvim: Nvim, buf: Buffer) -> None:
    count = buf_line_count(nvim, buf=buf)
    rows = min(count, 100)
    lines = buf_get_lines(nvim, buf=buf, lo=0, hi=rows)
    _set_tabsize(nvim, buf=buf, lines=lines)
    _set_usetab(nvim, buf=buf, lines=lines)
Example #25
0
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
Example #26
0
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())