├── .npmignore ├── .prettierignore ├── bun.lockb ├── src ├── sample.test.ts ├── index.ts ├── cli │ ├── types.ts │ ├── index.ts │ └── generate.ts ├── tests.ts ├── logger.ts ├── attach.ts ├── types.ts └── neovim-api.types.ts ├── .prettierrc.json ├── .github ├── dependabot.yml └── workflows │ └── release.yml ├── .eslintrc.json ├── tsconfig.json ├── LICENSE ├── package.json ├── docs ├── nvim.svg └── bun.svg ├── .gitignore └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | docs 2 | bun.lockb 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .github/dependabot.yml 2 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wallpants/bunvim/HEAD/bun.lockb -------------------------------------------------------------------------------- /src/sample.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "bun:test"; 2 | 3 | test("2 + 1", () => { 4 | expect(2 + 1).toBe(3); 5 | }); 6 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { attach } from "./attach.ts"; 2 | 3 | export * from "./types.ts"; 4 | 5 | export const NVIM_LOG_LEVELS = { 6 | TRACE: 0, 7 | DEBUG: 1, 8 | INFO: 2, 9 | WARN: 3, 10 | ERROR: 4, 11 | OFF: 5, 12 | } as const; 13 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 100, 4 | "plugins": ["prettier-plugin-organize-imports"], 5 | "overrides": [ 6 | { 7 | "files": ["*.json", "*.md", "*.yml"], 8 | "options": { "tabWidth": 2 } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | - package-ecosystem: "npm" 8 | versioning-strategy: "increase" 9 | directory: "/" 10 | schedule: 11 | interval: "monthly" 12 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": "latest", 6 | "sourceType": "module", 7 | "project": "tsconfig.json" 8 | }, 9 | "extends": [ 10 | "eslint:recommended", 11 | "plugin:@typescript-eslint/strict-type-checked", 12 | "plugin:@typescript-eslint/stylistic-type-checked" 13 | ], 14 | "rules": { 15 | "@typescript-eslint/restrict-template-expressions": ["error", { "allowNumber": true }], 16 | "@typescript-eslint/consistent-type-definitions": ["error", "type"], 17 | "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], 18 | "@typescript-eslint/consistent-type-imports": [ 19 | "warn", 20 | { "prefer": "type-imports", "fixStyle": "inline-type-imports" } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/cli/types.ts: -------------------------------------------------------------------------------- 1 | export type Params = [type: string, name: string][]; 2 | 3 | export type ApiMeta = { 4 | version: { 5 | major: number; 6 | minor: number; 7 | patch: number; 8 | prerelease: boolean; 9 | api_level: number; 10 | api_compatible: number; 11 | api_prerelease: boolean; 12 | }; 13 | functions: { 14 | name: string; 15 | since: number; 16 | parameters: Params; 17 | return_type: string; 18 | method: boolean; 19 | deprecated_since?: number; 20 | }[]; 21 | ui_events: { 22 | name: string; 23 | parameters: Params; 24 | since: number; 25 | }[]; 26 | ui_options: string[]; 27 | error_types: Record; 28 | types: Record; 29 | }; 30 | -------------------------------------------------------------------------------- /src/tests.ts: -------------------------------------------------------------------------------- 1 | import { attach } from "./index.ts"; 2 | 3 | const SOCKET = process.env["NVIM"]; 4 | if (!SOCKET) throw Error("socket missing"); 5 | 6 | type MyEvents = { 7 | notifications: { 8 | "some-notification": [name: string, id: number]; 9 | }; 10 | requests: { 11 | "some-request": [name: string]; 12 | }; 13 | }; 14 | 15 | const nvim = await attach({ 16 | socket: SOCKET, 17 | client: { 18 | name: "bunvim", 19 | version: { 20 | minor: 0, 21 | }, 22 | methods: { 23 | "some-method": { 24 | async: false, 25 | nargs: 9, 26 | }, 27 | }, 28 | attributes: { 29 | website: "https://wallpants.io", 30 | }, 31 | }, 32 | logging: { level: "debug" }, 33 | }); 34 | 35 | nvim.detach(); 36 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["ESNext"], 4 | "module": "ESNext", 5 | "target": "ESNext", 6 | "moduleResolution": "Bundler", 7 | "moduleDetection": "force", 8 | "types": ["bun-types"], 9 | "allowImportingTsExtensions": true, 10 | "noEmit": true, 11 | "strict": true, 12 | "downlevelIteration": true, 13 | "skipLibCheck": true, 14 | "allowSyntheticDefaultImports": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "allowJs": true, 17 | 18 | // strict: https://github.com/tsconfig/bases/blob/main/bases/strictest.json 19 | "allowUnusedLabels": false, 20 | "allowUnreachableCode": false, 21 | "exactOptionalPropertyTypes": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noImplicitOverride": true, 24 | "noImplicitReturns": true, 25 | "noPropertyAccessFromIndexSignature": true, 26 | "noUncheckedIndexedAccess": true, 27 | "noUnusedLocals": true, 28 | "noUnusedParameters": true, 29 | "isolatedModules": true, 30 | "checkJs": true, 31 | "esModuleInterop": true 32 | }, 33 | "include": ["src", "./package.json"] 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # License information 2 | 3 | ## Contribution License Agreement 4 | 5 | If you contribute code to this project, you are implicitly allowing your code 6 | to be distributed under the MIT license. You are also implicitly verifying that 7 | all code is your original work. 8 | 9 | ## Bunvim 10 | 11 | Copyright (c) 2023+, Wallpants (https://github.com/wallpants/) 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in 21 | all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | THE SOFTWARE. 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bunvim", 3 | "author": "wallpants", 4 | "type": "module", 5 | "version": "0.0.0", 6 | "license": "MIT", 7 | "description": "Neovim bun client", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/wallpants/bunvim.git" 11 | }, 12 | "bin": { 13 | "bunvim": "src/cli/index.ts" 14 | }, 15 | "exports": { 16 | "types": "./src/index.ts", 17 | "bun": "./src/index.ts" 18 | }, 19 | "release": { 20 | "branches": [ 21 | "main" 22 | ] 23 | }, 24 | "config": { 25 | "commitizen": { 26 | "path": "@commitlint/cz-commitlint" 27 | } 28 | }, 29 | "commitlint": { 30 | "extends": [ 31 | "@commitlint/config-conventional" 32 | ], 33 | "rules": { 34 | "body-max-line-length": [ 35 | 0 36 | ] 37 | } 38 | }, 39 | "scripts": { 40 | "commit": "cz", 41 | "vi": "nvim", 42 | "format": "prettier **/*.{md,ts,yml} -w", 43 | "lint": "eslint . --ext ts --report-unused-disable-directives --max-warnings 0", 44 | "typecheck": "tsc", 45 | "check": "bun run typecheck && bun run lint" 46 | }, 47 | "dependencies": { 48 | "commander": "^12.1.0", 49 | "msgpackr": "^1.11.2", 50 | "winston": "^3.16.0" 51 | }, 52 | "devDependencies": { 53 | "@commitlint/config-conventional": "^19.5.0", 54 | "@commitlint/cz-commitlint": "^19.5.0", 55 | "@types/bun": "^1.1.12", 56 | "@typescript-eslint/eslint-plugin": "^8.12.2", 57 | "@typescript-eslint/parser": "^8.13.0", 58 | "commitizen": "^4.3.1", 59 | "commitlint": "^19.5.0", 60 | "eslint": "^8.57.1", 61 | "prettier": "^3.3.3", 62 | "prettier-plugin-organize-imports": "^4.1.0", 63 | "semantic-release": "^24.2.0", 64 | "typescript": "^5.6.3" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | Test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repo 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | - name: Setup node 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 20.x 23 | - name: Install bun 24 | uses: oven-sh/setup-bun@v2 25 | - name: Install dependencies 26 | run: bun install --no-save 27 | - name: Validate PR commits with commitlint 28 | if: github.event_name == 'pull_request' 29 | run: bun run commitlint --from=origin/main --to=HEAD 30 | - name: Validate code formatting 31 | run: | 32 | bun run format 33 | if [[ $(git status --porcelain) ]]; then 34 | echo "Code not properly formatted. Please run 'bun run format' locally and commit changes." 35 | exit 1 36 | fi 37 | - name: Lint & check types 38 | run: bun run check 39 | - name: Test 40 | run: bun test 41 | 42 | Release: 43 | needs: [Test] 44 | permissions: 45 | contents: write 46 | issues: write 47 | pull-requests: write 48 | if: | 49 | github.ref == 'refs/heads/main' && 50 | github.event.repository.fork == false 51 | runs-on: ubuntu-latest 52 | steps: 53 | - name: Checkout Code 54 | uses: actions/checkout@v4 55 | - name: Setup node 56 | uses: actions/setup-node@v4 57 | with: 58 | node-version: 20.x 59 | - name: Install bun 60 | uses: oven-sh/setup-bun@v2 61 | - name: Install dependencies 62 | run: bun install 63 | - name: Release 64 | env: 65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 66 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 67 | run: bun run semantic-release 68 | -------------------------------------------------------------------------------- /docs/nvim.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | neovim-mark@2x 4 | Created with Sketch (http://www.bohemiancoding.com/sketch) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/cli/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bun 2 | import { program } from "commander"; 3 | import { version } from "../../package.json"; 4 | 5 | program.name("bunvim").description("CLI to work with neovim's bun client").version(version); 6 | 7 | program 8 | .command("logs") 9 | .description("print bunvim client logs") 10 | .argument("", "Client name you specify in your attach call.") 11 | .action((name) => { 12 | Bun.spawn({ 13 | cmd: ["tail", "-F", "-n", "0", `/tmp/${name}.bunvim.logs`], 14 | stdin: null, 15 | stdout: "inherit", 16 | }); 17 | }) 18 | .exitOverride(() => process.exit(0)); 19 | 20 | // function validateLevel(value: string) { 21 | // const parsedInt = parseInt(value, 10); 22 | // if (isNaN(parsedInt)) { 23 | // throw new InvalidOptionArgumentError("\nNot a number."); 24 | // } 25 | // return parsedInt; 26 | // } 27 | 28 | // function validateOutDir(value: string) { 29 | // const resolved = resolve(value); 30 | // if (!existsSync(resolved) || !statSync(resolved).isDirectory()) { 31 | // throw new InvalidOptionArgumentError(`\nDirectory "${resolved}" not found.`); 32 | // } 33 | // return resolved; 34 | // } 35 | 36 | // program 37 | // .command("types") 38 | // .description("generate api types using your local neovim") 39 | // .option( 40 | // "-o, --outDir ", 41 | // "Path to dir where types file will be created.", 42 | // validateOutDir, 43 | // ".", 44 | // ) 45 | // .option( 46 | // "-l, --level ", 47 | // "Include info up to specified api level (inclusive). Leave unset to include all. Deprecated items are excluded by default.", 48 | // validateLevel, 49 | // ) 50 | // .action(async ({ level, outDir }: { level?: number; outDir?: string }) => { 51 | // const process = Bun.spawnSync({ cmd: ["nvim", "--api-info"] }); 52 | // const neovimApi = unpack(process.stdout) as ApiMeta; 53 | 54 | // neovimApi.functions = neovimApi.functions.filter((fn) => { 55 | // if (fn.deprecated_since !== undefined) return false; 56 | // if (level !== undefined && fn.since > level) return false; 57 | // return true; 58 | // }); 59 | 60 | // const content = generateTypescriptContent(neovimApi); 61 | 62 | // const segments: string[] = ["neovim-api.types.ts"]; 63 | // if (outDir) segments.unshift(outDir); 64 | // const resolved = resolve(...segments); 65 | 66 | // console.log(`\nGenerating types at:\n* ${resolved}\n`); 67 | // await Bun.write(resolved, content); 68 | // }) 69 | // .exitOverride(() => process.exit(0)); 70 | 71 | await program.exitOverride(() => process.exit(0)).parseAsync(); 72 | -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "node:path"; 2 | import winston from "winston"; 3 | import { MessageType, type Client, type LogLevel, type RPCMessage } from "./types.ts"; 4 | 5 | export function createLogger( 6 | client: Client, 7 | logging?: { level?: LogLevel | undefined; file?: string | undefined }, 8 | ) { 9 | if (!logging?.level) return; 10 | 11 | const defaultFilePath = `/tmp/${client.name}.bunvim.logs`; 12 | 13 | const logger = winston.createLogger({ 14 | level: logging.level, 15 | transports: [ 16 | new winston.transports.File({ 17 | filename: defaultFilePath, 18 | format: winston.format.combine( 19 | winston.format.colorize(), 20 | winston.format.timestamp({ format: "HH:mm:ss.SSS" }), 21 | winston.format.printf((info) => `\n${info.level} ${info["timestamp"]}`), 22 | ), 23 | }), 24 | new winston.transports.File({ 25 | filename: defaultFilePath, 26 | format: winston.format.combine( 27 | winston.format((info) => { 28 | // @ts-expect-error ts mad we delete non-optional prop `level` 29 | delete info.level; 30 | return info; 31 | })(), 32 | winston.format.prettyPrint({ 33 | colorize: true, 34 | }), 35 | ), 36 | }), 37 | ], 38 | }); 39 | 40 | if (logging.file) { 41 | const resolved = resolve(logging.file); 42 | logger.add( 43 | new winston.transports.File({ 44 | filename: resolved, 45 | format: winston.format.combine(winston.format.timestamp(), winston.format.json()), 46 | }), 47 | ); 48 | } 49 | 50 | return logger; 51 | } 52 | 53 | export function prettyRPCMessage(message: RPCMessage, direction: "out" | "in") { 54 | const prefix = direction === "out" ? "OUTGOING" : "INCOMING"; 55 | 56 | if (message[0] === MessageType.REQUEST) { 57 | return { 58 | [`${prefix}_RPC_REQUEST`]: { 59 | reqId: message[1], 60 | method: message[2], 61 | params: message[3], 62 | }, 63 | }; 64 | } 65 | 66 | if (message[0] === MessageType.RESPONSE) { 67 | return { 68 | [`${prefix}_RPC_RESPONSE`]: { 69 | reqId: message[1], 70 | error: message[2], 71 | result: message[3], 72 | }, 73 | }; 74 | } 75 | 76 | // if (message[0] === MessageType.NOTIFY) 77 | return { 78 | [`${prefix}_RPC_NOTIFICATION`]: { 79 | event: message[1], 80 | args: message[2], 81 | }, 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 2 | 3 | # Logs 4 | 5 | logs 6 | _.log 7 | npm-debug.log_ 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | 15 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 16 | 17 | # Runtime data 18 | 19 | pids 20 | _.pid 21 | _.seed 22 | \*.pid.lock 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | 26 | lib-cov 27 | 28 | # Coverage directory used by tools like istanbul 29 | 30 | coverage 31 | \*.lcov 32 | 33 | # nyc test coverage 34 | 35 | .nyc_output 36 | 37 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 38 | 39 | .grunt 40 | 41 | # Bower dependency directory (https://bower.io/) 42 | 43 | bower_components 44 | 45 | # node-waf configuration 46 | 47 | .lock-wscript 48 | 49 | # Compiled binary addons (https://nodejs.org/api/addons.html) 50 | 51 | build/Release 52 | 53 | # Dependency directories 54 | 55 | node_modules/ 56 | jspm_packages/ 57 | 58 | # Snowpack dependency directory (https://snowpack.dev/) 59 | 60 | web_modules/ 61 | 62 | # TypeScript cache 63 | 64 | \*.tsbuildinfo 65 | 66 | # Optional npm cache directory 67 | 68 | .npm 69 | 70 | # Optional eslint cache 71 | 72 | .eslintcache 73 | 74 | # Optional stylelint cache 75 | 76 | .stylelintcache 77 | 78 | # Microbundle cache 79 | 80 | .rpt2_cache/ 81 | .rts2_cache_cjs/ 82 | .rts2_cache_es/ 83 | .rts2_cache_umd/ 84 | 85 | # Optional REPL history 86 | 87 | .node_repl_history 88 | 89 | # Output of 'npm pack' 90 | 91 | \*.tgz 92 | 93 | # Yarn Integrity file 94 | 95 | .yarn-integrity 96 | 97 | # dotenv environment variable files 98 | 99 | .env 100 | .env.development.local 101 | .env.test.local 102 | .env.production.local 103 | .env.local 104 | 105 | # parcel-bundler cache (https://parceljs.org/) 106 | 107 | .cache 108 | .parcel-cache 109 | 110 | # Next.js build output 111 | 112 | .next 113 | out 114 | 115 | # Nuxt.js build / generate output 116 | 117 | .nuxt 118 | dist 119 | 120 | # Gatsby files 121 | 122 | .cache/ 123 | 124 | # Comment in the public line in if your project uses Gatsby and not Next.js 125 | 126 | # https://nextjs.org/blog/next-9-1#public-directory-support 127 | 128 | # public 129 | 130 | # vuepress build output 131 | 132 | .vuepress/dist 133 | 134 | # vuepress v2.x temp and cache directory 135 | 136 | .temp 137 | .cache 138 | 139 | # Docusaurus cache and generated files 140 | 141 | .docusaurus 142 | 143 | # Serverless directories 144 | 145 | .serverless/ 146 | 147 | # FuseBox cache 148 | 149 | .fusebox/ 150 | 151 | # DynamoDB Local files 152 | 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | 161 | .vscode-test 162 | 163 | # yarn v2 164 | 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.\* 170 | -------------------------------------------------------------------------------- /src/cli/generate.ts: -------------------------------------------------------------------------------- 1 | import { type ApiMeta, type Params } from "./types.ts"; 2 | 3 | // TODO: make this dynamic 4 | function toTypescriptType(type: string) { 5 | const typesMap: Record = { 6 | Array: "unknown[]", 7 | "ArrayOf(Buffer)": "number[]", 8 | "ArrayOf(Dictionary)": "Record[]", 9 | "ArrayOf(Integer)": "number[]", 10 | "ArrayOf(Integer, 2)": "[number, number]", 11 | "ArrayOf(String)": "string[]", 12 | "ArrayOf(Tabpage)": "number[]", 13 | "ArrayOf(Window)": "number[]", 14 | Boolean: "boolean", 15 | Buffer: "number", 16 | Dictionary: "Record", 17 | Float: "number", 18 | Integer: "number", 19 | LuaRef: "unknown", 20 | Object: "unknown", 21 | String: "string", 22 | Tabpage: "number", 23 | void: "void", 24 | Window: "number", 25 | }; 26 | 27 | const typescriptType = typesMap[type]; 28 | if (!typescriptType) throw Error(`typescriptType for type: ${type} could not be termined`); 29 | return typescriptType; 30 | } 31 | 32 | function parseParameters(params: Params) { 33 | return params.map((param) => `${param[1]}: ${toTypescriptType(param[0])}`).join(", "); 34 | } 35 | 36 | export function generateTypescriptContent(neovimApi: ApiMeta) { 37 | let output = `/* eslint @typescript-eslint/no-invalid-void-type: 0 */ 38 | 39 | /* 40 | * file generated by bunvim: https://github.com/gualcasas/bunvim 41 | */ 42 | 43 | import { type EventsMap } from "bunvim"; 44 | 45 | export type NeovimApi< 46 | Notifications extends EventsMap = EventsMap, 47 | Requests extends EventsMap = EventsMap, 48 | > = {\n`; 49 | // functions 50 | output += " functions: {\n"; 51 | neovimApi.functions.forEach((fun) => { 52 | output += ` ${fun.name}: { 53 | parameters: [${parseParameters(fun.parameters)}]; 54 | return_type: ${toTypescriptType(fun.return_type)};\n };\n`; 55 | }); 56 | output += " };\n\n"; 57 | 58 | // ui_events 59 | output += " ui_events: {\n"; 60 | neovimApi.ui_events.forEach((event) => { 61 | output += ` ${event.name}: {\n parameters: [${parseParameters( 62 | event.parameters, 63 | )}];\n };\n`; 64 | }); 65 | output += " };\n\n"; 66 | 67 | // error_types 68 | output += " error_types: {\n"; 69 | Object.entries(neovimApi.error_types).forEach(([type, { id }]) => { 70 | output += ` ${type}: { id: ${id}; };\n`; 71 | }); 72 | output += " };\n\n"; 73 | 74 | // types 75 | output += " types: {\n"; 76 | Object.entries(neovimApi.types).forEach(([type, { id, prefix }]) => { 77 | output += ` ${type}: { id: ${id}; prefix: "${prefix}"; };\n`; 78 | }); 79 | output += " };\n\n"; 80 | 81 | // ui_options 82 | output += ` ui_options: ${JSON.stringify(neovimApi.ui_options)};\n\n`; 83 | 84 | // custom user notifications 85 | output += ` notifications: Notifications;\n\n`; 86 | 87 | // custm user requests 88 | output += ` requests: Requests;\n`; 89 | 90 | output += "};"; 91 | 92 | return output; 93 | } 94 | -------------------------------------------------------------------------------- /docs/bun.svg: -------------------------------------------------------------------------------- 1 | Bun Logo -------------------------------------------------------------------------------- /src/attach.ts: -------------------------------------------------------------------------------- 1 | import { Packr, UnpackrStream, addExtension, unpack } from "msgpackr"; 2 | import { EventEmitter } from "node:events"; 3 | import { createLogger, prettyRPCMessage } from "./logger.ts"; 4 | import { 5 | MessageType, 6 | type AttachParams, 7 | type BaseEvents, 8 | type EventHandler, 9 | type Nvim, 10 | type RPCMessage, 11 | type RPCNotification, 12 | type RPCRequest, 13 | type RPCResponse, 14 | } from "./types.ts"; 15 | 16 | const packr = new Packr({ useRecords: false }); 17 | const unpackrStream = new UnpackrStream({ useRecords: false }); 18 | 19 | [0, 1, 2].forEach((type) => { 20 | // https://neovim.io/doc/user/api.html#api-definitions 21 | // decode Buffer, Window, and Tabpage as numbers 22 | // Buffer id: 0 prefix: nvim_buf_ 23 | // Window id: 1 prefix: nvim_win_ 24 | // Tabpage id: 2 prefix: nvim_tabpage_ 25 | addExtension({ type, unpack: (buffer) => unpack(buffer) as number }); 26 | }); 27 | 28 | export async function attach({ 29 | socket, 30 | client, 31 | logging, 32 | }: AttachParams): Promise> { 33 | const logger = createLogger(client, logging); 34 | const messageOutQueue: RPCMessage[] = []; 35 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 36 | const notificationHandlers = new Map>>(); 37 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 38 | const requestHandlers = new Map>(); 39 | const emitter = new EventEmitter({ captureRejections: true }); 40 | 41 | let lastReqId = 0; 42 | let handlerId = 0; 43 | 44 | const nvimSocket = await Bun.connect({ 45 | unix: socket, 46 | socket: { 47 | binaryType: "uint8array", 48 | data(_, data) { 49 | // Sometimes RPC messages are split into multiple socket messages. 50 | // `unpackrStream` handles collecting all socket messages if the RPC message 51 | // is split and decoding it. 52 | unpackrStream.write(data); 53 | }, 54 | error(_, error) { 55 | logger?.error("socket error", error); 56 | }, 57 | end() { 58 | logger?.debug("connection closed by neovim"); 59 | }, 60 | close() { 61 | logger?.debug("connection closed by bunvim"); 62 | }, 63 | }, 64 | }); 65 | 66 | function processMessageOutQueue() { 67 | // All writing to neovim happens through this function. 68 | // Outgoing RPC messages are added to the `messageOutQueue` and sent ASAP 69 | if (!messageOutQueue.length) return; 70 | 71 | const message = messageOutQueue.shift(); 72 | if (!message) { 73 | logger?.error("Cannot process undefined message"); 74 | return; 75 | } 76 | 77 | logger?.debug(prettyRPCMessage(message, "out")); 78 | nvimSocket.write(packr.pack(message) as unknown as Uint8Array); 79 | processMessageOutQueue(); 80 | } 81 | 82 | function runNotificationHandlers(message: RPCNotification) { 83 | // message[1] notification name 84 | // message[2] args 85 | const handlers = notificationHandlers.get(message[1]); 86 | if (!handlers) return; 87 | 88 | // eslint-disable-next-line @typescript-eslint/no-misused-promises 89 | Object.entries(handlers).forEach(async ([id, handler]) => { 90 | const result = await handler(message[2]); 91 | // remove notification handler if it returns specifically `true` 92 | // other truthy values won't trigger the removal 93 | // eslint-disable-next-line 94 | if (result === true) delete handlers[id]; 95 | }); 96 | } 97 | 98 | unpackrStream.on("data", (message: RPCMessage) => { 99 | (async () => { 100 | logger?.debug(prettyRPCMessage(message, "in")); 101 | if (message[0] === MessageType.NOTIFY) { 102 | // asynchronously run notification handlers. 103 | // RPCNotifications don't need a response 104 | runNotificationHandlers(message); 105 | } 106 | 107 | if (message[0] === MessageType.RESPONSE) { 108 | // message[1] reqId 109 | // message[2] error 110 | // message[3] result 111 | emitter.emit(`response-${message[1]}`, message[2], message[3]); 112 | } 113 | 114 | if (message[0] === MessageType.REQUEST) { 115 | // message[1] reqId 116 | // message[2] method name 117 | // message[3] args 118 | const handler = requestHandlers.get(message[2]); 119 | 120 | // RPCRequests block neovim until a response is received. 121 | // RPCResponse is added to beginning of queue to be sent ASAP. 122 | if (!handler) { 123 | const notFound: RPCResponse = [ 124 | MessageType.RESPONSE, 125 | message[1], 126 | `no handler for method ${message[2]} found`, 127 | null, 128 | ]; 129 | messageOutQueue.unshift(notFound); 130 | } else { 131 | try { 132 | const result = await handler(message[3]); 133 | const response: RPCResponse = [ 134 | MessageType.RESPONSE, 135 | message[1], 136 | null, 137 | result, 138 | ]; 139 | messageOutQueue.unshift(response); 140 | } catch (err) { 141 | const response: RPCResponse = [ 142 | MessageType.RESPONSE, 143 | message[1], 144 | String(err), 145 | null, 146 | ]; 147 | messageOutQueue.unshift(response); 148 | } 149 | } 150 | } 151 | 152 | // Continue processing queue 153 | processMessageOutQueue(); 154 | })().catch((err: unknown) => logger?.error("unpackrStream error", err)); 155 | }); 156 | 157 | const call: Nvim["call"] = (func, args) => { 158 | const reqId = ++lastReqId; 159 | const request: RPCRequest = [MessageType.REQUEST, reqId, func as string, args]; 160 | 161 | return new Promise((resolve, reject) => { 162 | // Register response listener before adding request to queue to avoid 163 | // response coming in before listener was set up. 164 | emitter.once(`response-${reqId}`, (error, result) => { 165 | if (error) reject(error as Error); 166 | resolve(result as unknown); 167 | }); 168 | 169 | messageOutQueue.push(request); 170 | // Start processing queue if we're not already 171 | processMessageOutQueue(); 172 | }); 173 | }; 174 | 175 | await call("nvim_set_client_info", [ 176 | client.name, 177 | client.version ?? {}, 178 | client.type ?? "msgpack-rpc", 179 | client.methods ?? {}, 180 | client.attributes ?? {}, 181 | ]); 182 | 183 | const channelId = (await call("nvim_get_api_info", []))[0] as number; 184 | 185 | return { 186 | call, 187 | channelId, 188 | logger: logger, 189 | onNotification(notification, callback) { 190 | const handlers = notificationHandlers.get(notification as string) ?? {}; 191 | handlers[++handlerId] = callback; 192 | notificationHandlers.set(notification as string, handlers); 193 | }, 194 | onRequest(method, callback) { 195 | requestHandlers.set(method as string, callback); 196 | }, 197 | detach() { 198 | nvimSocket.end(); 199 | }, 200 | }; 201 | } 202 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type winston from "winston"; 2 | import { type NeovimApi } from "./neovim-api.types.ts"; 3 | 4 | export type Awaitable = T | Promise; 5 | 6 | export type EventsMap = Record; 7 | 8 | export type BaseEvents = { 9 | notifications: EventsMap; 10 | requests: EventsMap; 11 | }; 12 | 13 | export type LogLevel = "error" | "warn" | "info" | "http" | "verbose" | "debug" | "silly"; 14 | 15 | export type Client = { 16 | /** 17 | * `name` can be used to find channel id on neovim. 18 | * It's also used for logging. 19 | * 20 | * ```lua 21 | * -- look for channel.client.name in channel list 22 | * 23 | * local chans_list = vim.fn.nvim_list_chans() 24 | * vim.print(chans_list) 25 | * ``` 26 | */ 27 | name: string; 28 | /** Dictionary describing the version */ 29 | version?: { 30 | /** major version (defaults to 0 if not set, for no release yet) */ 31 | major?: number; 32 | /** minor version */ 33 | minor?: number; 34 | /** patch number */ 35 | patch?: number; 36 | /** string describing a prerelease, like "dev" or "beta1" */ 37 | prerelease?: string; 38 | /** hash or similar identifier of commit */ 39 | commit?: string; 40 | }; 41 | /** 42 | * - `"remote"` remote client connected "Nvim flavored" MessagePack-RPC (responses must be in reverse order of requests). msgpack-rpc 43 | * - `"msgpack-rpc"` remote client connected to Nvim via fully MessagePack-RPC compliant protocol. 44 | * - `"ui"` gui frontend 45 | * - `"embedder"` application using Nvim as a component (for example, IDE/editor implementing a vim mode). 46 | * - `"host"` plugin host, typically started by nvim 47 | * - `"plugin"` single plugin, started by nvim 48 | * 49 | * @default 50 | * "msgpack-rpc" 51 | */ 52 | type?: "remote" | "msgpack-rpc" | "ui" | "embedder" | "host" | "plugin"; 53 | /** 54 | * Builtin methods in the client. 55 | * For a host, this does not include plugin methods which will be discovered later. 56 | * The key should be the method name. 57 | */ 58 | methods?: Record< 59 | string, 60 | { 61 | async?: boolean; 62 | nargs?: number; 63 | } 64 | >; 65 | /** 66 | * Arbitrary string:string map of informal client properties. 67 | */ 68 | attributes?: { 69 | [key: string]: string; 70 | website?: string; 71 | license?: string; 72 | logo?: string; 73 | }; 74 | }; 75 | 76 | export type AttachParams = { 77 | /** 78 | * neovim socket 79 | * 80 | * Usually you get this value from `process.env.NVIM` which is set 81 | * automagically by neovim on any child processes 82 | * 83 | * @see {@link https://neovim.io/doc/user/eval.html#%24NVIM} 84 | * @see {@link https://neovim.io/doc/user/eval.html#v%3Aservername} 85 | * 86 | * @example 87 | * ```lua 88 | * -- init.lua 89 | * vim.fn.jobstart("bun run src/main.ts", { cwd = root_dir }) 90 | * ``` 91 | * 92 | * ```typescript 93 | * // src/main.ts 94 | * const socket = process.env.NVIM; 95 | * if (!socket) throw Error("socket missing"); 96 | * 97 | * const nvim = await attach({ socket, client: { name: "my_client" } }); 98 | * ``` 99 | */ 100 | socket: string; 101 | 102 | /** 103 | * RPC client info, only name is required. 104 | * This is sent to neovim on-connection-open by calling `nvim_set_client_info()` 105 | * @see {@link https://neovim.io/doc/user/api.html#nvim_set_client_info()} 106 | */ 107 | client: Client; 108 | 109 | /** 110 | * If left undefined, logging will be disabled. 111 | */ 112 | logging?: { 113 | /** 114 | * @remarks 115 | * bunvim internally logs with `logger.debug()` and `logger.error()` 116 | * Set logLevel higher than `debug` to not display bunvim's internal logs 117 | * 118 | * Levels from highest to lowest priority 119 | * - error 120 | * - warn 121 | * - info 122 | * - http 123 | * - verbose 124 | * - debug 125 | * - silly 126 | */ 127 | level?: LogLevel | undefined; 128 | /** 129 | * Path to write logs to. 130 | * 131 | * @example 132 | * "~/Projects/logs/my-plugin.log" 133 | */ 134 | file?: string | undefined; 135 | }; 136 | }; 137 | 138 | export enum MessageType { 139 | REQUEST = 0, 140 | RESPONSE = 1, 141 | NOTIFY = 2, 142 | } 143 | 144 | export type RPCRequest = [MessageType.REQUEST, id: number, method: string, args: unknown[]]; 145 | export type RPCNotification = [MessageType.NOTIFY, notification: string, args: unknown[]]; 146 | export type RPCResponse = [MessageType.RESPONSE, id: number, error: string | null, result: unknown]; 147 | export type RPCMessage = RPCRequest | RPCNotification | RPCResponse; 148 | 149 | export type EventHandler = (args: Args) => Awaitable; 150 | export type NotificationHandler = EventHandler; 151 | export type RequestHandler = EventHandler; 152 | 153 | type UIEvent = [ 154 | event: E, 155 | args: NeovimApi["ui_events"][E]["parameters"], 156 | ]; 157 | 158 | type UINotifications = { 159 | redraw: UIEvent[]; 160 | }; 161 | 162 | export type Nvim = { 163 | /** 164 | * 165 | * Call a neovim function 166 | * @see {@link https://neovim.io/doc/user/api.html} 167 | * 168 | * @param func - function name 169 | * @param args - function arguments, provide empty array `[]` if no args 170 | */ 171 | call( 172 | func: M, 173 | args: NeovimApi["functions"][M]["parameters"], 174 | ): Promise; 175 | /** 176 | * 177 | * RPC channel 178 | */ 179 | channelId: number; 180 | /** 181 | * 182 | * Register a handler for rpc notifications. 183 | * There can be multiple handlers setup for the same notification. 184 | * Return `true` to remove handler. 185 | * 186 | * @param notification - event name 187 | * @param callback - notification handler 188 | * 189 | * @example 190 | * ```typescript 191 | * await nvim.call("nvim_subscribe", ["my_rpc_notification"]); 192 | * 193 | * nvim.onNotification("my_rpc_notification", (args) => { 194 | * nvim.logger?.info(args); 195 | * // return true to remove listener 196 | * return true; 197 | * }); 198 | * ``` 199 | */ 200 | onNotification( 201 | notification: N, 202 | callback: EventHandler<(ApiInfo["notifications"] & UINotifications)[N], unknown>, 203 | ): void; 204 | /** 205 | * 206 | * Register/Update a handler for rpc requests. 207 | * 208 | * There can only be one handler per method. 209 | * This means calling `nvim.onRequest("my_func", () => {})` will override 210 | * any previously registered handlers under `"my_func"`. 211 | * 212 | * @param method - method name 213 | * @param callback - request handler 214 | * 215 | * @example 216 | * ```typescript 217 | * nvim.onRequest("my_func", async (args) => { 218 | * const result = await asyncFunc(args); 219 | * 220 | * if (result < 10) { 221 | * // throwing an error sends the error back to neovim 222 | * throw Error("result too low"); 223 | * } 224 | * 225 | * return result; 226 | * }); 227 | * ``` 228 | */ 229 | onRequest( 230 | method: M, 231 | callback: EventHandler, 232 | ): void; 233 | /** 234 | * 235 | * Close socket connection to neovim. 236 | */ 237 | detach(): void; 238 | /** 239 | * 240 | * Reference to winston logger. `undefined` if no `logging` provided 241 | * to `attach` 242 | */ 243 | logger: winston.Logger | undefined; 244 | }; 245 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bunvim 2 | 3 | 4 | 5 | 6 | Bunvim is a [Bun](https://bun.sh/) client that allows you to interact with Neovim through RPC 7 | using TypeScript and JavaScript. If you're familiar with Neovim's Lua API, you'll find this client easy to use. 8 | 9 | This client includes [TypeScript definitions](https://github.com/wallpants/bunvim/blob/main/src/neovim-api.types.ts), 10 | generated from [Neovim's api-metadata](https://neovim.io/doc/user/api.html#api-metadata), describing API function signatures, 11 | including function parameters and return values. 12 | 13 | All functionality is implemented in [one file](https://github.com/wallpants/bunvim/blob/main/src/attach.ts). 14 | If you're looking for higher levels of abstraction, take a look at [neovim/node-client](https://github.com/neovim/node-client) 15 | and [neoclide/neovim](https://github.com/neoclide/neovim). Good luck. 16 | 17 | ## ✅ Requirements 18 | 19 | - [x] [Bun](https://bun.sh/) 20 | - [x] [Neovim](https://neovim.io/) 21 | 22 | ## 📦 Installation 23 | 24 | ```sh 25 | bun install bunvim 26 | ``` 27 | 28 | ## 💻 Usage 29 | 30 | For examples of plugins using Bunvim, take a look at [napoleon.nvim](https://github.com/wallpants/napoleon.nvim), 31 | [ghost-text.nvim](https://github.com/wallpants/ghost-text.nvim) and [github-preview.nvim](https://github.com/wallpants/github-preview.nvim). 32 | 33 | You should keep a tab open with [Neovim API docs](https://neovim.io/doc/user/api.html) when working with Bunvim. 34 | Although this client includes generated TypeScript types, you'll find the detailed descriptions in the official docs very helpful if not necessary. 35 | 36 | Create a script: 37 | 38 | ```typescript 39 | // my-plugin.ts 40 | import { attach } from "bunvim"; 41 | 42 | // RPC listenning address 43 | const SOCKET = "/tmp/bunvim.nvim.socket"; 44 | 45 | const nvim = await attach({ 46 | socket: SOCKET, 47 | client: { name: "my-plugin-name" }, 48 | }); 49 | 50 | // append "hello world" to current buffer 51 | await nvim.call("nvim_buf_set_lines", [0, -1, -1, true, ["hello world"]]); 52 | 53 | // disable relative numbers 54 | await nvim.call("nvim_set_option_value", ["relativenumber", false, {}]); 55 | 56 | // create a vertical split 57 | await nvim.call("nvim_command", ["vs"]); 58 | 59 | // print cursor position on cursor move 60 | await nvim.call("nvim_create_autocmd", [ 61 | ["CursorHold", "CursorHoldI"], 62 | { 63 | desc: "Print Cursor Position", 64 | command: `lua 65 | local cursor_pos = vim.api.nvim_win_get_cursor(0) 66 | vim.print(cursor_pos)`, 67 | }, 68 | ]); 69 | 70 | nvim.detach(); 71 | ``` 72 | 73 | Initialize Neovim with the [RPC listening address](https://neovim.io/doc/user/starting.html#--listen) specified above: 74 | 75 | ```bash 76 | nvim --listen /tmp/bunvim.nvim.socket 77 | ``` 78 | 79 | Execute your script from another terminal: 80 | 81 | ```sh 82 | bun run my-plugin.ts 83 | ``` 84 | 85 | If your plugin is executed as a child process of Neovim: 86 | 87 | ```lua 88 | -- somewhere in your neovim lua config files 89 | 90 | local function run_script() 91 | 92 | -- neovim sets the environment variable NVIM in all its child processes 93 | -- NVIM = the RPC listening address assigned to the current neovim instance 94 | -- neovim sets an RPC listening address if you don't manually specify one 95 | -- https://neovim.io/doc/user/builtin.html#jobstart-env 96 | 97 | vim.fn.jobstart("bun run my-plugin.ts", { 98 | cwd = vim.fn.expand("~/path/to/plugin/"), 99 | }) 100 | end 101 | 102 | vim.api.nvim_create_user_command("RunMyScript", run_script, {}) 103 | ``` 104 | 105 | You could then open Neovim without manually specifying an RPC listening address, just `nvim` and then run the command `:RunMyScript`. 106 | Your Bun process would then have access to the `NVIM` environment variable. 107 | 108 | ```typescript 109 | // my-plugin.ts 110 | import { attach } from "bunvim"; 111 | 112 | const SOCKET = process.env["NVIM"]; 113 | if (!SOCKET) throw Error("socket missing"); 114 | 115 | const nvim = await attach({ 116 | socket: SOCKET, 117 | client: { name: "my-plugin-name" }, 118 | }); 119 | ``` 120 | 121 | ## 📖 API Reference 122 | 123 | This module exports only one method `attach` and a bunch of TypeScript types. `attach` returns 124 | an `Nvim` object that can be used to interact with Neovim. 125 | 126 |
127 | 128 | nvim.call(function: string, args: unknown[]) 129 | 130 | 131 | > 132 | 133 | > Used to call [any of these functions](https://neovim.io/doc/user/api.html). They're all typed. You should 134 | > get function names autocompletion & warnings from TypeScript if the parameters don't match the expected types. 135 | > Some function calls return a value, others don't. 136 | > 137 | > ```typescript 138 | > const bufferContent = await nvim.call("nvim_buf_get_lines", [0, 0, -1, true]); 139 | > ``` 140 | 141 | > --- 142 | 143 |
144 | 145 |
146 | 147 | nvim.channelId 148 | 149 | 150 | > 151 | 152 | > RPC Channel ID. 153 | > 154 | > ```typescript 155 | > const channelId = nvim.channelId; 156 | > 157 | > await nvim.call("nvim_create_autocmd", [ 158 | > ["CursorMove"], 159 | > { 160 | > desc: "Notify my-plugin", 161 | > command: `lua 162 | > vim.rpcnotify(${channelId}, "my-notification")`, 163 | > }, 164 | > ]); 165 | > ``` 166 | 167 | > --- 168 | 169 |
170 | 171 |
172 | 173 | nvim.onNotification(notification: string, callback: function) 174 | 175 | 176 | > 177 | 178 | > Registers a handler for a specific RPC Notification. 179 | > 180 | > Notifications must be typed before you declare a handler for them, or TypeScript will complain. 181 | > 182 | > ```typescript 183 | > import { attach, type BaseEvents, type EventsMap } from "bunvim"; 184 | > 185 | > // an interface to define your notifications and their args 186 | > interface MyEvents extends BaseEvents { 187 | > requests: EventsMap; // default type 188 | > notifications: { 189 | > // declare custom notification: "cursor_move", 190 | > // that would be called with args: [row: number, col: number] 191 | > "cursor_move": [row: number, col: number]; 192 | > }; 193 | > } 194 | > 195 | > // attach to neovim 196 | > const nvim = await attach({ ... }) 197 | > 198 | > let count = 0; 199 | > 200 | > // register a handler for the notification "cursor_move" 201 | > nvim.onNotification("cursor_move", async ([row, col]) => { 202 | > // "row" and "col" are of type "number" as specified above 203 | > 204 | > // CAUTION: 205 | > // it's up to you to make sure the handler receives the correct args, 206 | > // bunvim doesn't do any validations 207 | > 208 | > // print row and col in neovim 209 | > await nvim.call("nvim_exec_lua", [`print("row: ${row} - col: ${col}")`, []]); 210 | > 211 | > // return `true` to remove handler 212 | > return count++ >= 5; 213 | > }); 214 | > 215 | > // multiple handlers can be registered for the same notification 216 | > nvim.onNotification("cursor_move", async ([row, col]) => { 217 | > // replace contents in current buffer lines 1 and 2 218 | > await nvim.call("nvim_buf_set_lines", [0, 0, 2, true, [`row: ${row}`, `col: ${col}`]]); 219 | > }); 220 | > 221 | > const channelId = nvim.channelId; 222 | > 223 | > // create autocommand to notify our plugin via `vim.rpcnotify` 224 | > // whenever the cursor moves 225 | > await nvim.call("nvim_create_autocmd", [ 226 | > ["CursorHold", "CursorHoldI"], 227 | > { 228 | > desc: "Notify on Cursor Move", 229 | > command: `lua 230 | > local cursor_pos = vim.api.nvim_win_get_cursor(0) 231 | > local row = cursor_pos[1] 232 | > local col = cursor_pos[2] 233 | > vim.rpcnotify(${channelId}, "cursor_move", row, col)`, 234 | > }, 235 | > ]); 236 | > ``` 237 | 238 | > --- 239 | 240 |
241 | 242 |
243 | 244 | nvim.onRequest(request: string, callback: function) 245 | 246 | 247 | > 248 | 249 | > Registers a handler for a specific RPC Request. 250 | > 251 | > Requests must be typed before you declare a handler for them, or TypeScript will complain. 252 | > 253 | > The difference between an RPC Notification and an RPC Request, is that requests block neovim 254 | > until a response is returned. Notifications are non-blocking. 255 | > 256 | > ```typescript 257 | > import { attach, type BaseEvents, type EventsMap } from "bunvim"; 258 | > import { gracefulShutdown } from "./utils.ts"; 259 | > 260 | > // an interface to define your requests and their args 261 | > interface MyEvents extends BaseEvents { 262 | > notifications: EventsMap; // default type 263 | > requests: { 264 | > // declare custom request: "before_exit", 265 | > // that would be called with args: [buffer_name: string] 266 | > "before_exit": [buffer_name: string]; 267 | > }; 268 | > } 269 | > 270 | > // attach to neovim 271 | > const nvim = await attach({ ... }) 272 | > 273 | > // register a handler for the request "before_exit" 274 | > nvim.onRequest("before_exit", async ([buffer_name]) => { 275 | > // "buffer_name" is of type "string" as specified above 276 | > 277 | > // CAUTION: 278 | > // it's up to you to make sure the handler receives the correct args, 279 | > // bunvim doesn't do any validations 280 | > 281 | > // this should actually never get called, 282 | > // because this handler gets overwritten below 283 | > console.log("buffer_name: ", buffer_name); 284 | > 285 | > // we must return something to unblock neovim 286 | > return null; 287 | > }); 288 | > 289 | > // only one handler per request may be registered. 290 | > // if you call `nvim.onRequest` for an already registered handler, 291 | > // the older handler is replaced with the new one. 292 | > nvim.onRequest("before_exit", async ([buffer_name]) => { 293 | > gracefulShutdown(buffer_name); 294 | > return null; 295 | > }); 296 | > 297 | > const channelId = nvim.channelId; 298 | > 299 | > // create autocommand to call our function via `vim.rpcrequest` 300 | > // whenever neovim is about to close 301 | > await nvim.call("nvim_create_autocmd", [ 302 | > ["VimLeavePre"], 303 | > { 304 | > desc: "RPC Request before exit", 305 | > command: `lua 306 | > local buffer_name = vim.api.nvim_get_current_buf() 307 | > vim.rpcrequest(${channelId}, "before_exit", buffer_name)`, 308 | > }, 309 | > ]); 310 | > ``` 311 | 312 | > --- 313 | 314 |
315 | 316 |
317 | 318 | nvim.detach() 319 | 320 | 321 | > 322 | 323 | > Closes connection with neovim. 324 | > 325 | > ```ts 326 | > nvim.detach(); 327 | > ``` 328 | 329 | > --- 330 | 331 |
332 | 333 |
334 | 335 | nvim.logger 336 | 337 | 338 | > 339 | 340 | > Instance of [winston logger](https://github.com/winstonjs/winston). 341 | > May be `undefined` if logging was not enabled. 342 | > 343 | > Used to log data to console and/or file. Does not log/print messages to Neovim. 344 | > 345 | > [See Logging](#%EF%B8%8F-logging). 346 | > 347 | > ```typescript 348 | > // log functions sorted from highest to lowest priority: 349 | > 350 | > nvim.logger?.error("error message"); 351 | > nvim.logger?.warn("warn message"); 352 | > nvim.logger?.info("info message"); 353 | > nvim.logger?.http("http message"); 354 | > nvim.logger?.verbose("verbose message"); 355 | > nvim.logger?.debug("debug message"); 356 | > nvim.logger?.silly("silly message"); 357 | > ``` 358 | 359 | > --- 360 | 361 |
362 | 363 | ## 🖨️ Logging 364 | 365 | To enable logging to **Console** and/or **File**, a `logging.level` 366 | must be specified when calling the `attach` method. 367 | 368 | ```typescript 369 | const nvim = await attach({ 370 | socket: SOCKET, 371 | client: { name: "my-plugin-name" }, 372 | logging: { 373 | level: "debug", // <= LOG LEVEL 374 | }, 375 | }); 376 | 377 | nvim.logger?.info("hello world"); 378 | ``` 379 | 380 | **Bunvim** internally logs with `logger.debug()` and `logger.error()`. 381 | Set `logging.level` higher than `debug` to not display bunvim's internal logs when 382 | printing logs for your plugin. 383 | 384 | Levels from highest to lowest priority: 385 | 386 | 1. error 387 | 2. warn 388 | 3. info 389 | 4. http 390 | 5. verbose 391 | 6. debug 392 | 7. silly 393 | 394 | ### Console 395 | 396 | After setting a `logging.level`, you can see your logs live with the command `bunvim logs` and 397 | specifying the `client.name` you defined in your `attach`. 398 | 399 | In a terminal, run the command: 400 | 401 | ```sh 402 | # this process will listen for logs and print them to the console 403 | # Ctrl-C to stop process 404 | 405 | bunx bunvim logs my-plugin-name 406 | ``` 407 | 408 | For more information about the CLI tool, run the command: 409 | 410 | ```sh 411 | bunx bunvim --help 412 | ``` 413 | 414 | ### File 415 | 416 | You can also write your logs to a `file` by specifying a path when calling the `attach` method: 417 | 418 | ```typescript 419 | const nvim = await attach({ 420 | socket: SOCKET, 421 | client: { name: "my-plugin-name" }, 422 | logging: { 423 | level: "debug", // <= LOG LEVEL 424 | file: : "~/my-plugin-name.log" // <= PATH TO LOG FILE 425 | }, 426 | }); 427 | ``` 428 | 429 | ### Neovim 430 | 431 | If you want to log/print a message to the user in Neovim, use: 432 | 433 | ```typescript 434 | import { NVIM_LOG_LEVELS } from "bunvim"; 435 | 436 | await nvim.call("nvim_notify", ["some message", NVIM_LOG_LEVELS.INFO, {}]); 437 | ``` 438 | -------------------------------------------------------------------------------- /src/neovim-api.types.ts: -------------------------------------------------------------------------------- 1 | /* eslint @typescript-eslint/no-invalid-void-type: 0 */ 2 | 3 | import { type EventsMap } from "./types.ts"; 4 | 5 | type ui_options = [ 6 | "rgb", 7 | "ext_cmdline", 8 | "ext_popupmenu", 9 | "ext_tabline", 10 | "ext_wildmenu", 11 | "ext_messages", 12 | "ext_linegrid", 13 | "ext_multigrid", 14 | "ext_hlstate", 15 | "ext_termcolors", 16 | ]; 17 | 18 | export type NeovimApi< 19 | Notifications extends EventsMap = EventsMap, 20 | Requests extends EventsMap = EventsMap, 21 | > = { 22 | functions: { 23 | nvim_get_autocmds: { 24 | parameters: [opts: Record]; 25 | return_type: unknown[]; 26 | }; 27 | nvim_create_autocmd: { 28 | parameters: [event: unknown, opts: Record]; 29 | return_type: number; 30 | }; 31 | nvim_del_autocmd: { 32 | parameters: [id: number]; 33 | return_type: void; 34 | }; 35 | nvim_clear_autocmds: { 36 | parameters: [opts: Record]; 37 | return_type: void; 38 | }; 39 | nvim_create_augroup: { 40 | parameters: [name: string, opts: Record]; 41 | return_type: number; 42 | }; 43 | nvim_del_augroup_by_id: { 44 | parameters: [id: number]; 45 | return_type: void; 46 | }; 47 | nvim_del_augroup_by_name: { 48 | parameters: [name: string]; 49 | return_type: void; 50 | }; 51 | nvim_exec_autocmds: { 52 | parameters: [event: unknown, opts: Record]; 53 | return_type: void; 54 | }; 55 | nvim_buf_line_count: { 56 | parameters: [buffer: number]; 57 | return_type: number; 58 | }; 59 | nvim_buf_attach: { 60 | parameters: [buffer: number, send_buffer: boolean, opts: Record]; 61 | return_type: boolean; 62 | }; 63 | nvim_buf_detach: { 64 | parameters: [buffer: number]; 65 | return_type: boolean; 66 | }; 67 | nvim_buf_get_lines: { 68 | parameters: [buffer: number, start: number, end: number, strict_indexing: boolean]; 69 | return_type: string[]; 70 | }; 71 | nvim_buf_set_lines: { 72 | parameters: [ 73 | buffer: number, 74 | start: number, 75 | end: number, 76 | strict_indexing: boolean, 77 | replacement: string[], 78 | ]; 79 | return_type: void; 80 | }; 81 | nvim_buf_set_text: { 82 | parameters: [ 83 | buffer: number, 84 | start_row: number, 85 | start_col: number, 86 | end_row: number, 87 | end_col: number, 88 | replacement: string[], 89 | ]; 90 | return_type: void; 91 | }; 92 | nvim_buf_get_text: { 93 | parameters: [ 94 | buffer: number, 95 | start_row: number, 96 | start_col: number, 97 | end_row: number, 98 | end_col: number, 99 | opts: Record, 100 | ]; 101 | return_type: string[]; 102 | }; 103 | nvim_buf_get_offset: { 104 | parameters: [buffer: number, index: number]; 105 | return_type: number; 106 | }; 107 | nvim_buf_get_var: { 108 | parameters: [buffer: number, name: string]; 109 | return_type: unknown; 110 | }; 111 | nvim_buf_get_changedtick: { 112 | parameters: [buffer: number]; 113 | return_type: number; 114 | }; 115 | nvim_buf_get_keymap: { 116 | parameters: [buffer: number, mode: string]; 117 | return_type: Record[]; 118 | }; 119 | nvim_buf_set_keymap: { 120 | parameters: [ 121 | buffer: number, 122 | mode: string, 123 | lhs: string, 124 | rhs: string, 125 | opts: Record, 126 | ]; 127 | return_type: void; 128 | }; 129 | nvim_buf_del_keymap: { 130 | parameters: [buffer: number, mode: string, lhs: string]; 131 | return_type: void; 132 | }; 133 | nvim_buf_set_var: { 134 | parameters: [buffer: number, name: string, value: unknown]; 135 | return_type: void; 136 | }; 137 | nvim_buf_del_var: { 138 | parameters: [buffer: number, name: string]; 139 | return_type: void; 140 | }; 141 | nvim_buf_get_name: { 142 | parameters: [buffer: number]; 143 | return_type: string; 144 | }; 145 | nvim_buf_set_name: { 146 | parameters: [buffer: number, name: string]; 147 | return_type: void; 148 | }; 149 | nvim_buf_is_loaded: { 150 | parameters: [buffer: number]; 151 | return_type: boolean; 152 | }; 153 | nvim_buf_delete: { 154 | parameters: [buffer: number, opts: Record]; 155 | return_type: void; 156 | }; 157 | nvim_buf_is_valid: { 158 | parameters: [buffer: number]; 159 | return_type: boolean; 160 | }; 161 | nvim_buf_del_mark: { 162 | parameters: [buffer: number, name: string]; 163 | return_type: boolean; 164 | }; 165 | nvim_buf_set_mark: { 166 | parameters: [ 167 | buffer: number, 168 | name: string, 169 | line: number, 170 | col: number, 171 | opts: Record, 172 | ]; 173 | return_type: boolean; 174 | }; 175 | nvim_buf_get_mark: { 176 | parameters: [buffer: number, name: string]; 177 | return_type: [number, number]; 178 | }; 179 | nvim_buf_call: { 180 | parameters: [buffer: number, fun: unknown]; 181 | return_type: unknown; 182 | }; 183 | nvim_parse_cmd: { 184 | parameters: [str: string, opts: Record]; 185 | return_type: Record; 186 | }; 187 | nvim_cmd: { 188 | parameters: [cmd: Record, opts: Record]; 189 | return_type: string; 190 | }; 191 | nvim_create_user_command: { 192 | parameters: [name: string, command: unknown, opts: Record]; 193 | return_type: void; 194 | }; 195 | nvim_del_user_command: { 196 | parameters: [name: string]; 197 | return_type: void; 198 | }; 199 | nvim_buf_create_user_command: { 200 | parameters: [ 201 | buffer: number, 202 | name: string, 203 | command: unknown, 204 | opts: Record, 205 | ]; 206 | return_type: void; 207 | }; 208 | nvim_buf_del_user_command: { 209 | parameters: [buffer: number, name: string]; 210 | return_type: void; 211 | }; 212 | nvim_get_commands: { 213 | parameters: [opts: Record]; 214 | return_type: Record; 215 | }; 216 | nvim_buf_get_commands: { 217 | parameters: [buffer: number, opts: Record]; 218 | return_type: Record; 219 | }; 220 | nvim_get_option_info: { 221 | parameters: [name: string]; 222 | return_type: Record; 223 | }; 224 | nvim_create_namespace: { 225 | parameters: [name: string]; 226 | return_type: number; 227 | }; 228 | nvim_get_namespaces: { 229 | parameters: []; 230 | return_type: Record; 231 | }; 232 | nvim_buf_get_extmark_by_id: { 233 | parameters: [buffer: number, ns_id: number, id: number, opts: Record]; 234 | return_type: number[]; 235 | }; 236 | nvim_buf_get_extmarks: { 237 | parameters: [ 238 | buffer: number, 239 | ns_id: number, 240 | start: unknown, 241 | end: unknown, 242 | opts: Record, 243 | ]; 244 | return_type: unknown[]; 245 | }; 246 | nvim_buf_set_extmark: { 247 | parameters: [ 248 | buffer: number, 249 | ns_id: number, 250 | line: number, 251 | col: number, 252 | opts: Record, 253 | ]; 254 | return_type: number; 255 | }; 256 | nvim_buf_del_extmark: { 257 | parameters: [buffer: number, ns_id: number, id: number]; 258 | return_type: boolean; 259 | }; 260 | nvim_buf_add_highlight: { 261 | parameters: [ 262 | buffer: number, 263 | ns_id: number, 264 | hl_group: string, 265 | line: number, 266 | col_start: number, 267 | col_end: number, 268 | ]; 269 | return_type: number; 270 | }; 271 | nvim_buf_clear_namespace: { 272 | parameters: [buffer: number, ns_id: number, line_start: number, line_end: number]; 273 | return_type: void; 274 | }; 275 | nvim_set_decoration_provider: { 276 | parameters: [ns_id: number, opts: Record]; 277 | return_type: void; 278 | }; 279 | nvim_get_option_value: { 280 | parameters: [name: string, opts: Record]; 281 | return_type: unknown; 282 | }; 283 | nvim_set_option_value: { 284 | parameters: [name: string, value: unknown, opts: Record]; 285 | return_type: void; 286 | }; 287 | nvim_get_all_options_info: { 288 | parameters: []; 289 | return_type: Record; 290 | }; 291 | nvim_get_option_info2: { 292 | parameters: [name: string, opts: Record]; 293 | return_type: Record; 294 | }; 295 | nvim_set_option: { 296 | parameters: [name: string, value: unknown]; 297 | return_type: void; 298 | }; 299 | nvim_get_option: { 300 | parameters: [name: string]; 301 | return_type: unknown; 302 | }; 303 | nvim_buf_get_option: { 304 | parameters: [buffer: number, name: string]; 305 | return_type: unknown; 306 | }; 307 | nvim_buf_set_option: { 308 | parameters: [buffer: number, name: string, value: unknown]; 309 | return_type: void; 310 | }; 311 | nvim_win_get_option: { 312 | parameters: [window: number, name: string]; 313 | return_type: unknown; 314 | }; 315 | nvim_win_set_option: { 316 | parameters: [window: number, name: string, value: unknown]; 317 | return_type: void; 318 | }; 319 | nvim_tabpage_list_wins: { 320 | parameters: [tabpage: number]; 321 | return_type: number[]; 322 | }; 323 | nvim_tabpage_get_var: { 324 | parameters: [tabpage: number, name: string]; 325 | return_type: unknown; 326 | }; 327 | nvim_tabpage_set_var: { 328 | parameters: [tabpage: number, name: string, value: unknown]; 329 | return_type: void; 330 | }; 331 | nvim_tabpage_del_var: { 332 | parameters: [tabpage: number, name: string]; 333 | return_type: void; 334 | }; 335 | nvim_tabpage_get_win: { 336 | parameters: [tabpage: number]; 337 | return_type: number; 338 | }; 339 | nvim_tabpage_get_number: { 340 | parameters: [tabpage: number]; 341 | return_type: number; 342 | }; 343 | nvim_tabpage_is_valid: { 344 | parameters: [tabpage: number]; 345 | return_type: boolean; 346 | }; 347 | nvim_ui_attach: { 348 | parameters: [ 349 | width: number, 350 | height: number, 351 | options: Partial>, 352 | ]; 353 | return_type: void; 354 | }; 355 | nvim_ui_set_focus: { 356 | parameters: [gained: boolean]; 357 | return_type: void; 358 | }; 359 | nvim_ui_detach: { 360 | parameters: []; 361 | return_type: void; 362 | }; 363 | nvim_ui_try_resize: { 364 | parameters: [width: number, height: number]; 365 | return_type: void; 366 | }; 367 | nvim_ui_set_option: { 368 | parameters: [name: string, value: unknown]; 369 | return_type: void; 370 | }; 371 | nvim_ui_try_resize_grid: { 372 | parameters: [grid: number, width: number, height: number]; 373 | return_type: void; 374 | }; 375 | nvim_ui_pum_set_height: { 376 | parameters: [height: number]; 377 | return_type: void; 378 | }; 379 | nvim_ui_pum_set_bounds: { 380 | parameters: [width: number, height: number, row: number, col: number]; 381 | return_type: void; 382 | }; 383 | nvim_get_hl_id_by_name: { 384 | parameters: [name: string]; 385 | return_type: number; 386 | }; 387 | nvim_get_hl: { 388 | parameters: [ns_id: number, opts: Record]; 389 | return_type: Record; 390 | }; 391 | nvim_set_hl: { 392 | parameters: [ns_id: number, name: string, val: Record]; 393 | return_type: void; 394 | }; 395 | nvim_set_hl_ns: { 396 | parameters: [ns_id: number]; 397 | return_type: void; 398 | }; 399 | nvim_set_hl_ns_fast: { 400 | parameters: [ns_id: number]; 401 | return_type: void; 402 | }; 403 | nvim_feedkeys: { 404 | parameters: [keys: string, mode: string, escape_ks: boolean]; 405 | return_type: void; 406 | }; 407 | nvim_input: { 408 | parameters: [keys: string]; 409 | return_type: number; 410 | }; 411 | nvim_input_mouse: { 412 | parameters: [ 413 | button: string, 414 | action: string, 415 | modifier: string, 416 | grid: number, 417 | row: number, 418 | col: number, 419 | ]; 420 | return_type: void; 421 | }; 422 | nvim_replace_termcodes: { 423 | parameters: [str: string, from_part: boolean, do_lt: boolean, special: boolean]; 424 | return_type: string; 425 | }; 426 | nvim_exec_lua: { 427 | parameters: [code: string, args: unknown[]]; 428 | return_type: unknown; 429 | }; 430 | nvim_notify: { 431 | parameters: [msg: string, log_level: number, opts: Record]; 432 | return_type: unknown; 433 | }; 434 | nvim_strwidth: { 435 | parameters: [text: string]; 436 | return_type: number; 437 | }; 438 | nvim_list_runtime_paths: { 439 | parameters: []; 440 | return_type: string[]; 441 | }; 442 | nvim_get_runtime_file: { 443 | parameters: [name: string, all: boolean]; 444 | return_type: string[]; 445 | }; 446 | nvim_set_current_dir: { 447 | parameters: [dir: string]; 448 | return_type: void; 449 | }; 450 | nvim_get_current_line: { 451 | parameters: []; 452 | return_type: string; 453 | }; 454 | nvim_set_current_line: { 455 | parameters: [line: string]; 456 | return_type: void; 457 | }; 458 | nvim_del_current_line: { 459 | parameters: []; 460 | return_type: void; 461 | }; 462 | nvim_get_var: { 463 | parameters: [name: string]; 464 | return_type: unknown; 465 | }; 466 | nvim_set_var: { 467 | parameters: [name: string, value: unknown]; 468 | return_type: void; 469 | }; 470 | nvim_del_var: { 471 | parameters: [name: string]; 472 | return_type: void; 473 | }; 474 | nvim_get_vvar: { 475 | parameters: [name: string]; 476 | return_type: unknown; 477 | }; 478 | nvim_set_vvar: { 479 | parameters: [name: string, value: unknown]; 480 | return_type: void; 481 | }; 482 | nvim_echo: { 483 | parameters: [chunks: unknown[], history: boolean, opts: Record]; 484 | return_type: void; 485 | }; 486 | nvim_out_write: { 487 | parameters: [str: string]; 488 | return_type: void; 489 | }; 490 | nvim_err_write: { 491 | parameters: [str: string]; 492 | return_type: void; 493 | }; 494 | nvim_err_writeln: { 495 | parameters: [str: string]; 496 | return_type: void; 497 | }; 498 | nvim_list_bufs: { 499 | parameters: []; 500 | return_type: number[]; 501 | }; 502 | nvim_get_current_buf: { 503 | parameters: []; 504 | return_type: number; 505 | }; 506 | nvim_set_current_buf: { 507 | parameters: [buffer: number]; 508 | return_type: void; 509 | }; 510 | nvim_list_wins: { 511 | parameters: []; 512 | return_type: number[]; 513 | }; 514 | nvim_get_current_win: { 515 | parameters: []; 516 | return_type: number; 517 | }; 518 | nvim_set_current_win: { 519 | parameters: [window: number]; 520 | return_type: void; 521 | }; 522 | nvim_create_buf: { 523 | parameters: [listed: boolean, scratch: boolean]; 524 | return_type: number; 525 | }; 526 | nvim_open_term: { 527 | parameters: [buffer: number, opts: Record]; 528 | return_type: number; 529 | }; 530 | nvim_chan_send: { 531 | parameters: [chan: number, data: string]; 532 | return_type: void; 533 | }; 534 | nvim_list_tabpages: { 535 | parameters: []; 536 | return_type: number[]; 537 | }; 538 | nvim_get_current_tabpage: { 539 | parameters: []; 540 | return_type: number; 541 | }; 542 | nvim_set_current_tabpage: { 543 | parameters: [tabpage: number]; 544 | return_type: void; 545 | }; 546 | nvim_paste: { 547 | parameters: [data: string, crlf: boolean, phase: number]; 548 | return_type: boolean; 549 | }; 550 | nvim_put: { 551 | parameters: [lines: string[], type: string, after: boolean, follow: boolean]; 552 | return_type: void; 553 | }; 554 | nvim_subscribe: { 555 | parameters: [event: string]; 556 | return_type: void; 557 | }; 558 | nvim_unsubscribe: { 559 | parameters: [event: string]; 560 | return_type: void; 561 | }; 562 | nvim_get_color_by_name: { 563 | parameters: [name: string]; 564 | return_type: number; 565 | }; 566 | nvim_get_color_map: { 567 | parameters: []; 568 | return_type: Record; 569 | }; 570 | nvim_get_context: { 571 | parameters: [opts: Record]; 572 | return_type: Record; 573 | }; 574 | nvim_load_context: { 575 | parameters: [dict: Record]; 576 | return_type: unknown; 577 | }; 578 | nvim_get_mode: { 579 | parameters: []; 580 | return_type: Record; 581 | }; 582 | nvim_get_keymap: { 583 | parameters: [mode: string]; 584 | return_type: Record[]; 585 | }; 586 | nvim_set_keymap: { 587 | parameters: [mode: string, lhs: string, rhs: string, opts: Record]; 588 | return_type: void; 589 | }; 590 | nvim_del_keymap: { 591 | parameters: [mode: string, lhs: string]; 592 | return_type: void; 593 | }; 594 | nvim_get_api_info: { 595 | parameters: []; 596 | return_type: unknown[]; 597 | }; 598 | nvim_set_client_info: { 599 | parameters: [ 600 | name: string, 601 | version: Record, 602 | type: string, 603 | methods: Record, 604 | attributes: Record, 605 | ]; 606 | return_type: void; 607 | }; 608 | nvim_get_chan_info: { 609 | parameters: [chan: number]; 610 | return_type: Record; 611 | }; 612 | nvim_list_chans: { 613 | parameters: []; 614 | return_type: unknown[]; 615 | }; 616 | nvim_call_atomic: { 617 | parameters: [calls: unknown[]]; 618 | return_type: unknown[]; 619 | }; 620 | nvim_list_uis: { 621 | parameters: []; 622 | return_type: unknown[]; 623 | }; 624 | nvim_get_proc_children: { 625 | parameters: [pid: number]; 626 | return_type: unknown[]; 627 | }; 628 | nvim_get_proc: { 629 | parameters: [pid: number]; 630 | return_type: unknown; 631 | }; 632 | nvim_select_popupmenu_item: { 633 | parameters: [ 634 | item: number, 635 | insert: boolean, 636 | finish: boolean, 637 | opts: Record, 638 | ]; 639 | return_type: void; 640 | }; 641 | nvim_del_mark: { 642 | parameters: [name: string]; 643 | return_type: boolean; 644 | }; 645 | nvim_get_mark: { 646 | parameters: [name: string, opts: Record]; 647 | return_type: unknown[]; 648 | }; 649 | nvim_eval_statusline: { 650 | parameters: [str: string, opts: Record]; 651 | return_type: Record; 652 | }; 653 | nvim_exec2: { 654 | parameters: [src: string, opts: Record]; 655 | return_type: Record; 656 | }; 657 | nvim_command: { 658 | parameters: [command: string]; 659 | return_type: void; 660 | }; 661 | nvim_eval: { 662 | parameters: [expr: string]; 663 | return_type: unknown; 664 | }; 665 | nvim_call_function: { 666 | parameters: [fn: string, args: unknown[]]; 667 | return_type: unknown; 668 | }; 669 | nvim_call_dict_function: { 670 | parameters: [dict: unknown, fn: string, args: unknown[]]; 671 | return_type: unknown; 672 | }; 673 | nvim_parse_expression: { 674 | parameters: [expr: string, flags: string, highlight: boolean]; 675 | return_type: Record; 676 | }; 677 | nvim_open_win: { 678 | parameters: [buffer: number, enter: boolean, config: Record]; 679 | return_type: number; 680 | }; 681 | nvim_win_set_config: { 682 | parameters: [window: number, config: Record]; 683 | return_type: void; 684 | }; 685 | nvim_win_get_config: { 686 | parameters: [window: number]; 687 | return_type: Record; 688 | }; 689 | nvim_win_get_buf: { 690 | parameters: [window: number]; 691 | return_type: number; 692 | }; 693 | nvim_win_set_buf: { 694 | parameters: [window: number, buffer: number]; 695 | return_type: void; 696 | }; 697 | nvim_win_get_cursor: { 698 | parameters: [window: number]; 699 | return_type: [number, number]; 700 | }; 701 | nvim_win_set_cursor: { 702 | parameters: [window: number, pos: [number, number]]; 703 | return_type: void; 704 | }; 705 | nvim_win_get_height: { 706 | parameters: [window: number]; 707 | return_type: number; 708 | }; 709 | nvim_win_set_height: { 710 | parameters: [window: number, height: number]; 711 | return_type: void; 712 | }; 713 | nvim_win_get_width: { 714 | parameters: [window: number]; 715 | return_type: number; 716 | }; 717 | nvim_win_set_width: { 718 | parameters: [window: number, width: number]; 719 | return_type: void; 720 | }; 721 | nvim_win_get_var: { 722 | parameters: [window: number, name: string]; 723 | return_type: unknown; 724 | }; 725 | nvim_win_set_var: { 726 | parameters: [window: number, name: string, value: unknown]; 727 | return_type: void; 728 | }; 729 | nvim_win_del_var: { 730 | parameters: [window: number, name: string]; 731 | return_type: void; 732 | }; 733 | nvim_win_get_position: { 734 | parameters: [window: number]; 735 | return_type: [number, number]; 736 | }; 737 | nvim_win_get_tabpage: { 738 | parameters: [window: number]; 739 | return_type: number; 740 | }; 741 | nvim_win_get_number: { 742 | parameters: [window: number]; 743 | return_type: number; 744 | }; 745 | nvim_win_is_valid: { 746 | parameters: [window: number]; 747 | return_type: boolean; 748 | }; 749 | nvim_win_hide: { 750 | parameters: [window: number]; 751 | return_type: void; 752 | }; 753 | nvim_win_close: { 754 | parameters: [window: number, force: boolean]; 755 | return_type: void; 756 | }; 757 | nvim_win_call: { 758 | parameters: [window: number, fun: unknown]; 759 | return_type: unknown; 760 | }; 761 | nvim_win_set_hl_ns: { 762 | parameters: [window: number, ns_id: number]; 763 | return_type: void; 764 | }; 765 | }; 766 | 767 | ui_events: { 768 | mode_info_set: { 769 | parameters: [enabled: boolean, cursor_styles: unknown[]]; 770 | }; 771 | update_menu: { 772 | parameters: []; 773 | }; 774 | busy_start: { 775 | parameters: []; 776 | }; 777 | busy_stop: { 778 | parameters: []; 779 | }; 780 | mouse_on: { 781 | parameters: []; 782 | }; 783 | mouse_off: { 784 | parameters: []; 785 | }; 786 | mode_change: { 787 | parameters: [mode: string, mode_idx: number]; 788 | }; 789 | bell: { 790 | parameters: []; 791 | }; 792 | visual_bell: { 793 | parameters: []; 794 | }; 795 | flush: { 796 | parameters: []; 797 | }; 798 | suspend: { 799 | parameters: []; 800 | }; 801 | set_title: { 802 | parameters: [title: string]; 803 | }; 804 | set_icon: { 805 | parameters: [icon: string]; 806 | }; 807 | screenshot: { 808 | parameters: [path: string]; 809 | }; 810 | option_set: { 811 | parameters: [name: string, value: unknown]; 812 | }; 813 | update_fg: { 814 | parameters: [fg: number]; 815 | }; 816 | update_bg: { 817 | parameters: [bg: number]; 818 | }; 819 | update_sp: { 820 | parameters: [sp: number]; 821 | }; 822 | resize: { 823 | parameters: [width: number, height: number]; 824 | }; 825 | clear: { 826 | parameters: []; 827 | }; 828 | eol_clear: { 829 | parameters: []; 830 | }; 831 | cursor_goto: { 832 | parameters: [row: number, col: number]; 833 | }; 834 | highlight_set: { 835 | parameters: [attrs: Record]; 836 | }; 837 | put: { 838 | parameters: [str: string]; 839 | }; 840 | set_scroll_region: { 841 | parameters: [top: number, bot: number, left: number, right: number]; 842 | }; 843 | scroll: { 844 | parameters: [count: number]; 845 | }; 846 | default_colors_set: { 847 | parameters: [ 848 | rgb_fg: number, 849 | rgb_bg: number, 850 | rgb_sp: number, 851 | cterm_fg: number, 852 | cterm_bg: number, 853 | ]; 854 | }; 855 | hl_attr_define: { 856 | parameters: [ 857 | id: number, 858 | rgb_attrs: Record, 859 | cterm_attrs: Record, 860 | info: unknown[], 861 | ]; 862 | }; 863 | hl_group_set: { 864 | parameters: [name: string, id: number]; 865 | }; 866 | grid_resize: { 867 | parameters: [grid: number, width: number, height: number]; 868 | }; 869 | grid_clear: { 870 | parameters: [grid: number]; 871 | }; 872 | grid_cursor_goto: { 873 | parameters: [grid: number, row: number, col: number]; 874 | }; 875 | grid_line: { 876 | parameters: [ 877 | grid: number, 878 | row: number, 879 | col_start: number, 880 | data: unknown[], 881 | wrap: boolean, 882 | ]; 883 | }; 884 | grid_scroll: { 885 | parameters: [ 886 | grid: number, 887 | top: number, 888 | bot: number, 889 | left: number, 890 | right: number, 891 | rows: number, 892 | cols: number, 893 | ]; 894 | }; 895 | grid_destroy: { 896 | parameters: [grid: number]; 897 | }; 898 | win_pos: { 899 | parameters: [ 900 | grid: number, 901 | win: number, 902 | startrow: number, 903 | startcol: number, 904 | width: number, 905 | height: number, 906 | ]; 907 | }; 908 | win_float_pos: { 909 | parameters: [ 910 | grid: number, 911 | win: number, 912 | anchor: string, 913 | anchor_grid: number, 914 | anchor_row: number, 915 | anchor_col: number, 916 | focusable: boolean, 917 | zindex: number, 918 | ]; 919 | }; 920 | win_external_pos: { 921 | parameters: [grid: number, win: number]; 922 | }; 923 | win_hide: { 924 | parameters: [grid: number]; 925 | }; 926 | win_close: { 927 | parameters: [grid: number]; 928 | }; 929 | msg_set_pos: { 930 | parameters: [grid: number, row: number, scrolled: boolean, sep_char: string]; 931 | }; 932 | win_viewport: { 933 | parameters: [ 934 | grid: number, 935 | win: number, 936 | topline: number, 937 | botline: number, 938 | curline: number, 939 | curcol: number, 940 | line_count: number, 941 | scroll_delta: number, 942 | ]; 943 | }; 944 | win_extmark: { 945 | parameters: [ 946 | grid: number, 947 | win: number, 948 | ns_id: number, 949 | mark_id: number, 950 | row: number, 951 | col: number, 952 | ]; 953 | }; 954 | popupmenu_show: { 955 | parameters: [ 956 | items: unknown[], 957 | selected: number, 958 | row: number, 959 | col: number, 960 | grid: number, 961 | ]; 962 | }; 963 | popupmenu_hide: { 964 | parameters: []; 965 | }; 966 | popupmenu_select: { 967 | parameters: [selected: number]; 968 | }; 969 | tabline_update: { 970 | parameters: [ 971 | current: number, 972 | tabs: unknown[], 973 | current_buffer: number, 974 | buffers: unknown[], 975 | ]; 976 | }; 977 | cmdline_show: { 978 | parameters: [ 979 | content: unknown[], 980 | pos: number, 981 | firstc: string, 982 | prompt: string, 983 | indent: number, 984 | level: number, 985 | ]; 986 | }; 987 | cmdline_pos: { 988 | parameters: [pos: number, level: number]; 989 | }; 990 | cmdline_special_char: { 991 | parameters: [c: string, shift: boolean, level: number]; 992 | }; 993 | cmdline_hide: { 994 | parameters: [level: number]; 995 | }; 996 | cmdline_block_show: { 997 | parameters: [lines: unknown[]]; 998 | }; 999 | cmdline_block_append: { 1000 | parameters: [lines: unknown[]]; 1001 | }; 1002 | cmdline_block_hide: { 1003 | parameters: []; 1004 | }; 1005 | wildmenu_show: { 1006 | parameters: [items: unknown[]]; 1007 | }; 1008 | wildmenu_select: { 1009 | parameters: [selected: number]; 1010 | }; 1011 | wildmenu_hide: { 1012 | parameters: []; 1013 | }; 1014 | msg_show: { 1015 | parameters: [kind: string, content: unknown[], replace_last: boolean]; 1016 | }; 1017 | msg_clear: { 1018 | parameters: []; 1019 | }; 1020 | msg_showcmd: { 1021 | parameters: [content: unknown[]]; 1022 | }; 1023 | msg_showmode: { 1024 | parameters: [content: unknown[]]; 1025 | }; 1026 | msg_ruler: { 1027 | parameters: [content: unknown[]]; 1028 | }; 1029 | msg_history_show: { 1030 | parameters: [entries: unknown[]]; 1031 | }; 1032 | msg_history_clear: { 1033 | parameters: []; 1034 | }; 1035 | }; 1036 | 1037 | error_types: { 1038 | Exception: { id: 0 }; 1039 | Validation: { id: 1 }; 1040 | }; 1041 | 1042 | types: { 1043 | Buffer: { id: 0; prefix: "nvim_buf_" }; 1044 | Window: { id: 1; prefix: "nvim_win_" }; 1045 | Tabpage: { id: 2; prefix: "nvim_tabpage_" }; 1046 | }; 1047 | 1048 | notifications: Notifications; 1049 | 1050 | requests: Requests; 1051 | }; 1052 | --------------------------------------------------------------------------------