feat(completion): Lua driver, popup bindings, LSP scoping + flush seams
Q#C1/C9: builtin/runtime/completion.lua reconstructs typing intent
from state (buffer.after-edit has no payload): a {buffer, cursor}
snapshot recognizes the single-byte-advance typing signature, so
paste/undo/kill/remote edits never auto-open; prefix >= 2 opens off
the synchronous providers, server trigger chars open a pending session
that materializes when the LSP answer lands; refresh-on-typing
re-derives the prefix from the text; a core-closed popup suppresses
reopen off the same edit (the accept case). completion.at-point on
C-M-i covers deliberate invocation; the driver filters collect() to
score >= 0 (collect keeps non-matches, merely sorted last).
Q#C8: CompletionContext gains uri; the built-in LSP provider scopes to
it (legacy global drain only when absent); Lua providers get uri as a
trailing ninth positional arg; context_for can now express char
triggers + uri. pmacs.lsp.attachment_for_request() exposes the
flushing accessor (attached_for_active) so completion requests answer
against current text, not the debounced didChange backlog.
Q#C2 write path: pmacs.completion.popup_show/popup_hide/popup_visible
publish into the core session (kind tags shared with collect() rows,
so driver code passes rows straight through).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
361cc542c7
commit
53d771a935
|
|
@ -0,0 +1,228 @@
|
|||
-- completion.lua --- in-buffer completion popup driver (Arc 1a).
|
||||
--
|
||||
-- Wires the M4.11 provider framework (`pmacs.completion.collect`) and
|
||||
-- the M4.7 LSP request path to the core's popup session
|
||||
-- (`pmacs.completion.popup_show/hide`, Q#C2). The dispatcher owns
|
||||
-- navigation and accept (Q#C3/Q#C7); this file decides WHEN the popup
|
||||
-- opens, WHAT it shows, and keeps it fresh as the user types.
|
||||
--
|
||||
-- Trigger policy (Q#C9): `buffer.after-edit` carries no payload, so
|
||||
-- intent is reconstructed from state. A snapshot of {buffer, cursor}
|
||||
-- from the previous invocation recognizes the single-char typing
|
||||
-- signature (cursor advanced exactly one byte --- word and LSP
|
||||
-- trigger characters are all ASCII); paste, undo, kill, and remote
|
||||
-- edits (any other delta) never auto-open. `C-M-i`
|
||||
-- (`completion.at-point`) covers deliberate invocation.
|
||||
--
|
||||
-- Framing: docs/in-buffer-completion-framing.md.
|
||||
|
||||
local MIN_PREFIX = 2 -- typed word length before the popup auto-opens
|
||||
local MAX_ROWS = 64 -- cap on candidates published to the session
|
||||
|
||||
-- Snapshot of the previous after-edit invocation (Q#C9).
|
||||
local last = { key = nil, cursor = nil }
|
||||
|
||||
-- Driver-side mirror of the session we opened: { key, anchor,
|
||||
-- pending }. `popup_visible()` is the truth about the popup --- the
|
||||
-- core closes it independently (validation, accept, dismiss, a modal
|
||||
-- opening) --- so the mirror only remembers the anchor and detects
|
||||
-- "the core closed it since we last looked", which doubles as the
|
||||
-- reopen-after-accept suppressor. `pending = true` marks a session
|
||||
-- whose popup hasn't opened yet (awaiting the LSP response).
|
||||
local session = nil
|
||||
|
||||
local function word_prefix_before(buf, cursor)
|
||||
local start = cursor - 64
|
||||
if start < 0 then start = 0 end
|
||||
local ok, chunk = pcall(function() return buf:slice(start, cursor) end)
|
||||
if not ok or type(chunk) ~= "string" then return "" end
|
||||
return chunk:match("[%w_]*$") or ""
|
||||
end
|
||||
|
||||
local function char_before(buf, cursor)
|
||||
if cursor < 1 then return nil end
|
||||
local ok, ch = pcall(function() return buf:slice(cursor - 1, cursor) end)
|
||||
if ok and type(ch) == "string" and #ch == 1 then return ch end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function close_popup()
|
||||
session = nil
|
||||
if pmacs.completion.popup_visible() then pmacs.completion.popup_hide() end
|
||||
end
|
||||
|
||||
-- Collect through the framework, drop non-matches (collect keeps
|
||||
-- negative-score rows, merely sorted last --- Q#C1), cap, and shape
|
||||
-- rows for popup_show. Returns the rows plus the uncapped match count.
|
||||
local function collect_rows(buf, prefix, trigger, trigger_char)
|
||||
local rec = pmacs.lsp.active_attachment() -- peek: uri/language only
|
||||
local ok_text, text = pcall(function() return buf:slice(0, buf:len()) end)
|
||||
if not ok_text or type(text) ~= "string" then return {}, 0 end
|
||||
local ctx = {
|
||||
prefix = prefix,
|
||||
line = pmacs.editor.cursor_line(),
|
||||
col = pmacs.editor.cursor_col(),
|
||||
buffer_text = text,
|
||||
language = rec and rec.language or nil,
|
||||
uri = rec and rec.uri or nil, -- Q#C8: scope URI-keyed providers
|
||||
trigger = trigger,
|
||||
trigger_char = trigger_char,
|
||||
}
|
||||
local ok, cands = pcall(pmacs.completion.collect, ctx)
|
||||
if not ok or type(cands) ~= "table" then return {}, 0 end
|
||||
local rows, total = {}, 0
|
||||
for _, c in ipairs(cands) do
|
||||
if (c.score or -1) >= 0 then
|
||||
total = total + 1
|
||||
if #rows < MAX_ROWS then
|
||||
rows[#rows + 1] = {
|
||||
label = c.label,
|
||||
kind = c.kind,
|
||||
detail = c.detail,
|
||||
insert_text = c.insert_text,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
return rows, total
|
||||
end
|
||||
|
||||
-- Re-collect and show the session at `anchor`. On zero matches the
|
||||
-- popup hides; the caller decides whether the session survives as
|
||||
-- `pending` (initial trigger-char / at-point opens awaiting the LSP)
|
||||
-- or dies (a refresh that narrowed to nothing). Returns true when the
|
||||
-- popup is showing afterwards.
|
||||
local function publish(buf, anchor, prefix, trigger, trigger_char)
|
||||
local rows, total = collect_rows(buf, prefix, trigger, trigger_char)
|
||||
if #rows == 0 then
|
||||
if pmacs.completion.popup_visible() then pmacs.completion.popup_hide() end
|
||||
return false
|
||||
end
|
||||
session = { key = tostring(buf), anchor = anchor }
|
||||
pmacs.completion.popup_show {
|
||||
buffer = buf,
|
||||
anchor = anchor,
|
||||
prefix = prefix,
|
||||
total = total,
|
||||
candidates = rows,
|
||||
}
|
||||
return true
|
||||
end
|
||||
|
||||
-- Q#C8 "show fast, refresh on arrival": fire textDocument/completion
|
||||
-- through the FLUSHING accessor (the server must see current text),
|
||||
-- then re-publish when the response lands --- if the session is still
|
||||
-- anchored where it was when the request left.
|
||||
local function request_lsp_then_refresh()
|
||||
local rec = pmacs.lsp.attachment_for_request()
|
||||
if not rec or not session then return end
|
||||
local line = pmacs.editor.cursor_line()
|
||||
local col = pmacs.editor.cursor_col()
|
||||
local anchor_at_request = session.anchor
|
||||
local key_at_request = session.key
|
||||
pmacs.async(function()
|
||||
local ok = pcall(function()
|
||||
pmacs.lsp.request_completion(rec.server, rec.uri, line, col):await()
|
||||
end)
|
||||
if not ok or not session then return end
|
||||
if session.key ~= key_at_request or session.anchor ~= anchor_at_request then return end
|
||||
local buf = pmacs.window.buffer()
|
||||
if not buf or tostring(buf) ~= session.key then return end
|
||||
local cursor = pmacs.editor.cursor()
|
||||
local prefix = word_prefix_before(buf, cursor)
|
||||
if cursor - #prefix ~= session.anchor then return end
|
||||
if not publish(buf, session.anchor, prefix, "incomplete", nil) and session.pending then
|
||||
-- Still nothing, even with the server's answer: the pending
|
||||
-- session is dead.
|
||||
session = nil
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
pmacs.hook.add("buffer.after-edit", function()
|
||||
local buf = pmacs.window.buffer()
|
||||
if not buf then
|
||||
close_popup()
|
||||
last.key, last.cursor = nil, nil
|
||||
return
|
||||
end
|
||||
local key = tostring(buf)
|
||||
local cursor = pmacs.editor.cursor()
|
||||
local prev_key, prev_cursor = last.key, last.cursor
|
||||
last.key, last.cursor = key, cursor
|
||||
|
||||
local visible = pmacs.completion.popup_visible()
|
||||
|
||||
if session and not session.pending and not visible then
|
||||
-- The core closed the popup since we opened it (accept, dismiss,
|
||||
-- validation, or a modal). Drop the mirror and do NOT reopen off
|
||||
-- this same edit --- this is what stops an accept's own
|
||||
-- after-edit from instantly re-raising the popup it just closed.
|
||||
session = nil
|
||||
return
|
||||
end
|
||||
|
||||
if visible and session then
|
||||
-- Refresh the open session from the text. A prefix that no longer
|
||||
-- reaches back to the anchor means the word died; close (the
|
||||
-- core's post-dispatch validation independently enforces the same
|
||||
-- invariant).
|
||||
if key ~= session.key then
|
||||
close_popup()
|
||||
return
|
||||
end
|
||||
local prefix = word_prefix_before(buf, cursor)
|
||||
if cursor < session.anchor or cursor - #prefix ~= session.anchor then
|
||||
close_popup()
|
||||
return
|
||||
end
|
||||
if not publish(buf, session.anchor, prefix, "incomplete", nil) then
|
||||
session = nil -- narrowed to nothing: the session is over
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Popup closed: the Q#C9 auto-open policy. Same buffer, cursor
|
||||
-- advanced by exactly one byte since the previous edit.
|
||||
if key ~= prev_key or not prev_cursor or cursor - prev_cursor ~= 1 then return end
|
||||
local prefix = word_prefix_before(buf, cursor)
|
||||
if #prefix >= MIN_PREFIX then
|
||||
if publish(buf, cursor - #prefix, prefix, "invoked", nil) then
|
||||
request_lsp_then_refresh()
|
||||
end
|
||||
return
|
||||
end
|
||||
if #prefix == 0 then
|
||||
-- Maybe a server trigger character (`.`, `:`, ...): a pending
|
||||
-- session anchored at the cursor, opening when candidates arrive.
|
||||
local ch = char_before(buf, cursor)
|
||||
local rec = pmacs.lsp.active_attachment()
|
||||
if not (ch and rec) then return end
|
||||
local ok, fires = pcall(pmacs.completion.should_fire, rec.server, ch)
|
||||
if not (ok and fires) then return end
|
||||
if not publish(buf, cursor, "", "char", ch) then
|
||||
session = { key = key, anchor = cursor, pending = true }
|
||||
end
|
||||
request_lsp_then_refresh()
|
||||
end
|
||||
end)
|
||||
|
||||
local function completion_at_point()
|
||||
local buf = pmacs.window.buffer()
|
||||
if not buf then return end
|
||||
local cursor = pmacs.editor.cursor()
|
||||
local prefix = word_prefix_before(buf, cursor)
|
||||
local anchor = cursor - #prefix
|
||||
if not publish(buf, anchor, prefix, "invoked", nil) then
|
||||
session = { key = tostring(buf), anchor = anchor, pending = true }
|
||||
end
|
||||
request_lsp_then_refresh()
|
||||
end
|
||||
|
||||
pmacs.command.define {
|
||||
name = "completion.at-point",
|
||||
description = "Open the in-buffer completion popup at the cursor.",
|
||||
fn = completion_at_point,
|
||||
}
|
||||
|
||||
pmacs.keymap.bind { scope = "global", sequence = "C-M-i", command = "completion.at-point" }
|
||||
|
|
@ -544,6 +544,19 @@ function pmacs.lsp.active_attachment()
|
|||
return attachments[tostring(buf)]
|
||||
end
|
||||
|
||||
-- Flushing variant for request-issuing callers outside this file
|
||||
-- (Q#C8): resolves (or attaches) the active buffer's server AND
|
||||
-- flushes any debounced didChange first, so the server answers the
|
||||
-- caller's request against the current text --- exactly what every
|
||||
-- interactive command in this file gets from the local
|
||||
-- `attached_for_active`. The in-buffer completion driver
|
||||
-- (builtin/runtime/completion.lua) calls this before
|
||||
-- textDocument/completion; a non-flushing peek would hand the server
|
||||
-- stale text after a typing burst.
|
||||
function pmacs.lsp.attachment_for_request()
|
||||
return attached_for_active()
|
||||
end
|
||||
|
||||
-- Hooks --------------------------------------------------------------------
|
||||
|
||||
pmacs.hook.add("buffer.after-load", function()
|
||||
|
|
|
|||
|
|
@ -119,6 +119,11 @@ pub struct CompletionContext {
|
|||
pub project_root: Option<PathBuf>,
|
||||
/// What kicked off the request.
|
||||
pub trigger: CompletionTrigger,
|
||||
/// Document URI of the buffer being completed (Q#C8 scoping).
|
||||
/// When set, URI-keyed providers (LSP) surface only this
|
||||
/// document's entries; when `None` they fall back to the legacy
|
||||
/// global drain across every cached key.
|
||||
pub uri: Option<String>,
|
||||
}
|
||||
|
||||
impl CompletionContext {
|
||||
|
|
@ -133,6 +138,7 @@ impl CompletionContext {
|
|||
language: None,
|
||||
project_root: None,
|
||||
trigger: CompletionTrigger::Invoked,
|
||||
uri: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -618,11 +624,15 @@ fn project_kind_to_completion_kind(k: &crate::project_index::SymbolKind) -> Comp
|
|||
/// the LSP completion store. The framework does **not** drive a
|
||||
/// fresh `textDocument/completion` request --- that's the editor's
|
||||
/// job; we just read whatever the async pipeline has produced so
|
||||
/// far, across every cached `(server_id, uri)` key. The registry's
|
||||
/// dedup collapses identical entries; the prefix score ranks them.
|
||||
/// far. With `ctx.uri` set (Q#C8 scoping, the popup driver's path)
|
||||
/// only that document's entries surface --- across all servers keyed
|
||||
/// to it --- so a popup never shows another buffer's candidates.
|
||||
/// Without a URI the legacy global drain across every cached
|
||||
/// `(server_id, uri)` key applies. The registry's dedup collapses
|
||||
/// identical entries; the prefix score ranks them.
|
||||
#[must_use]
|
||||
pub fn lsp_completion_provider(lsp: crate::lsp::SharedLspManager) -> ProviderFn {
|
||||
Box::new(move |_ctx: &CompletionContext| -> Vec<CompletionItem> {
|
||||
Box::new(move |ctx: &CompletionContext| -> Vec<CompletionItem> {
|
||||
let store_handle = {
|
||||
let mgr = lsp.borrow();
|
||||
mgr.completion_store()
|
||||
|
|
@ -631,7 +641,11 @@ pub fn lsp_completion_provider(lsp: crate::lsp::SharedLspManager) -> ProviderFn
|
|||
return Vec::new();
|
||||
};
|
||||
let mut out: Vec<CompletionItem> = Vec::new();
|
||||
let keys: Vec<_> = store.keys().cloned().collect();
|
||||
let keys: Vec<_> = store
|
||||
.keys()
|
||||
.filter(|k| ctx.uri.as_ref().is_none_or(|uri| k.uri == *uri))
|
||||
.cloned()
|
||||
.collect();
|
||||
for key in keys {
|
||||
for item in store.items(&key) {
|
||||
out.push(item.clone());
|
||||
|
|
|
|||
|
|
@ -256,6 +256,16 @@ impl EditorState {
|
|||
include_str!("../builtin/runtime/lsp.lua"),
|
||||
)
|
||||
.expect("load lsp builtin chunk");
|
||||
// Arc 1a: the in-buffer completion popup driver. Loaded after
|
||||
// lsp.lua because it drives `pmacs.lsp.request_completion` /
|
||||
// `pmacs.lsp.attachment_for_request` and after the framework
|
||||
// install above because it calls `pmacs.completion.collect`.
|
||||
lua_host
|
||||
.eval(
|
||||
Some("@pmacs/builtin/runtime/completion.lua"),
|
||||
include_str!("../builtin/runtime/completion.lua"),
|
||||
)
|
||||
.expect("load completion builtin chunk");
|
||||
// T M7.11 bundled-package bootstrap. Through M7.10 the REPL
|
||||
// was loaded directly via `eval(include_str!(...))`; the
|
||||
// M7.11 deliverable migrates it to the package system so it
|
||||
|
|
|
|||
|
|
@ -239,6 +239,7 @@ impl LuaHost {
|
|||
/// or loading the builtin chunks.
|
||||
pub fn attach_editor(&mut self, core: &SharedCore) -> mlua::Result<()> {
|
||||
lua_bindings::install_editor(&self.lua, core)?;
|
||||
lua_bindings::install_completion_popup(&self.lua, core)?;
|
||||
self.core = Some(core.clone());
|
||||
// Hooks first: command bodies in default.lua reference them.
|
||||
self.load_builtin(
|
||||
|
|
|
|||
|
|
@ -9638,7 +9638,7 @@ fn lua_table_to_completion_item(t: &Table) -> mlua::Result<crate::completion::Co
|
|||
}
|
||||
|
||||
fn ctx_to_lua(lua: &Lua, ctx: &CompletionContext) -> mlua::Result<Table> {
|
||||
let t = lua.create_table_with_capacity(0, 7)?;
|
||||
let t = lua.create_table_with_capacity(0, 8)?;
|
||||
t.set("prefix", ctx.prefix.as_str())?;
|
||||
t.set("line", ctx.line)?;
|
||||
t.set("col", ctx.col)?;
|
||||
|
|
@ -9649,6 +9649,9 @@ fn ctx_to_lua(lua: &Lua, ctx: &CompletionContext) -> mlua::Result<Table> {
|
|||
if let Some(p) = &ctx.project_root {
|
||||
t.set("project_root", p.display().to_string())?;
|
||||
}
|
||||
if let Some(u) = &ctx.uri {
|
||||
t.set("uri", u.as_str())?;
|
||||
}
|
||||
let (trigger_tag, trigger_char): (&'static str, Option<String>) = match ctx.trigger {
|
||||
CompletionTrigger::Invoked => ("invoked", None),
|
||||
CompletionTrigger::Char(c) => ("char", Some(c.to_string())),
|
||||
|
|
@ -9677,6 +9680,7 @@ fn lua_table_to_ctx(t: &Table) -> CompletionContext {
|
|||
Some("incomplete") => CompletionTrigger::Incomplete,
|
||||
_ => CompletionTrigger::Invoked,
|
||||
};
|
||||
let uri: Option<String> = t.get::<Option<String>>("uri").ok().flatten();
|
||||
CompletionContext {
|
||||
prefix,
|
||||
line,
|
||||
|
|
@ -9685,6 +9689,7 @@ fn lua_table_to_ctx(t: &Table) -> CompletionContext {
|
|||
language,
|
||||
project_root: project_root.map(std::path::PathBuf::from),
|
||||
trigger,
|
||||
uri,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -9949,11 +9954,22 @@ pub fn install_completion_framework(
|
|||
// ctx_to_lua exposed as `pmacs.completion.context_for(...)`
|
||||
// for callers that need to construct a context table from
|
||||
// primitives. Convenience only --- callers can build their
|
||||
// own.
|
||||
// own. Trailing optionals: a "char" trigger needs its
|
||||
// `trigger_char` (Q#C1 nit --- the helper previously could
|
||||
// not express `CompletionTrigger::Char` at all), and `uri`
|
||||
// scopes URI-keyed providers (Q#C8).
|
||||
m.set(
|
||||
"context_for",
|
||||
lua.create_function(move |lua, args: ContextForArgs| {
|
||||
let (prefix, line, col, buffer_text, language, project_root, trigger) = args;
|
||||
let (prefix, line, col, buffer_text, language, project_root, trigger, ch, uri) =
|
||||
args;
|
||||
let trigger = match trigger.as_deref() {
|
||||
Some("incomplete") => CompletionTrigger::Incomplete,
|
||||
Some("char") => ch
|
||||
.and_then(|s| s.chars().next())
|
||||
.map_or(CompletionTrigger::Invoked, CompletionTrigger::Char),
|
||||
_ => CompletionTrigger::Invoked,
|
||||
};
|
||||
let ctx = CompletionContext {
|
||||
prefix,
|
||||
line: line.unwrap_or(0),
|
||||
|
|
@ -9961,11 +9977,8 @@ pub fn install_completion_framework(
|
|||
buffer_text: Rc::from(buffer_text.unwrap_or_default()),
|
||||
language,
|
||||
project_root: project_root.map(std::path::PathBuf::from),
|
||||
trigger: if trigger.as_deref() == Some("incomplete") {
|
||||
CompletionTrigger::Incomplete
|
||||
} else {
|
||||
CompletionTrigger::Invoked
|
||||
},
|
||||
trigger,
|
||||
uri,
|
||||
};
|
||||
ctx_to_lua(lua, &ctx)
|
||||
})?,
|
||||
|
|
@ -10007,6 +10020,97 @@ pub fn make_completion_framework(
|
|||
Ok((registry, snippets))
|
||||
}
|
||||
|
||||
/// Install the in-buffer completion popup surface (Arc 1a, Q#C2) into
|
||||
/// `pmacs.completion`: `popup_show{...}` publishes a session into the
|
||||
/// core's shared popup (the Lua driver's write path), `popup_hide()`
|
||||
/// closes it, `popup_visible()` peeks. Separate from
|
||||
/// [`install_completion_framework`] because these need the
|
||||
/// [`SharedCore`], which only exists once the editor attaches.
|
||||
pub fn install_completion_popup(lua: &Lua, core: &SharedCore) -> mlua::Result<()> {
|
||||
let pmacs: Table = lua.globals().get("pmacs")?;
|
||||
let m: Table = match pmacs.get::<Option<Table>>("completion")? {
|
||||
Some(t) => t,
|
||||
None => lua.create_table()?,
|
||||
};
|
||||
|
||||
{
|
||||
// popup_show{ buffer, anchor, prefix?, total?, candidates = {
|
||||
// { label, kind?, detail?, insert_text? }, ... } } -> bool
|
||||
//
|
||||
// Returns false (popup left closed) for an empty candidate
|
||||
// list. `kind` uses the same string tags as
|
||||
// `pmacs.completion.collect` rows, so driver code can pass
|
||||
// collect() output straight through.
|
||||
let cc = core.clone();
|
||||
m.set(
|
||||
"popup_show",
|
||||
lua.create_function(move |_, spec: Table| {
|
||||
let buffer: BufferIdLua = spec.get("buffer")?;
|
||||
let anchor: u64 = spec.get("anchor")?;
|
||||
let prefix: String = spec
|
||||
.get::<Option<String>>("prefix")
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
let rows: Table = spec.get("candidates")?;
|
||||
let mut candidates = Vec::new();
|
||||
for row in rows.sequence_values::<Table>() {
|
||||
let row = row?;
|
||||
let label: String = row.get("label")?;
|
||||
let kind_tag: Option<String> = row.get::<Option<String>>("kind").ok().flatten();
|
||||
let kind = kind_tag.as_deref().map_or(
|
||||
crate::completion::CompletionItemKind::Text,
|
||||
completion_kind_from_tag,
|
||||
);
|
||||
let detail: Option<String> = row.get::<Option<String>>("detail").ok().flatten();
|
||||
let insert_text: Option<String> =
|
||||
row.get::<Option<String>>("insert_text").ok().flatten();
|
||||
candidates.push(crate::completion::PopupCandidate {
|
||||
insert_text: insert_text.unwrap_or_else(|| label.clone()),
|
||||
label,
|
||||
kind,
|
||||
detail,
|
||||
});
|
||||
}
|
||||
let total: usize = spec
|
||||
.get::<Option<usize>>("total")
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(candidates.len());
|
||||
let Some(state) = crate::completion::CompletionPopupState::new(
|
||||
buffer.0, anchor, prefix, candidates, total,
|
||||
) else {
|
||||
return Ok(false);
|
||||
};
|
||||
cc.borrow_mut().completion_popup_open(state);
|
||||
Ok(true)
|
||||
})?,
|
||||
)?;
|
||||
}
|
||||
|
||||
{
|
||||
let cc = core.clone();
|
||||
m.set(
|
||||
"popup_hide",
|
||||
lua.create_function(move |_, ()| {
|
||||
cc.borrow_mut().completion_popup_close();
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
}
|
||||
|
||||
{
|
||||
let cc = core.clone();
|
||||
m.set(
|
||||
"popup_visible",
|
||||
lua.create_function(move |_, ()| Ok(cc.borrow().completion_popup_is_open()))?,
|
||||
)?;
|
||||
}
|
||||
|
||||
pmacs.set("completion", m)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Argument tuple passed to a Lua-registered completion provider.
|
||||
/// Positional rather than table-based because the provider closure
|
||||
/// has no `&Lua` to build a table with at call time.
|
||||
|
|
@ -10019,6 +10123,7 @@ type LuaProviderArgs = (
|
|||
Option<String>,
|
||||
String,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
);
|
||||
|
||||
/// Argument tuple for `pmacs.completion.context_for`: positional
|
||||
|
|
@ -10031,6 +10136,8 @@ type ContextForArgs = (
|
|||
Option<String>,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
);
|
||||
|
||||
/// mlua doesn't accept arbitrary Rust types as call arguments
|
||||
|
|
@ -10053,6 +10160,9 @@ fn lua_compat_ctx_args(ctx: &CompletionContext) -> LuaProviderArgs {
|
|||
ctx.project_root.as_ref().map(|p| p.display().to_string()),
|
||||
trigger_tag.to_owned(),
|
||||
trigger_char,
|
||||
// Trailing addition (Q#C8): existing Lua providers that
|
||||
// ignore the ninth positional arg are unaffected.
|
||||
ctx.uri.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue