stmux
Version:
Simple Terminal Multiplexing for Node Environments
176 lines (161 loc) • 6.68 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _nodeNotifier = _interopRequireDefault(require("node-notifier"));
var _stripAnsi = _interopRequireDefault(require("strip-ansi"));
/*
** stmux -- Simple Terminal Multiplexing for Node Environments
** Copyright (c) 2017-2024 Dr. Ralf S. Engelschall <rse@engelschall.com>
**
** Permission is hereby granted, free of charge, to any person obtaining
** a copy of this software and associated documentation files (the
** "Software"), to deal in the Software without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
class stmuxErrors {
handleErrors() {
/* determine error patterns */
const parseErrorPatterns = patterns => {
const result = [];
while (patterns) {
let [, pattern, rest] = patterns.match(/^((?:\\,|.)+?)(?:,(.+))?$/);
const m = pattern.match(/^!(.+)$/);
let negate = false;
if (m) {
negate = true;
pattern = m[1];
}
result.push({
negate,
regexp: new RegExp(pattern)
});
patterns = rest;
}
return result;
};
const globalErrorPatterns = parseErrorPatterns(this.argv.error);
this.terms.forEach(term => {
const patterns = term.node.get("error");
term.stmuxErrorPatterns = patterns ? parseErrorPatterns(patterns) : [];
term.stmuxError = false;
});
/* handle error detection */
let notifyLocked = false;
const notifyStateOld = [];
const notifyStateNew = [];
this.terms.forEach(term => {
notifyStateOld[term.stmuxNumber - 1] = "";
notifyStateNew[term.stmuxNumber - 1] = "";
});
setInterval(() => {
let dirty = false;
const notify = [];
this.terms.forEach(term => {
/* act only if an update exists */
if (!term.stmuxUpdate) return;
term.stmuxUpdate = false;
/* short-circuit processing */
if (globalErrorPatterns.length === 0 && term.stmuxErrorPatterns.length === 0) return;
/* take screenshot */
let screenshot = term.screenshot();
screenshot = (0, _stripAnsi.default)(screenshot);
const lines = screenshot.split(/\r?\n/);
/* match errors in screenshot */
const matches = (string, patterns) => {
for (let i = 0; i < patterns.length; i++) {
const matched = patterns[i].regexp.test(string);
if (!(matched && !patterns[i].negate || !matched && patterns[i].negate)) return false;
}
return true;
};
term.stmuxError = false;
for (let i = 0; i < lines.length; i++) {
if (globalErrorPatterns.length > 0 && matches(lines[i], globalErrorPatterns) || term.stmuxErrorPatterns.length > 0 && matches(lines[i], term.stmuxErrorPatterns)) {
term.stmuxError = true;
break;
}
}
/* record notification state */
if (term.stmuxError) {
notifyStateNew[term.stmuxNumber - 1] = screenshot;
notify.push(term);
} else notifyStateNew[term.stmuxNumber - 1] = "";
/* determine and record screen updates */
if (term.stmuxError && term.style.border.fg === "default") {
term.style.border.fg = "red";
dirty = true;
} else if (!term.stmuxError && term.style.border.fg === "red") {
term.style.border.fg = "default";
dirty = true;
}
if (term.stmuxError && term.style.focus.border.fg === "green") {
term.style.border.fg = "red";
dirty = true;
} else if (!term.stmuxError && term.style.focus.border.fg === "red") {
term.style.border.fg = "green";
dirty = true;
}
if (term.stmuxError && !term.stmuxTitle.match(/-\[ERROR\]/)) {
this.setTerminalTitle(term);
dirty = true;
} else if (!term.stmuxError && term.stmuxTitle.match(/-\[ERROR\]/)) {
this.setTerminalTitle(term);
dirty = true;
}
});
/* update screen */
if (dirty) this.screen.render();
/* raise notification */
if (this.argv.method !== "" && notify.length > 0 && !notifyLocked) {
const stateOld = notifyStateOld.reduce((a, v) => a + v, "");
const stateNew = notifyStateNew.reduce((a, v) => a + v, "");
if (stateOld !== stateNew) {
/* determine message */
const notifyMsg = `${this.my.name}: ERROR situation${notify.length > 1 ? "s" : ""} ` + `detected in terminal${notify.length > 1 ? "s" : ""} ` + notify.map(term => "#" + term.stmuxNumber).join(", ");
/* determine method(s) */
const methods = {};
this.argv.method.split(",").forEach(method => {
methods[method] = true;
});
/* send notification(s) */
if (methods.beep) this.screen.program.output.write("\x07");
if (methods.system) {
_nodeNotifier.default.notify({
title: `${this.my.name}: Detected new ERROR situation${notify.length > 1 ? "s" : ""}`,
message: notifyMsg,
wait: false
});
}
/* swap notification state */
this.terms.forEach(term => {
notifyStateOld[term.stmuxNumber - 1] = notifyStateNew[term.stmuxNumber - 1];
notifyStateNew[term.stmuxNumber - 1] = "";
});
/* lock notification for some time to not bug the user too much */
notifyLocked = true;
setTimeout(() => {
notifyLocked = false;
}, 5 * 1000);
}
}
}, 500);
}
}
exports.default = stmuxErrors;