diff --git a/package-lock.json b/package-lock.json index ebcfe39..beb08ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,24 +1,24 @@ { "name": "mcp-neovim-server", - "version": "0.5.2", + "version": "0.5.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mcp-neovim-server", - "version": "0.5.2", + "version": "0.5.3", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.11.0", + "@modelcontextprotocol/sdk": "^1.13.2", "neovim": "^5.3.0", "ts-node": "^10.9.2", - "zod": "^3.25.64" + "zod": "^3.25.67" }, "bin": { "mcp-neovim-server": "build/index.js" }, "devDependencies": { - "@types/node": "^22.15.3", + "@types/node": "^24.0.6", "typescript": "^5.8.3" } }, @@ -80,9 +80,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.3.tgz", - "integrity": "sha512-DyVYSOafBvk3/j1Oka4z5BWT8o4AFmoNyZY9pALOm7Lh3GZglR71Co4r4dEUoqDWdDazIZQHBe7J2Nwkg6gHgQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.13.2.tgz", + "integrity": "sha512-Vx7qOcmoKkR3qhaQ9qf3GxiVKCEu+zfJddHv6x3dY/9P6+uIwJnmuAur5aB+4FDXf41rRrDnOEGkviX5oYZ67w==", "license": "MIT", "dependencies": { "ajv": "^6.12.6", @@ -135,12 +135,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.15.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.31.tgz", - "integrity": "sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==", + "version": "24.0.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.6.tgz", + "integrity": "sha512-ZOyn+gOs749xU7ovp+Ibj0g1o3dFRqsfPnT22C2t5JzcRvgsEDpGawPbCISGKLudJk9Y0wiu9sYd6kUh0pc9TA==", "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.8.0" } }, "node_modules/@types/triple-beam": { @@ -517,12 +517,12 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", - "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.3.tgz", + "integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==", "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/express": { @@ -568,9 +568,9 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", - "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", "license": "MIT", "engines": { "node": ">= 16" @@ -579,7 +579,7 @@ "url": "https://github.com/sponsors/express-rate-limit" }, "peerDependencies": { - "express": "^4.11 || 5 || ^5.0.0-beta.1" + "express": ">= 4.11" } }, "node_modules/fast-deep-equal": { @@ -1402,9 +1402,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "license": "MIT" }, "node_modules/unpipe": { @@ -1513,18 +1513,18 @@ } }, "node_modules/zod": { - "version": "3.25.64", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.64.tgz", - "integrity": "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==", + "version": "3.25.67", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", + "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zod-to-json-schema": { - "version": "3.24.5", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", - "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", "license": "ISC", "peerDependencies": { "zod": "^3.24.1" diff --git a/package.json b/package.json index b8e3c18..10bf07c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-neovim-server", - "version": "0.5.2", + "version": "0.5.3", "description": "An MCP server for neovim", "type": "module", "bin": { @@ -26,13 +26,13 @@ }, "homepage": "https://github.com/bigcodegen/mcp-neovim-server#readme", "dependencies": { - "@modelcontextprotocol/sdk": "^1.11.0", + "@modelcontextprotocol/sdk": "^1.13.2", "neovim": "^5.3.0", "ts-node": "^10.9.2", - "zod": "^3.25.64" + "zod": "^3.25.67" }, "devDependencies": { - "@types/node": "^22.15.3", + "@types/node": "^24.0.6", "typescript": "^5.8.3" } } diff --git a/src/index.ts b/src/index.ts index 4ba395d..0df5f7a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,7 @@ import { z } from "zod"; const server = new McpServer( { name: "mcp-neovim-server", - version: "0.5.2" + version: "0.5.3" } ); @@ -244,7 +244,7 @@ server.tool( "vim_register", "Manage Neovim register contents", { - register: z.string().regex(/^[a-z\"]$/).describe("Register name - a lowercase letter [a-z] or double-quote [\"] for the unnamed register"), + register: z.string().regex(/^[a-z"]$/).describe("Register name - a lowercase letter [a-z] or double-quote [\"] for the unnamed register"), content: z.string().describe("The text content to store in the specified register") }, async ({ register, content }) => { diff --git a/src/neovim.ts b/src/neovim.ts index 1d19fa0..799224f 100644 --- a/src/neovim.ts +++ b/src/neovim.ts @@ -213,25 +213,32 @@ export class NeovimManager { const tabpage = await nvim.tabpage; const currentTab = await tabpage.number; - // Get marks (a-z) + // 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]; - marks[mark] = [pos[1], pos[2]]; + // 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) + // 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 { - registers[reg] = String(await nvim.eval(`getreg('${reg}')`)); + 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 + // Register empty or error } } @@ -243,8 +250,8 @@ export class NeovimManager { let pluginInfo = ''; try { - // Get LSP clients if available - const lspClients = await nvim.eval('luaeval("vim.lsp.get_active_clients()")'); + // 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}`; @@ -288,14 +295,72 @@ export class NeovimManager { }; if (mode.mode.startsWith('v')) { - const start = await nvim.eval(`getpos("'<")`) as [number, number, number, number]; - const end = await nvim.eval(`getpos("'>")`) as [number, number, number, number]; - const lines = await buffer.getLines({ - start: start[1] - 1, - end: end[1], - strictIndexing: true - }); - neovimStatus.visualSelection = lines.join('\n'); + try { + // Use a more reliable method to get the visual selection + // This Lua code gets the actual selected text + const visualText = await nvim.lua(` + local mode = vim.fn.visualmode() + if mode == '' then + return '' + end + + -- Save current register content + local save_reg = vim.fn.getreg('"') + local save_regtype = vim.fn.getregtype('"') + + -- Yank the visual selection to unnamed register + vim.cmd('normal! "vy') + + -- Get the yanked text + local selected_text = vim.fn.getreg('"') + + -- Restore the register + vim.fn.setreg('"', save_reg, save_regtype) + + return selected_text + `); + + neovimStatus.visualSelection = String(visualText || ''); + } catch (e) { + // Fallback method using getpos and getline + try { + const start = await nvim.eval(`getpos("'<")`) as [number, number, number, number]; + const end = await nvim.eval(`getpos("'>")`) as [number, number, number, number]; + + if (start[1] === end[1]) { + // Single line selection + const line = await nvim.eval(`getline(${start[1]})`) as string; + const startCol = start[2] - 1; // Convert to 0-based + const endCol = end[2]; // Keep 1-based for substring end + neovimStatus.visualSelection = line.substring(startCol, endCol); + } else { + // Multi-line selection + const lines = await nvim.eval(`getline(${start[1]}, ${end[1]})`) as string[]; + if (lines && lines.length > 0) { + const result = []; + const startCol = start[2] - 1; + const endCol = end[2]; + + // First line: from start column to end + result.push(lines[0].substring(startCol)); + + // Middle lines: complete lines + for (let i = 1; i < lines.length - 1; i++) { + result.push(lines[i]); + } + + // Last line: from beginning to end column + if (lines.length > 1) { + result.push(lines[lines.length - 1].substring(0, endCol)); + } + + neovimStatus.visualSelection = result.join('\n'); + } + } + } catch (e2) { + neovimStatus.visualSelection = ''; + } + } } return neovimStatus;