Flemma adapts to your colour scheme with theme-aware highlights, line backgrounds, rulers, turn indicators, and folding. Every visual element is configurable. Several UI components are the visible face of the harness: the approval prompt surfaces the gating decision, the rejection popup captures human-in-the-loop feedback that becomes the model's error string, and the jobs bar provides background-job lifecycle observability.
Tip
Switching colorschemes mid-session (:colorscheme gruvbox, etc.) automatically refreshes all Flemma highlight groups — no buffer switch or restart required.
For the full configuration block including all UI-related keys, see docs/configuration.md.
Configuration keys map to dedicated highlight groups:
| Key | Applies to |
|---|---|
highlights.system |
System messages (FlemmaSystem) |
highlights.user |
User messages (FlemmaUser) |
highlights.assistant |
Assistant messages (FlemmaAssistant) |
highlights.lua_expression |
{{ expression }} fragments (in @You and @System messages) |
highlights.lua_code_block |
{% code %} block content (in @You and @System messages) |
highlights.lua_delimiter |
{{ }} and {% %} delimiters including trim markers |
highlights.user_file_reference |
@./path fragments |
highlights.thinking_tag |
<thinking> / </thinking> tags |
highlights.thinking_block |
Content inside thinking blocks |
highlights.tool_icon |
⬡ / ⬢ icons in tool fold text (FlemmaToolIcon) |
highlights.tool_name |
Tool name in tool fold text (FlemmaToolName) |
highlights.tool_use_title |
**Tool Use:** title line (FlemmaToolUseTitle) |
highlights.tool_result_title |
**Tool Result:** title line (FlemmaToolResultTitle) |
highlights.tool_result_error |
(error) suffix on tool result headers (FlemmaToolResultError) |
highlights.tool_result_pending |
(pending) suffix on tool result headers (FlemmaToolResultPending) |
highlights.tool_result_approved |
(approved) suffix on tool result headers (FlemmaToolResultApproved) |
highlights.tool_result_rejected |
(rejected) suffix on tool result headers (FlemmaToolResultRejected) |
highlights.tool_result_denied |
(denied) suffix on tool result headers (FlemmaToolResultDenied) |
highlights.tool_result_aborted |
(aborted) suffix on tool result headers (FlemmaToolResultAborted) |
highlights.tool_preview |
Tool preview virtual lines in pending placeholders (FlemmaToolPreview) |
highlights.tool_label |
Tool intent label in fold previews, italic by default (FlemmaToolLabel) |
highlights.tool_detail |
Raw technical detail in structured tool previews (FlemmaToolDetail) |
highlights.fold_preview |
Content preview text in fold lines (FlemmaFoldPreview) |
highlights.fold_meta |
Line count and padding in fold lines (FlemmaFoldMeta) |
highlights.fence_label |
Language label on fenced code block overlays (FlemmaFenceLabel) |
highlights.fence_bar |
Delimiter bar on fenced code block overlays (FlemmaFenceBar) |
highlights.approval_indicator |
Status indicator on approval prompts (FlemmaApprovalIndicator) |
highlights.approval_label |
Tool name label on approval prompts (FlemmaApprovalLabel) |
highlights.approval_key |
Keybinding hints on approval prompts (FlemmaApprovalKey) |
highlights.approval_action |
Action text on approval prompts (FlemmaApprovalAction) |
highlights.rejection_input |
Rejection popup input area (FlemmaRejection) |
highlights.rejection_border |
Rejection popup border (FlemmaRejectionBorder) |
highlights.busy |
Busy indicator icon in integrations like bufferline (FlemmaBusy) |
highlights.progress_accent |
Bold accent for tool name in the progress bar (FlemmaProgressBarAccent) |
highlights.role_name |
GUI attributes for role name text, e.g., bold (FlemmaRole*Name) |
Each value is either a string shorthand or a highlight builder from require("flemma.hl"):
- String (group name) —
"Comment"is equivalent toh.link("Comment") - String (hex colour) —
"#ff0000"is equivalent toh.hex("#ff0000") - HlOp builder — for anything more complex, use the full API:
h.from(),h.attrs(),:blend(),:omit(),:pick(), etc.
The string shorthand means most overrides don't need require("flemma.hl") at all:
require("flemma").setup({
highlights = {
tool_name = "Keyword", -- link to an existing group
thinking_tag = "#6a6a6a", -- direct hex colour (applied as fg)
},
})**Job Result:** headers use their own syntax groups, each linked to the corresponding tool result group by default:
| Group | Links to |
|---|---|
FlemmaJobResultTitle |
FlemmaToolResultTitle |
FlemmaJobResultError |
FlemmaToolResultError |
FlemmaJobResultPending |
FlemmaToolResultPending |
FlemmaJobResultApproved |
FlemmaToolResultApproved |
FlemmaJobResultRejected |
FlemmaToolResultRejected |
FlemmaJobResultDenied |
FlemmaToolResultDenied |
FlemmaJobResultAborted |
FlemmaToolResultAborted |
Override any group to style job results independently from tool results.
Note
Fold text uses FlemmaJobResultTitle for the title, but indicators currently use the FlemmaTool* groups directly for both tool and job results.
Most theme-aware highlights use :tint() or :mute() — theme-aware blends that automatically flip direction based on vim.o.background:
:tint(attr, mod, ratio?)— offset away from the theme's background. Adds in dark mode (toward white), subtracts in light mode (toward black). Use for backgrounds that need to stand out from the base.:mute(attr, mod, ratio?)— offset toward the theme's background. Subtracts in dark mode (toward black), adds in light mode (toward white). Use for foregrounds that need to be more subdued.
Both accept a hex string or an HlOp as the mod colour source. When ratio is provided (0.0–1.0), the colour is scaled by that factor before blending — useful for borrowing a fraction of another group's colour.
local h = require("flemma.hl")
-- Mute Comment's fg (darker in dark mode, lighter in light mode)
ruler = { hl = h.from("Comment"):mute("fg", "#303030") }
-- Tint Normal's bg (lighter in dark mode, darker in light mode)
line_highlights = { user = h.from("Normal"):tint("bg", "#202122") }
-- Tint bg using 10% of DiagnosticWarn's fg colour
highlights = { assistant = h.from("Normal"):tint("bg", h.from("DiagnosticWarn"):pick("fg"), 0.10) }For cases requiring truly different ops per theme (not just flipped blend direction), h.themed() is available:
highlights = { example = h.themed({
dark = h.link("Special"),
light = h.from("Comment"):pick("fg"),
}) }Colours are derived from existing highlight groups using chainable operations. Operations can be combined freely — each returns a new builder, so chains never mutate the original.
Constructors — starting points for a highlight chain:
| Constructor | What it does |
|---|---|
h.link("Group") |
Link to an existing group (no copy, follows changes) |
h.from("Group") |
Resolve a group's concrete attributes as a starting point |
h.attrs({ ... }) |
Literal attributes ({ bold = true }, { fg = "#ff0000" }) |
h.hex("#aabbcc") |
Literal colour, defaults to fg; pass "bg" as second arg for background |
h.themed({ dark = ..., light = ... }) |
Branch on vim.o.background (see Theme-aware values) |
h.coalesce(a, b, c) |
Try each operation in order, use the first that resolves |
h.default(attr) |
The terminal default for an attribute — black or white based on vim.o.background |
h.none() |
A no-op: resolves to nothing and sets no group — leaves it unmanaged |
Chain operations — transform the result of the constructor or a previous chain step:
:tint(attr, mod, ratio?) / :mute(attr, mod, ratio?) — theme-aware blends described in Theme-aware values above.
:blend(attr, mod, ratio?) — add or subtract a colour with an explicit direction. When mod is a prefixed hex string, + brightens and - darkens. When mod is an HlOp and ratio is provided, the sign of the ratio controls direction and its magnitude scales the colour. Each RGB channel is clamped to 0-255. Prefer :tint() / :mute() when the only difference between dark and light mode is the blend direction:
h.from("Normal"):blend("bg", "+#101010")
h.from("Normal"):blend("bg", "+#101010"):blend("fg", "-#202020")
-- Blend 10% of DiagnosticWarn's fg into the bg (positive = add)
h.from("Normal"):blend("bg", h.from("DiagnosticWarn"):pick("fg"), 0.10):omit(attr, ...) — strip attributes from the result. Valid targets are colour attributes (fg, bg, sp) and style attributes (bold, italic, underline, undercurl, strikethrough, reverse, standout, etc.):
-- Keep fg and styles from Folded, but drop its bg
h.from("Folded"):omit("bg")
-- Drop both bg and italic
h.from("Folded"):omit("bg", "italic"):pick(attr, ...) — the inverse of :omit() — keep only the named attributes, discarding everything else:
-- Extract just the fg colour from Comment
h.from("Comment"):pick("fg"):style({ ... }) — merge in style attributes without changing colours:
h.from("Comment"):style({ italic = true, bold = true }):contrast(attr, against, ratio) — adjust an attribute's colour to ensure a minimum WCAG contrast ratio against a reference colour. attr is the colour to adjust ("fg" or "bg"), against is an HlOp providing the reference, and ratio is the minimum contrast ratio (e.g., 4.5 for WCAG AA). If the current colour already meets the ratio, it passes through unchanged:
-- Ensure approval label fg has at least 4.5:1 contrast against the line background
h.from("Folded"):pick("fg"):contrast("fg", h.from("FlemmaLineUser"):pick("bg"), 4.5):merge(other, strategy?) — combine the resolved attributes of two operations. other is an HlOp whose attributes are merged into the result. strategy is "keep" (default, parent wins on conflict) or "force" (other wins):
-- Take MsgArea's bg, force FloatBorder's fg on top
h.from("MsgArea"):pick("bg"):merge(h.from("FloatBorder"):pick("fg"), "force")Coalesce — h.coalesce() is particularly useful for fallback chains:
-- Try FlemmaLineUser first; if it has no bg, fall back to Normal
h.coalesce(
h.from("FlemmaLineUser"):tint("bg", h.from("DiagnosticInfo"):pick("fg"), 0.1),
h.from("Normal"):tint("bg", h.from("DiagnosticInfo"):pick("fg"), 0.1)
)When an operation resolves to nothing (e.g., a group doesn't exist, or :pick("sp") on a group with no sp), it returns nil and the coalesce moves to the next candidate. Missing colours fall back to your Normal group, or to black/white based on vim.o.background.
Full-line background colours distinguish message roles. Applied via line-level extmarks on every line of each message block. Disable with line_highlights.enabled = false (default: true):
local h = require("flemma.hl")
line_highlights = {
enabled = true,
frontmatter = h.from("Normal"):tint("bg", "#18111a"),
system = h.from("Normal"):tint("bg", "#101112"),
user = h.from("Normal"):tint("bg", "#202122"),
assistant = h.link("Normal"),
}Role markers (@You:, @System:, @Assistant:) must appear on their own line -- content starts on the next line. The highlights.role_name option (default: h.attrs({ bold = true })) applies styling to the role name text only (not the ruler).
Rulers are drawn directly on each role marker line (@System:, @You:, @Assistant:) using overlay extmarks. The ruler character replaces the @ symbol and extends across the remaining window width, producing a visual separator that doesn't consume extra vertical space. Rulers resize automatically when the window is resized.
local h = require("flemma.hl")
ruler = {
enabled = true, -- default: true
char = "─", -- drawn over the role marker and repeated to fill the line
hl = h.from("Comment"):mute("fg", "#303030"),
}With rulers enabled, a role marker line like @You: renders as ─ You ────────... spanning the full window width. The first message also gets a ruler when frontmatter is present.
Turn indicators use the statuscolumn to visually group contiguous request/response cycles — an @You message through its terminal @Assistant response, including any intermediate tool use. Box drawing characters run continuously down the left edge, covering wrapped lines and virtual lines.
turns = {
enabled = true,
padding = { left = 0, right = 1 }, -- also accepts a number (left only) or tuple {L, R}
hl = "FlemmaTurn", -- links to FlemmaRuler by default
}Three visual states indicate turn progress:
- Complete (
╭│╰) — the assistant responded with no pending tool calls. - Incomplete (
╭┊└) — mid-tool-use; tool results are pending before the turn can finish. - Streaming (
╭┊└moving) — the assistant is actively generating a response.
When padding.right > 0, the top arc (╭) connects to the ruler for a seamless visual join.
When a tool call requires manual approval, Flemma renders an inline widget at the end of the tool result's closing fence line. The cursor-focused tool shows a pause indicator (⏸), the tool label, and keybind hints; other pending tools instead show Awaiting approval… (N/M) when more than one tool is queued. Approved-but-not-yet-executing tools show a check icon (✓), and executing or already-settled tools show an hourglass (⧖) with just the tool label.
ui = {
approval = {
enabled = true, -- Show approval prompts (default: true)
syntax_highlighting = true, -- Treesitter-powered syntax highlighting in tool previews
preview_lines = { head = 6, tail = 6 },
},
}When syntax_highlighting is enabled (the default), tool preview content inside the approval widget uses treesitter highlighting for the detected language — JSON tool inputs get proper syntax colours instead of a flat FlemmaToolPreview tint.
When you reject a tool call, Flemma opens an inline floating input overlaid on the tool result fence. The input opens in insert mode pre-filled with User feedback: and supports multi-line editing with Vim motions; type your reason and press Enter to confirm, or Ctrl-C (or Esc/q in normal mode) to cancel. The reason you type becomes the User feedback: {{ reason }} line written into the tool result fence, and that text is delivered to the model verbatim as the tool_result error string it reads.
ui = {
rejection = {
enabled = true, -- Show inline rejection popup (default: true)
completion = false, -- Buffer-local completion in the popup (default: false)
winblend = 15, -- Window transparency (default: 15)
},
}While the popup is open, a (rejected) overlay replaces the (pending) suffix on the tool result header so you can see the state change in real time.
Two highlight groups control the popup appearance:
| Group | Default | Applies to |
|---|---|---|
FlemmaRejection |
MsgArea |
The popup input area |
FlemmaRejectionBorder |
FloatBorder fg on MsgArea bg |
The popup border |
Customise via highlights.rejection_input and highlights.rejection_border in your config. Set ui.rejection.enabled = false to fall back to vim.ui.input for rejection messages.
While a request is in flight, Flemma writes an @Assistant: marker on its own line and renders "Waiting…" as end-of-line virtual text with an animated braille spinner, followed by the elapsed time – e.g., ⠋ Waiting… · 3s. The animation uses phase-specific frame sequences so the visual rhythm reflects what is happening.
The spinner transitions between phases automatically and is removed once streaming starts.
Once content begins arriving, the label is replaced with a live character count alongside the elapsed time – e.g., 3.2K characters · 12s – so you can gauge progress at a glance.
While a response is streaming, Flemma automatically scrolls the viewport to keep new content visible — the buffer follows the growing response to the bottom. If you scroll away (e.g., to review an earlier message), auto-scroll disengages and the viewport stays where you put it. Auto-scroll re-engages when the next request starts, and also re-engages mid-stream if you move the cursor back to the last line.
When async tool sources (registered via tools.modules or tools.register() with a resolve function) are still loading, Flemma shows a booting state. The booting statusline variable is true during this phase and becomes false once all sources resolve. A FlemmaBootComplete User autocmd fires when booting finishes. If you send a request while booting, the buffer shows "Waiting for tool definitions to load…" and auto-sends once everything resolves.
During tool execution, an animated braille spinner appears next to the **Tool Result:** block using the tool phase frames (a falling sand animation at 200ms intervals). When execution completes, the indicator changes to ✔ Complete or ⚠ Failed. Indicators reposition automatically if the buffer is modified during execution and clear on the next buffer edit.
Eleven highlight groups control indicator colours — seven for the inline ⬢ icon and four for the EOL status text:
| Group | Default link | When it's used |
|---|---|---|
FlemmaToolIconPending |
DiagnosticInfo |
Inline ⬢ on pending tools |
FlemmaToolIconExecuting |
FlemmaToolResultTitle |
(not shown — no prefix when executing) |
FlemmaToolIconSuccess |
FlemmaToolResultTitle |
Inline ⬢ + fold icon on success |
FlemmaToolIconError |
DiagnosticError |
Inline ⬢ + fold icon on error |
FlemmaToolIconRejected |
FlemmaToolResultRejected |
Inline ⬢ on rejected tools |
FlemmaToolIconDenied |
FlemmaToolResultDenied |
Inline ⬢ on denied tools |
FlemmaToolIconAborted |
FlemmaToolResultAborted |
Inline ⬢ on aborted tools |
FlemmaToolPending |
DiagnosticHint |
EOL ⏸ Pending text |
FlemmaToolExecuting |
DiagnosticInfo |
EOL spinner + Executing… text |
FlemmaToolSuccess |
DiagnosticOk |
EOL ✔ Complete text |
FlemmaToolError |
DiagnosticError |
EOL ⚠ Failed text |
By default, the executing and success icons match the Tool Result: header, while the pending, error, rejected, denied, and aborted icons carry their own semantic colours; status text uses semantic Diagnostic colours throughout. Override any group to customise:
-- Make icons match status colours instead of the header
vim.api.nvim_set_hl(0, "FlemmaToolIconSuccess", { link = "DiagnosticOk" })
vim.api.nvim_set_hl(0, "FlemmaToolIconError", { link = "DiagnosticError" })When tool calls are pending approval, Flemma renders a virtual line inside each empty tool_result placeholder fence showing a compact summary of what the tool will do. This lets you review and approve tools without scrolling back to the **Tool Use:** block.
Previews dynamically size to the editor's text area width (window width minus sign, number, and fold columns) and truncate with … when the content exceeds available space. Built-in tools return structured previews with a detail (the raw command or path) and a label (the LLM's stated intent), separated by an em-dash and rendered detail-first: bash: $ make test — running tests. When width is limited, detail truncates first to preserve the label. The italic-label/dim-detail split is only visible in folded message previews, where each chunk gets its own highlight; the virt-line preview shown over pending placeholders uses a single combined FlemmaToolPreview highlight. Custom tools can provide their own previews via format_preview on the tool definition. Tools without a custom formatter get a YAML-style generic preview — scalar values as key: value pairs, compound values JSON-encoded — with treesitter syntax highlighting when ui.approval.syntax_highlighting is enabled.
Preview lines use the FlemmaToolPreview highlight group (default: linked to Comment). See docs/tools.md for the full reference on built-in formatters, the generic fallback, and writing custom preview functions.
Flemma uses a two-level fold hierarchy:
| Fold level | What folds | Why |
|---|---|---|
| Level 1 | Each message | Collapse long exchanges without losing context. |
| Level 2 | Thinking blocks, frontmatter | Keep reasoning traces and templates out of the way. |
The initial fold level is controlled by editing.fold.level (default: 1, which collapses thinking blocks and frontmatter but keeps messages open). Set to 0 to collapse everything, or 99 to open everything.
When editing.fold.gap is true, folded messages leave one trailing blank line visible between them for visual separation. This only applies to message-level folds — tool block folds always collapse fully. Disabled by default.
A turn is one round-trip in the conversation: an @You message and every @Assistant reply and tool exchange that answers it, up to the next @You. Two keybindings collapse turns to their endpoints so you can scan a long conversation as a clean question/answer dialogue:
| Key | Action | Config |
|---|---|---|
zy |
Fold every message in the turn under the cursor except the first and last | keymaps.normal.fold_turn |
zY |
Apply the same fold to every turn in the buffer | keymaps.normal.fold_turns |
The first and last messages stay open so the question and final answer remain visible — intermediate assistant responses, tool calls, and tool results collapse. A turn with only a question and a single response folds nothing (there's nothing intermediate to hide).
Collapsed folds show a preview of their content with per-segment syntax highlighting. Neovim's foldtext returns {text, hl_group} tuples so each part of the fold line uses its own highlight group. The format varies by content type:
- Messages:
─ Role preview... (N lines)when rulers are enabled (default), or@Role: preview... (N lines)otherwise – role name usesFlemmaRole{Role}Name, preview usesFlemmaFoldPreview, line count usesFlemmaFoldMeta, ruler char usesFlemmaRuler. - Tool Use:
⬡ Tool Use: name: detail — label (N lines)– icon (hollow hexagon) usesFlemmaToolIcon, title usesFlemmaToolUseTitle, name usesFlemmaToolName, detail usesFlemmaToolDetail, label usesFlemmaToolLabel(italic), meta usesFlemmaFoldMeta. When the tool'sformat_previewreturns a structured{ label, detail }, detail shows the raw technical summary and label shows the LLM's stated intent. When only detail is available, it falls back to the previous format. - Tool Result:
⬢ Tool Result: name: detail — label (N lines)– same structure as tool use but with a filled hexagon icon andFlemmaToolResultTitle. Errors show(error)withFlemmaToolResultError. - Thinking blocks:
<thinking preview...> (N lines)– shows<thinking redacted>for redacted blocks, or<thinking provider>for blocks with a provider signature. UsesFlemmaThinkingTagfor delimiters andFlemmaThinkingFoldPreviewfor content (fg-only, so the background comes from the line highlight extmark and correctly blends with CursorLine). - Frontmatter:
```language preview... ``` (N lines)– usesFlemmaFoldMetafor fences andFlemmaFoldPreviewfor content.
When experimental.patch_markdown_conceal is enabled (the default), fenced code block delimiters are replaced with styled overlay extmarks instead of being hidden via treesitter conceal_lines. This eliminates significant per-keystroke overhead on large buffers while keeping the visual appearance clean.
Two highlight groups control the overlay appearance:
| Group | Default | Applies to |
|---|---|---|
FlemmaFenceLabel |
theme-aware Comment |
Language tag on the opening delimiter (e.g., lua) |
FlemmaFenceBar |
links to FlemmaFenceLabel |
Delimiter bar character |
When the cursor line overlaps with a fence overlay, the highlights are automatically contrast-adjusted against CursorLine to ensure readability. This uses pre-computed highlight variants that blend the fence colours with the CursorLine background.
Fence overlays are only shown when conceallevel >= 2. Toggling conceal off ([oe or yoe) reveals the raw ``` delimiters. See docs/conceal.md for the full interaction between conceallevel and fence rendering.
Completed requests show a single-line usage bar anchored to one of the chat window's edges. The bar displays model, provider, token counts, cost, and cache statistics – all rendered using priority-based truncation so content degrades gracefully in narrow terminals. Higher-priority items (model name, cost) survive; lower-priority items (individual token breakdowns) are dropped first.
ui = {
usage = {
enabled = true, -- set to false to suppress the usage bar
timeout = 10000, -- milliseconds before auto-dismiss (0 = persistent)
position = "top", -- one of: top, bottom, top left, top right,
-- bottom left, bottom right
highlight = "@text.note,PmenuSel", -- highlight group(s) for bar colours; first with both fg+bg wins
},
}The highlight option accepts a comma-separated list of highlight group names. Flemma tries each in order and uses the first one that provides both fg and bg attributes. This lets you specify preferred groups with fallbacks for colorschemes that may not define them all.
The usage bar derives all colours from the resolved highlight group using three foreground tiers against a shared background:
| Tier | Group | Used by |
|---|---|---|
| Primary | FlemmaUsageBar |
Model name, cost |
| Secondary | FlemmaUsageBarSecondary |
Token counts, cache label, request count |
| Muted | FlemmaUsageBarMuted |
Provider, separators, session label |
Cache hit percentage uses semantic colours through FlemmaUsageBarCacheGood (links to DiagnosticOk) and FlemmaUsageBarCacheBad (links to DiagnosticWarn) with automatic WCAG contrast enforcement against the bar background.
Each .chat buffer owns at most one active usage bar — a new request dismisses the previous bar before rendering the next. Bars re-render automatically on window resize to reflow content for the new width. Recall the most recent usage bar with :Flemma usage:recall.
Before sending, preview the cost of the next request with :Flemma usage:estimate. The command delegates to the active provider — Anthropic (POST /v1/messages/count_tokens), OpenAI (POST /v1/responses/input_tokens), Google Vertex AI ({model}:countTokens), and Moonshot (POST /v1/tokenizers/estimate-token-count) implement it today. Each sends the exact body a real request would produce, with provider-specific counting-only fields removed, and reports input tokens, estimated cost, and the model's per-MTok rates as a single notify.info line. Output cost is intentionally not projected: we have no way to know how long the model will talk before it starts.
For OpenAI, neither the public token-counting/API docs nor live curl probes of successful responses exposed billing, quota, or rate-limit metadata for the token-count endpoint. Flemma therefore treats estimates conservatively as real API requests that may count against account limits. The default statusline includes these debounced estimates; custom statusline formats only get them if they reference buffer.tokens.input. Estimates are deduped and suppressed while a chat request is already in flight.
Flemma uses a priority hierarchy to layer visual elements correctly when they overlap. Higher-priority extmarks take precedence:
| Priority | Element | Notes |
|---|---|---|
| 50 | Line highlights | Base backgrounds for messages and frontmatter |
| 100 | Thinking blocks | Overrides message line highlights |
| 125 | CursorLine | Blended overlay so CursorLine shows through |
| 200 | Thinking tags | <thinking> / </thinking> styling |
| 250 | Tool indicators | Execution spinners and status |
| 300 | Spinner | Highest priority; suppresses spell checking |
These priorities are not user-configurable, but understanding them explains why certain elements visually override others. The base layers (50, 100, 125, 200) are defined in lua/flemma/ui/init.lua; the tool indicator priority (250) lives in lua/flemma/ui/indicators.lua and the spinner priority (300) in lua/flemma/ui/activity.lua. Tool preview virtual lines use virt_lines extmarks (not line-level highlights), so they don't participate in this priority hierarchy.
While a request is streaming, Flemma shows a persistent progress indicator as a floating bar anchored to one of the chat window's edges. The bar displays the current phase (thinking, streaming text, receiving tool input) and re-renders automatically on window resize.
ui = {
progress = {
position = "bottom left", -- one of: top, bottom, top left, top right,
-- bottom left, bottom right
highlight = "StatusLine", -- highlight group(s); first with both fg+bg is used
},
}When background jobs are running, Flemma shows a floating bar anchored to one of the chat window's edges (default: bottom right). The bar displays the active job count with an animated spinner and disappears when all jobs complete.
ui = {
jobs = {
position = "bottom right", -- one of: top, bottom, top left, top right,
-- bottom left, bottom right
},
}When autopilot schedules a debounced auto-continue after a background job completes, the bar shows a countdown animation alongside the job count. The countdown reflects the tools.autopilot.resume_delay timer. Press Ctrl-C during the countdown to cancel the auto-continue — the countdown disappears from the bar but the job count remains while jobs are still running.
When lsp.enabled is set (the default whenever vim.lsp is available), hovering over any element in a .chat buffer shows a compact AST node dump in a fenced flemma-ast code block. The dump shows the node's kind, position, and key fields at depth 1 — container nodes (messages, documents) show a child summary instead of recursing.
Press gd (or use your LSP go-to-definition keymap) on:
- A
**Tool Use:**or**Tool Result:**placeholder → jumps to its sibling (the matching result or use block). - A
tool_resultcarrying ajob=modeline → jumps to the matching**Job Result:**block. - A
**Job Result:**header → jumps back to the originatingtool_resultplaceholder. - An
include("...")expression → opens the included file. - A
@./pathfile reference → opens the referenced file.
:Flemma ast:diff opens a side-by-side diff comparing the raw AST (before preprocessor rewriters) with the rewritten AST (after rewriters). Both buffers use the flemma-ast filetype with syntax highlighting and fold support. The diff view scrolls to the node under the cursor in the source buffer.
Use this to debug rewriter transformations — for example, to see how @./file references get rewritten to {{ include() }} expressions, or to verify that a custom rewriter is producing the expected AST changes. Fold regions let you collapse nodes to focus on the parts you care about.
Flemma ships optional integrations for lualine (statusline component) and bufferline (busy tab indicator). See docs/integrations.md for setup instructions and configuration.