GitHub Copilot-style inline suggestions that appear as gray text.
Virtual text shows AI suggestions inline as you type, in a non-intrusive way (gray comment color).
def hello_world_▌
↑
Your code here (normal)
def hello_world__test()__ ← Virtual text (gray, not real)
^^^^
The gray text is not inserted until you press a key to accept it.
- You type 3+ characters in a supported buffer
- You stop typing and wait 800ms (idle)
- Plugin requests completion from Mistral API
- API responds with suggestion
- Suggestion appears as gray text at end of line
<M-l>- Insert the suggestion (moves cursor to end)<C-Right>- Insert only next word<C-Down>- Insert only current line<C-c>- Clear suggestion (dismiss)- Move cursor >5 characters - Automatically clears
- Exit insert mode - Automatically clears
virtual_text = {
enabled = true -- Default: enabled
}virtual_text = {
manual = false -- Default: automatic (shows while you type)
manual = true -- Manual only: use :MistralCodestralVirtualComplete
}virtual_text = {
idle_delay = 800 -- Wait 800ms after last keystroke
}
-- Lower = faster but more intrusive
-- Higher = less intrusive but slowervirtual_text = {
min_chars = 3 -- Need 3+ chars in current word
}
-- The requirement applies to the word before cursor
-- Example: "return a " = 1 char ("a"), won't trigger
-- "return ab" = 2 chars ("ab"), won't trigger
-- "return abc" = 3 chars ("abc"), will triggerCustomize the keys to accept/dismiss suggestions:
virtual_text = {
key_bindings = {
accept = "<M-l>", -- Accept full text
accept_word = "<C-Right>", -- Accept next word
accept_line = "<C-Down>", -- Accept one line
next = "<M-]>", -- Next variant (future)
prev = "<M-[>", -- Prev variant (future)
clear = "<C-c>", -- Clear suggestion
}
}Set to false or empty string to disable a binding:
virtual_text = {
key_bindings = {
accept = "<M-l>",
accept_word = false, -- Disable accept_word
clear = "<C-c>",
}
}Show completion status in your status line.
-- Get status string like "1/1" or " * " (waiting)
local status_str = require("mistral-codestral.virtual_text").status_string()
-- Use in your status line config
-- Example for lualine:
{
function()
return require("mistral-codestral.virtual_text").status_string()
end,
color = { fg = "#999999" }
}Status meanings:
(spaces) - No active completion*- Waiting for API response1/1- Showing completion 1 of 12/3- Showing completion 2 of 3
Virtual text triggers in these conditions:
✅ Will show:
- In supported file types (lua, python, javascript, etc.)
- After typing 3+ characters (configurable)
- After 800ms idle (configurable)
- When not in excluded buffers
- When API key is valid
❌ Won't show:
- In help buffers
- In plugin windows (neo-tree, lazy, telescope, etc.)
- In terminal/quickfix/prompt buffers
- When typing less than min_chars
- When buffer is globally excluded
function sum(a, b) {
return▌
↑ cursorType "a +" and wait:
function sum(a, b) {
return a +__b__
^^^^^^^^ gray virtual text (not inserted yet)Press <M-l>:
function sum(a, b) {
return a + b▌
↑ cursor moved herenumbers = [1, 2, 3]
doubled = [▌
↑ cursorType "x * 2" and wait:
numbers = [1, 2, 3]
doubled = [x * 2__ for x in numbers]__
^^^^^^^^^^^^^^^^^^^^ gray virtual textPress <C-Down> (accept line) to insert only first line:
numbers = [1, 2, 3]
doubled = [x * 2▌
↑ cursor| Key | Action | Use Case |
|---|---|---|
<M-l> |
Accept full suggestion | Accept all lines |
<C-Right> |
Accept next word | Accept one word |
<C-Down> |
Accept current line | Accept one line |
| Key | Action |
|---|---|
<C-c> |
Clear (dismiss) |
| Move cursor | Clears automatically if >5 chars away |
| Exit insert | Clears automatically |
The plugin avoids showing duplicate text.
Scenario:
You type: "return hello_"
API suggests: "_world()"
Plugin shows: "world()" ← removes duplicate "_"
(not "__world()")
Screen: "return hello_world()" ← looks natural
^^^^^^^^ gray virtual text
This matching prevents the confusing double characters that might occur.
Check these in order:
-
Is it enabled?
virtual_text = { enabled = true }
-
Did you type 3+ characters?
- Type at least 3 characters in current word
- "ab" won't work, "abc" will
-
Did you wait 800ms?
- Stop typing and count to 1
- Virtual text should appear
-
Is buffer excluded?
:lua print(require("mistral-codestral").is_buffer_excluded())- Should print
false - If
true, buffer is excluded
- Should print
-
Is API key valid?
:MistralCodestralAuth status
-
Check debug output:
-- Enable debug logging debug = true -- Check :messages for logs :messages
If pressing <M-l> doesn't work:
-
Check key isn't bound to something else
:verbose imap <M-l>
-
Check it's the right mode (insert mode only)
- Use
ito enter insert mode first - Virtual text only works in insert
- Use
-
Try with explicit accept command:
:MistralCodestralVirtualComplete
If suggestion clears before you can accept:
-
Increase
idle_delay:virtual_text = { idle_delay = 1500 } -- 1.5 seconds instead of 0.8
-
Don't move cursor - even 5 chars movement clears it
- Use
<C-Right>or<C-Down>to move cursor within suggestion
- Use
Example showing world_world instead of just world:
This shouldn't happen - prefix matching removes duplicates. If you see this:
- File a bug report
- As workaround, disable virtual text temporarily:
:MistralCodestralToggle
Reduce delay for quicker suggestions:
virtual_text = {
idle_delay = 200, -- 200ms instead of 800ms
min_chars = 1, -- Show for single chars
}Trade-off: More intrusive (suggestions pop up while you're still typing)
Increase delay to be less disruptive:
virtual_text = {
idle_delay = 1500, -- 1.5 seconds
min_chars = 5, -- Only for longer words
}Trade-off: Slower feedback
Large files with slow LSP can cause lag. Exclude them:
exclusions = {
buffer_patterns = {
"^huge_legacy_file", -- Don't trigger in this file
}
}If you prefer to request completions manually:
virtual_text = {
enabled = true,
manual = true, -- Don't auto-trigger
}Then use command to manually trigger:
:MistralCodestralVirtualComplete " Request suggestion
:MistralCodestralVirtualClear " Clear suggestionOr create keybindings:
vim.keymap.set("i", "<M-Enter>", function()
require("mistral-codestral.virtual_text").complete()
end)
vim.keymap.set("i", "<M-Escape>", function()
require("mistral-codestral.virtual_text").clear_virtual_text()
end)Virtual text uses the Comment highlight group (gray by default).
Customize the color:
-- Neovim
vim.cmd("highlight MistralVirtualText ctermfg=8 guifg=#808080")
-- Or in your colorscheme:
vim.api.nvim_set_hl(0, "Comment", { fg = "#808080", italic = true })| Feature | Mistral | Copilot |
|---|---|---|
| Inline display | ✓ | ✓ |
| Gray color | ✓ | ✓ |
| Accept/dismiss | ✓ | ✓ |
| Cycle variants | Planned | ✓ |
| Tab to accept | Configurable | Yes |
| Feature | Virtual Text | Menu |
|---|---|---|
| Always visible | No, waits for idle | Yes, on demand |
| Clean UI | ✓ | ✓ |
| Multiple options | Planned | ✓ |
| LSP priority | No | Yes |
See CONFIGURATION.md for all config options. See ARCHITECTURE.md for technical details.