UNPKG

@cocalc/project

Version:
275 lines 10.5 kB
"use strict"; /* * This file is part of CoCalc: Copyright © 2020 Sagemath, Inc. * License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details */ 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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.lean_server = exports.Lean = void 0; const underscore_1 = require("underscore"); const lean_client = __importStar(require("lean-client-js-node")); const awaiting_1 = require("awaiting"); const async_utils_1 = require("@cocalc/util/async-utils"); const misc_1 = require("@cocalc/util/misc"); const hof_1 = require("async-await-utils/hof"); const events_1 = require("events"); // do not try to sync with lean more frequently than this // unless it is completing quickly. const SYNC_INTERVAL = 6000; class Lean extends events_1.EventEmitter { constructor(client) { super(); this.paths = {}; this._state = { tasks: [], paths: {} }; this.running = {}; this.server = (0, hof_1.reuseInFlight)(this.server); this.client = client; this.dbg = this.client.dbg("LEAN SERVER"); this.running = {}; } close() { this.kill(); (0, misc_1.close)(this); } is_running(path) { return !!this.running[path] && now() - this.running[path] < SYNC_INTERVAL; } // nothing actually async in here... yet. async server() { if (this._server != undefined) { if (this._server.alive()) { return this._server; } // Kill cleans up any assumptions about stuff // being sync'd. this.kill(); // New server will now be created... below. } this._server = new lean_client.Server(new lean_client.ProcessTransport("lean", process.env.HOME ? process.env.HOME : ".", // satisfy typescript. ["-M 4096"])); this._server.error.on((err) => this.dbg("error:", err)); this._server.allMessages.on((allMessages) => { this.dbg("messages: ", allMessages); const new_messages = {}; for (const x of allMessages.msgs) { const path = x.file_name; delete x.file_name; if (new_messages[path] === undefined) { new_messages[path] = [x]; } else { new_messages[path].push(x); } } for (const path in this._state.paths) { this.dbg("messages for ", path, new_messages[path]); if (new_messages[path] === undefined) { new_messages[path] = []; } this.dbg("messages for ", path, new_messages[path], this._state.paths[path]); // length 0 is a special case needed when going from pos number of messages to none. if (new_messages[path].length === 0 || !(0, underscore_1.isEqual)(this._state.paths[path], new_messages[path])) { this.dbg("messages for ", path, "EMIT!"); this.emit("messages", path, new_messages[path]); this._state.paths[path] = new_messages[path]; } } }); this._server.tasks.on((currentTasks) => { const { tasks } = currentTasks; this.dbg("tasks: ", tasks); const running = {}; for (const task of tasks) { running[task.file_name] = true; } for (const path in running) { const v = []; for (const task of tasks) { if (task.file_name === path) { delete task.file_name; // no longer needed v.push(task); } } this.emit("tasks", path, v); } for (const path in this.running) { if (!running[path]) { this.dbg("server", path, " done; no longer running"); this.running[path] = 0; this.emit("tasks", path, []); if (this.paths[path].changed) { // file changed while lean was running -- so run lean again. this.dbg("server", path, " changed while running, so running again"); this.paths[path].on_change(); } } } }); this._server.connect(); return this._server; } // Start learn server parsing and reporting info about the given file. // It will get updated whenever the file change. async register(path) { this.dbg("register", path); if (this.paths[path] !== undefined) { this.dbg("register", path, "already registered"); return; } // get the syncstring and start updating based on content let syncstring = undefined; while (syncstring == null) { // todo change to be event driven! syncstring = this.client.syncdoc({ path }); if (syncstring == null) { await (0, awaiting_1.delay)(1000); } else if (syncstring.get_state() != "ready") { await (0, async_utils_1.once)(syncstring, "ready"); } } const on_change = async () => { this.dbg("sync", path); if (syncstring._closed) { this.dbg("sync", path, "closed"); return; } if (this.is_running(path)) { // already running, so do nothing - it will rerun again when done with current run. this.dbg("sync", path, "already running"); this.paths[path].changed = true; return; } const value = syncstring.to_str(); if (this.paths[path].last_value === value) { this.dbg("sync", path, "skipping sync since value did not change"); return; } if (value.trim() === "") { this.dbg("sync", path, "skipping sync document is empty (and LEAN behaves weird in this case)"); this.emit("sync", path, syncstring.hash_of_live_version()); return; } this.paths[path].last_value = value; this._state.paths[path] = []; this.running[path] = now(); this.paths[path].changed = false; this.dbg("sync", path, "causing server sync now"); await (await this.server()).sync(path, value); this.emit("sync", path, syncstring.hash_of_live_version()); }; this.paths[path] = { syncstring, on_change, }; syncstring.on("change", on_change); if (!syncstring._closed) { on_change(); } syncstring.on("closed", () => { this.unregister(path); }); } // Stop updating given file on changes. unregister(path) { this.dbg("unregister", path); if (!this.paths[path]) { // not watching it return; } const x = this.paths[path]; delete this.paths[path]; delete this.running[path]; x.syncstring.removeListener("change", x.on_change); x.syncstring.close(); } // Kill the lean server and unregister all paths. kill() { this.dbg("kill"); if (this._server != undefined) { for (const path in this.paths) { this.unregister(path); } this._server.dispose(); delete this._server; } } async restart() { this.dbg("restart"); if (this._server != undefined) { for (const path in this.paths) { this.unregister(path); } await this._server.restart(); } } async info(path, line, column) { this.dbg("info", path, line, column); if (!this.paths[path]) { this.register(path); await (0, awaiting_1.callback)((cb) => this.once(`sync-#{path}`, cb)); } return await (await this.server()).info(path, line, column); } async complete(path, line, column, skipCompletions) { this.dbg("complete", path, line, column); if (!this.paths[path]) { this.register(path); await (0, awaiting_1.callback)((cb) => this.once(`sync-#{path}`, cb)); } const resp = await (await this.server()).complete(path, line, column, skipCompletions); //this.dbg("complete response", path, line, column, resp); return resp; } async version() { return (await this.server()).getVersion(); } // Return state of parsing for everything that is currently registered. state() { return this._state; } messages(path) { const x = this._state.paths[path]; if (x !== undefined) { return x; } return []; } } exports.Lean = Lean; let singleton; // Return the singleton lean instance. The client is assumed to never change. function lean_server(client) { if (singleton === undefined) { singleton = new Lean(client); } return singleton; } exports.lean_server = lean_server; function now() { return new Date().valueOf(); } //# sourceMappingURL=lean.js.map