UNPKG

@chemzqm/neovim

Version:

NodeJS client API for vim9 and neovim

193 lines (192 loc) 6.92 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NvimTransport = void 0; const msgpack = __importStar(require("@chemzqm/msgpack-lite")); const types_1 = require("../api/types"); const buffered_1 = __importDefault(require("../utils/buffered")); const base_1 = __importDefault(require("./base")); class NvimTransport extends base_1.default { constructor(logger) { super(logger, false); this.pending = new Map(); this.nextRequestId = 1; this.attached = false; const codec = this.setupCodec(); this.encodeStream = msgpack.createEncodeStream({ codec }); this.decodeStream = msgpack.createDecodeStream({ codec }); this.decodeStream.on('data', (msg) => { this.parseMessage(msg); }); this.decodeStream.on('end', () => { this.detach(); this.emit('detach'); }); } parseMessage(msg) { const msgType = msg[0]; this.debugMessage(msg); if (msgType === 0) { // request // - msg[1]: id // - msg[2]: method name // - msg[3]: arguments let method = msg[2].toString(); this.emit('request', method, msg[3], this.createResponse(method, msg[1])); } else if (msgType === 1) { // response to a previous request: // - msg[1]: the id // - msg[2]: error(if any) // - msg[3]: result(if not errored) const id = msg[1]; const handler = this.pending.get(id); if (handler) { this.pending.delete(id); let err = msg[2]; if (err && err.length != 2) { err = [0, err.toString()]; } handler(err, msg[3]); } } else if (msgType === 2) { // notification/event // - msg[1]: event name // - msg[2]: arguments this.emit('notification', msg[1].toString(), msg[2]); } else { // tslint:disable-next-line: no-console console.error(`Invalid message type ${msgType}`); } } setupCodec() { const codec = msgpack.createCodec(); types_1.Metadata.forEach(({ constructor }, id) => { codec.addExtPacker(id, constructor, (obj) => msgpack.encode(obj.data)); codec.addExtUnpacker(id, data => new constructor({ client: this.client, data: msgpack.decode(data), })); }); this.codec = codec; return this.codec; } attach(writer, reader, client) { this.encodeStream = this.encodeStream.pipe(writer); const buffered = new buffered_1.default(); reader.pipe(buffered).pipe(this.decodeStream); this.writer = writer; this.reader = reader; this.client = client; this.attached = true; } detach() { if (!this.attached) return; this.attached = false; this.encodeStream.unpipe(this.writer); this.reader.unpipe(this.decodeStream); for (let handler of this.pending.values()) { handler([0, 'transport disconnected']); } this.pending.clear(); } request(method, args, cb) { if (!this.attached) return cb([0, 'transport disconnected']); let id = this.nextRequestId; this.nextRequestId = this.nextRequestId + 1; let startTs = Date.now(); this.debug('request to nvim:', id, method, args); this.encodeStream.write(msgpack.encode([0, id, method, args], { codec: this.codec, })); this.pending.set(id, (err, res) => { this.debug('response of nvim:', id, Date.now() - startTs, res, err); cb(err, res); }); } notify(method, args) { if (!this.attached) return; if (this.pauseLevel != 0) { let arr = this.paused.get(this.pauseLevel); if (arr) { arr.push([method, args]); return; } } this.debug('nvim notification:', method, args); this.encodeStream.write(msgpack.encode([2, method, args], { codec: this.codec, })); } send(arr) { this.encodeStream.write(msgpack.encode(arr, { codec: this.codec, })); } vimCommand(command, ..._args) { throw new Error(`Command "${command}" not exists on nvim`); } vimRequest(command, _args) { throw new Error(`Command "${command}" not exists on nvim`); } createResponse(_method, requestId) { let { encodeStream } = this; let startTs = Date.now(); let called = false; return { send: (resp, isError) => { if (called || !this.attached) return; this.debug('response of client:', requestId, `${Date.now() - startTs}ms`, resp, isError == true); called = true; encodeStream.write(msgpack.encode([ 1, requestId, isError ? resp : null, !isError ? resp : null, ])); } }; } } exports.NvimTransport = NvimTransport;