diff --git a/app.js b/app.js index 43859a3..587cc69 100644 --- a/app.js +++ b/app.js @@ -2,7 +2,6 @@ import fs from 'fs'; import readline from 'readline'; -import properLockFile from 'proper-lockfile'; import ansi from 'ansi-escapes'; import boring from 'boring'; import { inspect } from 'util'; @@ -15,8 +14,20 @@ import loadLanguages from './load-languages.js'; import Screen from './screen.js'; +const tabSpaces = 2; + +const argv = boring(); + +const filename = argv._[0]; +argv.stdout = argv.stdout || !!argv._[1] || !process.stdout.isTTY; + const stdin = process.stdin; -const stdout = process.stdout; +let stdout; +if(argv.stdout === true) { + stdout = process.stderr; +} else { + stdout = process.stdout; +} stdin.setRawMode(true); readline.emitKeypressEvents(stdin); const screen = new Screen({ @@ -24,13 +35,10 @@ const screen = new Screen({ log }); -const tabSpaces = 2; - -const argv = boring(); - -const filename = argv._[0]; - -if (!filename && !argv['debug-keycodes']) { +if ((!argv.stdout && !filename && !argv['debug-keycodes']) || (argv.stdout && !process.stderr.isTTY)) { + if(argv.stdout && !process.stderr.isTTY) { + process.stderr.write('Error: Unable to display editor UI over stderr. When piping or using --stdout, stderr must be a TTY.\n\n'); + } usage(); } @@ -42,8 +50,7 @@ fs.mkdirSync(stateFolder, { recursive: true }); const logFile = fs.createWriteStream(`${stateFolder}/log.txt`, 'utf8'); const clipboard = clipboardFactory({ - stateFolder, - lock + stateFolder }); const hintStack = [ @@ -95,12 +102,13 @@ stdin.on('keypress', (c, k) => { // ignore occasional undefined keys on mac return; } - if (k.shift) { - k.name = `shift-${k.name}`; - } - if (k.ctrl) { - k.name = `control-${k.name}`; - } + let prefix = ''; + if (k.ctrl) prefix += 'control-'; + if (k.meta) prefix += 'meta-'; + if (k.shift) prefix += 'shift-'; + + k.name = prefix + k.name; + if ((k.sequence.charCodeAt(0) === 27) && (k.sequence.charCodeAt(1) === 27)) { // readline isn't quite smart enough on its own to do the right thing if // ESC is followed quickly by an arrow key, but gives us enough information @@ -146,6 +154,9 @@ while (true) { } function shortFilename(prompt) { + if (!filename || (argv.stdout && !fs.existsSync(filename))) { + return ''; + } return filename.split('/').pop().substring(0, stdout.columns - (prompt || '').length - 5); } @@ -171,25 +182,22 @@ function log(...args) { } } -function lock(filename) { - return properLockFile(filename, { - retries: { - retries: 30, - factor: 1, - minTimeout: 1000, - maxTimeout: 1000 - }, - // Avoid chicken and egg problem when the file does not exist yet - realpath: false - }); -} - function loadFile() { - if (!fs.existsSync(filename)) { + let content; + if(argv.stdout) { + if(!!argv._[1]) { + content = argv._[1].split('\n').map(line => [...line]); + } + if(!filename && !content) { + return false; + } + } + if (!filename || !fs.existsSync(filename)) { return false; + } else { + // Emoji-safe split by character (split('') is not safe) + content = fs.readFileSync(filename, 'utf8').split('\n').map(line => [...line]); } - // Emoji-safe split by character (split('') is not safe) - const content = fs.readFileSync(filename, 'utf8').split('\n').map(line => [...line]); if (!content.length) { content.push([]); } @@ -202,6 +210,8 @@ function newFile() { } function saveFile() { + if(argv.stdout && (filename == null || !fs.existsSync(filename))) + return; fs.writeFileSync(filename, getText(editor.chars)); } @@ -210,6 +220,12 @@ function getText(chars) { } async function closeEditor() { + if(argv.stdout){ + stdout.write(ansi.clearScreen); + console.log(getText(editor.chars)); + process.exit(0); + } + const text = getText(editor.chars); if (text !== originalText) { if (await confirm('Save before exiting? [Y/n]', true)) { @@ -249,10 +265,13 @@ function usage() { function resize() { screen.resize(); - editor.resize(process.stdout.columns, process.stdout.rows - 3); + editor.resize(stdout.columns, stdout.rows - 3); } function guessLanguage(filename) { + if(!filename) { + return languages.default; + } const matches = filename.match(/\.([^\.]+)$/); if (matches) { const found = Object.values(languages).find(language => language.extensions.includes(matches[1])); @@ -265,12 +284,12 @@ function status(prompt) { const hints = hintStack[hintStack.length - 1]; const width = Math.max(...hints.map(s => s.length)) + 2; let col = 0; - let row = process.stdout.rows - 2; + let row = stdout.rows - 2; for (const hint of hints) { - if (col + width >= process.stdout.columns) { + if (col + width >= stdout.columns) { fillRest(); row++; - if (row >= process.stdout.rows) { + if (row >= stdout.rows) { break; } } @@ -279,7 +298,7 @@ function status(prompt) { } col += width; } - while (row < process.stdout.rows) { + while (row < stdout.rows) { for (let sx = col; (sx < screen.width); sx++) { screen.set(sx, row, ' '); } @@ -288,9 +307,9 @@ function status(prompt) { } const left = `${editor.row + 1} ${editor.col + 1} ${shortFilename()}`; const right = (prompt !== false) ? prompt : ''; - const s = left + ' '.repeat(process.stdout.columns - 1 - right.length - left.length) + right; + const s = left + ' '.repeat(stdout.columns - 1 - right.length - left.length) + right; for (let i = 0; (i < s.length); i++) { - screen.set(i, process.stdout.rows - 3, s.charAt(i)); + screen.set(i, stdout.rows - 3, s.charAt(i)); } function fillRest() { while (col < screen.width) { diff --git a/bin/tome.sh b/bin/tome.sh new file mode 100644 index 0000000..a1efd1d --- /dev/null +++ b/bin/tome.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env sh + +scriptroot=$(dirname "$(realpath "$0")") +real_tome="$scriptroot/tome.js" + +case " $* " in + *" --stdout "*) has_stdout=1 ;; + *) has_stdout=0 ;; +esac + +args= +for arg in "$@"; do + [ "$arg" = "--stdout" ] || args="$args \"$arg\"" +done + +eval "set -- $args" + +# Detect presence of --stdout among arguments and handle stdin accordingly +if [ -t 0 ]; then + if [ "$has_stdout" -eq 1 ]; then + exec "$real_tome" "$1" --stdout 2>/dev/tty + else + exec "$real_tome" "$1" + fi +else + input_data=$(cat) + if [ "$has_stdout" -eq 1 ]; then + exec "$real_tome" "$1" "$input_data" --stdout 2>/dev/tty < /dev/tty + else + exec "$real_tome" "$1" "$input_data" < /dev/tty + fi +fi + diff --git a/clipboard.js b/clipboard.js index 296dca9..9d30a79 100644 --- a/clipboard.js +++ b/clipboard.js @@ -1,10 +1,22 @@ "use strict"; import fs from 'fs'; +import properLockFile from 'proper-lockfile'; export default ({ stateFolder, - lock + lock = function lock(filename) { + return properLockFile(filename, { + retries: { + retries: 30, + factor: 1, + minTimeout: 1000, + maxTimeout: 1000 + }, + // Avoid chicken and egg problem when the file does not exist yet + realpath: false + }); + } }) => { const clipboardFile = `${stateFolder}/clipboard.json`; const clipboardLockFile = `${clipboardFile}.lock`; diff --git a/package-lock.json b/package-lock.json index eede83f..05c4490 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@boutell/tome", - "version": "0.4.1", + "version": "0.13.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@boutell/tome", - "version": "0.4.1", + "version": "0.13.1", "license": "GPLv3", "dependencies": { "ansi-escapes": "^6.2.0", diff --git a/package.json b/package.json index dbdbdfe..a866ca3 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "url": "https://github.com/boutell/tome/issues" }, "bin": { - "tome": "./bin/tome.js" + "tome": "./bin/tome.sh" }, "homepage": "https://github.com/boutell/tome#readme", "dependencies": { diff --git a/screen.js b/screen.js index 73623c0..7feeb2f 100644 --- a/screen.js +++ b/screen.js @@ -35,8 +35,8 @@ export default class Screen { } allocate() { const data = []; - this.width = process.stdout.columns; - this.height = process.stdout.rows; + this.width = this.stdout.columns; + this.height = this.stdout.rows; for (let row = 0; (row < this.height); row++) { const cells = []; for (let col = 0; (col < this.width); col++) {