Skip to main content
Glama

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
NameRequiredDescriptionDefault

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'
            }]
          };
        }
      }
    );
  • 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';
      }
    }
  • 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];
      };
    }
  • 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'}` };
      }
    }
Install Server

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bigcodegen/mcp-neovim-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server