@chemzqm/neovim
Version:
NodeJS client API for vim9 and neovim
193 lines (192 loc) • 6.92 kB
JavaScript
"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;