vim_status
Retrieve detailed Neovim status information such as cursor position, editing mode, marks, and registers for enhanced workflow management in the mcp-neovim-server.
Instructions
Get comprehensive Neovim status including cursor position, mode, marks, and registers
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/index.ts:136-158 (registration)Registers the 'vim_status' MCP tool with no input parameters. The handler fetches Neovim status via neovimManager and returns it as JSON.server.tool( "vim_status", "Get comprehensive Neovim status including cursor position, mode, marks, and registers", {}, async () => { try { const status = await neovimManager.getNeovimStatus(); return { content: [{ type: "text", text: JSON.stringify(status, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: error instanceof Error ? error.message : 'Error getting Neovim status' }] }; } } );
- src/neovim.ts:328-438 (handler)Main handler logic in NeovimManager.getNeovimStatus(): Connects to Neovim, retrieves cursor position, mode, visual info, filename, marks, registers, cwd, LSP/plugin info, and constructs status object.public async getNeovimStatus(): Promise<NeovimStatus | string> { try { const nvim = await this.connect(); const window = await nvim.window; const cursor = await window.cursor; const mode = await nvim.mode; const buffer = await nvim.buffer; // Get window layout const layout = await nvim.eval('winlayout()'); const tabpage = await nvim.tabpage; const currentTab = await tabpage.number; // Get marks (a-z) - only include set marks const marks: { [key: string]: [number, number] } = {}; for (const mark of 'abcdefghijklmnopqrstuvwxyz') { try { const pos = await nvim.eval(`getpos("'${mark}")`) as [number, number, number, number]; // Only include marks that are actually set (not at position 0,0) if (pos[1] > 0 && pos[2] > 0) { marks[mark] = [pos[1], pos[2]]; } } catch (e) { // Mark not set } } // Get registers (a-z, ", 0-9) - only include non-empty registers const registers: { [key: string]: string } = {}; const registerNames = [...'abcdefghijklmnopqrstuvwxyz', '"', ...Array(10).keys()]; for (const reg of registerNames) { try { const content = String(await nvim.eval(`getreg('${reg}')`)); // Only include registers that have content if (content && content.trim().length > 0) { registers[String(reg)] = content; } } catch (e) { // Register empty or error } } // Get current working directory const cwd = await nvim.call('getcwd'); // Get basic plugin information (LSP clients, loaded plugins) let lspInfo = ''; let pluginInfo = ''; try { // Get LSP clients if available (use new API for Neovim >=0.10) const lspClients = await nvim.eval('luaeval("vim.lsp.get_clients()")'); if (Array.isArray(lspClients) && lspClients.length > 0) { const clientNames = lspClients.map((client: any) => client.name || 'unknown').join(', '); lspInfo = `Active LSP clients: ${clientNames}`; } else { lspInfo = 'No active LSP clients'; } } catch (e) { lspInfo = 'LSP information unavailable'; } try { // Get loaded plugins (simplified check) const hasLsp = await nvim.eval('exists(":LspInfo")'); const hasTelescope = await nvim.eval('exists(":Telescope")'); const hasTreesitter = await nvim.eval('exists("g:loaded_nvim_treesitter")'); const hasCompletion = await nvim.eval('exists("g:loaded_completion")'); const plugins = []; if (hasLsp) plugins.push('LSP'); if (hasTelescope) plugins.push('Telescope'); if (hasTreesitter) plugins.push('TreeSitter'); if (hasCompletion) plugins.push('Completion'); pluginInfo = plugins.length > 0 ? `Detected plugins: ${plugins.join(', ')}` : 'No common plugins detected'; } catch (e) { pluginInfo = 'Plugin information unavailable'; } // Get visual selection information using the new method const visualInfo = await this.getVisualSelectionInfo(nvim, mode.mode); const neovimStatus: NeovimStatus = { cursorPosition: cursor, mode: mode.mode, visualSelection: visualInfo.selectedText || '', fileName: await buffer.name, windowLayout: JSON.stringify(layout), currentTab, marks, registers, cwd, lspInfo, pluginInfo, visualInfo: { hasActiveSelection: visualInfo.hasSelection, visualModeType: visualInfo.visualModeType, startPos: visualInfo.startPos, endPos: visualInfo.endPos, lastVisualStart: visualInfo.lastVisualStart, lastVisualEnd: visualInfo.lastVisualEnd } }; return neovimStatus; } catch (error) { console.error('Error getting Neovim status:', error); return 'Error getting Neovim status'; } }
- src/neovim.ts:25-45 (schema)TypeScript interface defining the structure of NeovimStatus returned by the tool.interface NeovimStatus { cursorPosition: [number, number]; mode: string; visualSelection: string; fileName: string; windowLayout: string; currentTab: number; marks: { [key: string]: [number, number] }; registers: { [key: string]: string }; cwd: string; lspInfo?: string; pluginInfo?: string; visualInfo?: { hasActiveSelection: boolean; visualModeType?: string; startPos?: [number, number]; endPos?: [number, number]; lastVisualStart?: [number, number]; lastVisualEnd?: [number, number]; }; }
- src/neovim.ts:211-326 (helper)Helper method to retrieve detailed visual selection information, including active selections and last visual marks, used in getNeovimStatus.private async getVisualSelectionInfo(nvim: Neovim, mode: string): Promise<{ hasSelection: boolean; selectedText?: string; startPos?: [number, number]; endPos?: [number, number]; visualModeType?: string; lastVisualStart?: [number, number]; lastVisualEnd?: [number, number]; }> { try { const isInVisualMode = mode.includes('v') || mode.includes('V') || mode.includes('\x16'); if (isInVisualMode) { // Currently in visual mode - get active selection const [startPos, endPos, initialVisualModeType] = await Promise.all([ nvim.call('getpos', ['v']) as Promise<[number, number, number, number]>, nvim.call('getpos', ['.']) as Promise<[number, number, number, number]>, nvim.call('visualmode', []) as Promise<string> ]); // Convert positions to [line, col] format const start: [number, number] = [startPos[1], startPos[2]]; const end: [number, number] = [endPos[1], endPos[2]]; // Get the selected text using a more reliable approach let selectedText = ''; let visualModeType = initialVisualModeType; try { const result = await nvim.lua(` -- Get visual mode type first local mode = vim.fn.visualmode() if not mode or mode == '' then return { text = '', mode = '' } end local start_pos = vim.fn.getpos('v') local end_pos = vim.fn.getpos('.') local start_line, start_col = start_pos[2], start_pos[3] local end_line, end_col = end_pos[2], end_pos[3] -- Ensure proper ordering (start should be before end) if start_line > end_line or (start_line == end_line and start_col > end_col) then start_line, end_line = end_line, start_line start_col, end_col = end_col, start_col end local text = '' if mode == 'v' then -- Character-wise visual mode if start_line == end_line then local line = vim.api.nvim_buf_get_lines(0, start_line - 1, start_line, false)[1] or '' text = line:sub(start_col, end_col) else local lines = vim.api.nvim_buf_get_lines(0, start_line - 1, end_line, false) if #lines > 0 then -- Handle first line lines[1] = lines[1]:sub(start_col) -- Handle last line if #lines > 1 then lines[#lines] = lines[#lines]:sub(1, end_col) end text = table.concat(lines, '\\n') end end elseif mode == 'V' then -- Line-wise visual mode local lines = vim.api.nvim_buf_get_lines(0, start_line - 1, end_line, false) text = table.concat(lines, '\\n') elseif mode == '\\022' then -- Block-wise visual mode (Ctrl-V) local lines = vim.api.nvim_buf_get_lines(0, start_line - 1, end_line, false) local result = {} for _, line in ipairs(lines) do table.insert(result, line:sub(start_col, end_col)) end text = table.concat(result, '\\n') end return { text = text, mode = mode } `) as { text: string; mode: string }; selectedText = result.text || ''; visualModeType = result.mode || visualModeType; } catch (e) { selectedText = '[Selection text unavailable]'; } return { hasSelection: true, selectedText, startPos: start, endPos: end, visualModeType }; } else { // Not in visual mode - get last visual selection marks try { const [lastStart, lastEnd] = await Promise.all([ nvim.call('getpos', ["'<"]) as Promise<[number, number, number, number]>, nvim.call('getpos', ["'>"]) as Promise<[number, number, number, number]> ]); return { hasSelection: false, lastVisualStart: [lastStart[1], lastStart[2]], lastVisualEnd: [lastEnd[1], lastEnd[2]] }; } catch (e) { return { hasSelection: false }; } } } catch (error) { return { hasSelection: false, selectedText: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }; } }