GithubHelp home page GithubHelp logo

sindresorhus / log-update Goto Github PK

View Code? Open in Web Editor NEW
1.1K 11.0 39.0 92 KB

Log by overwriting the previous output in the terminal. Useful for rendering progress bars, animations, etc.

License: MIT License

JavaScript 94.43% TypeScript 5.57%

log-update's Introduction

log-update

Log by overwriting the previous output in the terminal.
Useful for rendering progress bars, animations, etc.

Install

npm install log-update

Usage

import logUpdate from 'log-update';

const frames = ['-', '\\', '|', '/'];
let index = 0;

setInterval(() => {
	const frame = frames[index = ++index % frames.length];

	logUpdate(
`
        ♥♥
   ${frame} unicorns ${frame}
        ♥♥
`
	);
}, 80);

API

logUpdate(text…)

Log to stdout.

logUpdate.clear()

Clear the logged output.

logUpdate.done()

Persist the logged output.

Useful if you want to start a new log session below the current one.

logUpdateStderr(text…)

Log to stderr.

logUpdateStderr.clear()

logUpdateStderr.done()

createLogUpdate(stream, options?)

Get a logUpdate method that logs to the specified stream.

options

Type: object

showCursor

Type: boolean
Default: false

Show the cursor. This can be useful when a CLI accepts input from a user.

import {createLogUpdate} from 'log-update';

// Write output but don't hide the cursor
const log = createLogUpdate(process.stdout, {
	showCursor: true
});

Examples

  • listr - Uses this module to render an interactive task list
  • ora - Uses this module to render awesome spinners
  • speed-test - Uses this module to render a spinner

log-update's People

Contributors

bendingbender avatar djmitche avatar etiktin avatar herecydev avatar imhoffd avatar jameskmonger avatar jamestalmage avatar kj800x avatar laurynaslubys avatar r4j4h avatar richienb avatar samverschueren avatar sapphi-red avatar sindresorhus avatar slashinfty avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

log-update's Issues

Skip render if output is unchanged

Similar to #31, but this is an even simpler case.

If the function logUpdate is called multiple times with the exact same input, the second call can return early and skip the re-rendering task.

In our experience, adding this short-circuiting logic in our render loop reduced the CPU usage of the iTerm process from 11-15% to 0.1% when output is unchanging.

`cliCursor.show()` not called on `.render()` after cursor is hidden

var render = function () {
  cliCursor.hide();
  var out = [].join.call(arguments, ' ') + '\n';
  stream.write(ansiEscapes.eraseLines(prevLineCount) + out);
  prevLineCount = out.split('\n').length;
};

After running logUpdate.render(), the cursor is only hidden, as you can see above, and never restored.

In looking at the cliCursor module, it doesn't look like you have a way of checking the starting status of the cursor, so perhaps you default it to hidden.

Node defaults to showing the cursor, and in my application I expect the cursor. Adding in cliCursor.show() at the end of the method fixed the problem.

If adding this is okay with you, I can do a PR:

var render = function () {
  cliCursor.hide();
  var out = [].join.call(arguments, ' ') + '\n';
  stream.write(ansiEscapes.eraseLines(prevLineCount) + out);
  prevLineCount = out.split('\n').length;
  cliCursor.show();
};

Tests

Need some real tests for this. Could probably mock process.stdout and check the output.

Don't draw above $LINES

Issuehunt badges

When handed output with more rows than the terminal has, log-update at best wastes time refreshing invisible data, and at worst causes scrolling (it's hard to tell since things update so quickly).

I think the right solution is to only output the bottom $LINES lines in the update.


IssueHunt Summary

djmitche djmitche has been rewarded.

Backers (Total: $30.00)

Submitted pull Requests


Tips


IssueHunt has been backed by the following sponsors. Become a sponsor

Update only the changed lines

Hi! Awesome project, thanks for it!

I use this package for the default pnpm reporter. It works fine but when the updated text is big there is a noticeable flickering (in Windows cmder). Would it be possible to make log-update smarter and instead of rewriting everything, do some diffing and update only the changed parts of the console view?

multiple page long text rewriting

When output long text(e.g. 100 lines spins) where as the terminal rows is 30 lines, how to make it overwrite the previous page that already exceed the screen?

The needed result is over write(refresh) the whole 100 lines, without leaking lines.

Cursor not shown after running all tasks.

I'm using listr with vorpal to make a CLI app, and after running the tasks, the cursor is not being redrawn.

I checked the CLIRenderer that's based on log-update and i realized that function done is not being called after on end function.

Maybe the idea is to change the render fn, adding a done condition:

const render = (tasks, done) => {
    const output = tasks.map(task => task.render());
    logUpdate(output.join('\n'));
    if (done) {
        logUpdate.done();
    }
};

Wrapped lines are not considered as part of the line count

Line wrapping occurs (via wrap-ansi) after the previous and current output lengths have been calculated based on newline counts. This might make those counts incorrect, making the eraseLines call erase the wrong number of lines..

Implement partial updates

In many cases, an update only changes a few lines -- an integer increments here, a status changes over there -- yet log-update erases and redraws the entire output. For large ouptuts over a remote connection, this can cause some flickering as the screen refreshes between the erase-everything sequence and the replacement output.

In these cases, log-update can easily determine which lines have changed and only update those lines. #29 is a start at this, optimized for the case where the updates are near the bottom, but we could do better.

The approach is sort of like the invalidation-rectangle approach used by browsers to decide which DOM elements to redraw -- only much simpler in this case!

Long lines are not taken into account

Long lines are not wrapped correctly.

This is because in render by the time the output is split out.split('\n') they are not always newlines, so their length is not taken into account.

Here is an example where the issue occurs:

"use strict";

const logUpdate = require("log-update");

let first = "Itwasgettingdark,andweweren’tthereyet.Italyismyfavoritecountry;infact,Iplantospendtwoweekstherenextyear.Catsaregoodpets,fortheyarecleanandarenotnoisy.Wheredorandomthoughtscomefrom?Christmasiscoming.";
let second = "Theriverstolethegods.Weneedtorentaroomforourparty.Thelakeisalongwayfromhere.Isitfree?WehaveneverbeentoAsia,norhavewevisitedAfrica.";
let third = "Maryplaysthepiano.Theshootersaysgoodbyetohislove.Thelakeisalongwayfromhere.Shouldwestartclassnow,orshouldwewaitforeveryonetogethere?WehavealotofraininJune.";

logUpdate(first);
setTimeout(() => logUpdate(second), 1000);
setTimeout(() => logUpdate(third), 2000);

cmder windows 10

It can be fixed by going through all lines and seeing which are longer than the console, like so:

let lines = out.split('\n');
prevLineCount = lines.length;

lines.forEach(l => {
	prevLineCount += Math.floor(l.length, termSize().columns);
});

logUpdate.clear() doesn't work

logUpdate.clear() doesn't clear the output. I experienced this issue in Windows 8.1 using node v0.12.

Example code:

var logUpdate = require('log-update');

var i = 0;
var frames = ['-', '\\', '|', '/'];

console.log('Start..');
var timer = setInterval(function () {
    var frame = frames[i++ % frames.length];
    logUpdate('\n' + '        ♥♥\n   ' + frame + ' unicorns ' + frame + '\n        ♥♥');
}, 100);

setTimeout(function () {
  clearInterval(timer);
  logUpdate.clear();
  console.log('Done!');
}, 1000);

Output:
output

If it worked the output should have been:

Start..
Done!

Let define the default columns.

    const getWidth = stream => {
	const {columns} = stream;

	if (!columns) {
		return 80;
	}

	return columns;
};

I want to define it to another number, is it possible?

Calling log-update breaks SIGINT signal handlers

It seems that using this library breaks the expected behaviour of SIGINT listeners. If logUpdate is used before registering a SIGINT listener, the listener is not called at all and the process exits immediately, if logUpdate is first called after registering a listener, the listener is called, but the process still exits. If logUpdate is not called, the code below doesn't exit of first SIGINT, but does so on the second one (which is the expected behaviour)

const logUpdate = require('log-update')

//logUpdate('This makes my SIGINT listener not called at all, and the process immediately exits')

process.stdin.on('data', () => {
	// just so we don't exit
})

let count = 0
process.on('SIGINT', () => {
	if (count >= 1) {
		process.exit(0)
	}
	console.log('press CTRL+C again to exit')
	count ++ 
})

//logUpdate('this makes my SIGINT listener be called, but the process still exits')

Uncommenting only the first usage yields

~/IdeaProjects/temp$ node index.js
This makes my SIGINT handler not called at all, and the process immediately exits
^C~/IdeaProjects/temp$

Uncommenting only the second usage yields

~/IdeaProjects/temp$ node index.js
this makes my SIGINT handler be called, but the process still exits
^Cpress CTRL+C again to exit
~/IdeaProjects/temp$

Uncommenting both yields

~/IdeaProjects/temp$ node index.js
^Cpress CTRL+C again to exit
^C~/IdeaProjects/temp$

Tested on node v6.10.1 and v7.7.4, macOS 10.12.3 (16D32)

require() of ES Module not supported.

const logUpdate = require('log-update');
                  ^

Error [ERR_REQUIRE_ESM]: require() of ES Module C:\Users\ExpErgio\Desktop\cookrecetasbot\node_modules\log-update\index.js from C:\Users\ExpErgio\Desktop\cookrecetasbot\index.js not supported.
Instead change the require of C:\Users\ExpErgio\Desktop\cookrecetasbot\node_modules\log-update\index.js in C:\Users\ExpErgio\Desktop\cookrecetasbot\index.js to a dynamic import() which is available in all CommonJS modules.
    at Object.<anonymous> (C:\Users\ExpErgio\Desktop\cookrecetasbot\index.js:2:19) {
  code: �[32m'ERR_REQUIRE_ESM'�[39m
}

Unexpected control characters when require’d and not invoked

I came across this issue when building watch functionality using the excellent elegant spinner, and it was hard to track down!

Some background: you can run mdast either with or without --watch. When not watching, nothing is written to stdout(4) normally. If you want, you can output a file to stdout(4) though.

My mother taught me to always write unit-tests, so I did: testing stdout(4) on some sample fixture to be an empty string.

Astonishment, I was, when I came to the conclusion that the tests started failing after including log-update (and not using it). I tracked it down to probably this. I’m not sure exactly where it originates though (maybe useful: Bash reported stdout(4)’’s length to be 18 characters).

Back to some actionable points: I worked around this by lazy-loading log-update, but maybe other people will also come across this too. Is it a bug? Interested in fixing it?

👍

Strange behavior when resizing terminal window

I am noticing a strange behavior when shrinking my terminal window so that it masks previously logged text and then making it bigger again. The gif below expose the problem :

issuelogupdate

The code is very minimal, it just log a new line every second.

const logUpdate = require('log-update')
let str = ''
let idx = 0
setInterval(() => {
  str += `Hello ${idx++}\n`
  logUpdate(str)
}, 1000)

My workstation is running maxOS High Sierra (v10.13.5) / Node v8.11.3.
I am using iTerm2 with zsh so at first I thought it might come from some setting of those, but I tried with default Terminal with bash (as captured in the gif) and problem is the same.

Any idea what could cause this glitch ?

Error [ERR_REQUIRE_ESM]: require() of ES Module index.js not supported

Getting:
Error [ERR_REQUIRE_ESM]: require() of ES Module C:\node_modules\log-update\index.js from C:\src\utils\files\log.utils.js not supported.
Instead change the require of index.js in C:\src\utils\files\log.utils.js to a dynamic import() which is available in all CommonJS modules.
courses\node_modules@babel\node\lib_babel-node.js:176:21) {
code: 'ERR_REQUIRE_ESM'
}
npm ERR! code 1
npm ERR! path C:\Web
npm ERR! command failed
npm ERR! command C:\WINDOWS\system32\cmd.exe /d /s /c babel-node src/scripts/initiate.script.js

npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\AppData\Local\npm-cache_logs\2022-01-01T17_56_21_202Z-debug-0.log

Also opened a ticket in SO:
https://stackoverflow.com/questions/70550799/error-err-require-esm-require-of-es-module-index-js-not-supported

Appending new multiline messages causes weird display issues

Reproducible code:

const logUpdate = require('log-update');

const log = logUpdate.create(process.stdout);
const messages = [];

const interval = setInterval(() => {
    log(messages.join('\n'));

    if (messages.length > 10) {
        clearInterval(interval);
    }

    messages.push(`This is a new multiline message.

Time is now: ${Date.now()}`)
}, 20);

Result:

bash_

This occurs on iTerm2 v3.1.2, using the hotkey visor (if that's relevant). The test case above is only reproducible when I've split-screened the window (either horizontally or vertically). However, the original issue I'm experiencing in ink (vadimdemedes/ink#48) occurs regardless of whether the window is split-screened.

This is kind of a corner-y case, but happy to help investigate.

can't use with node

const logUpdate = require('log-update');
                  ^

Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/aprilmintacpineda/work/wrg-digitisation/node_modules/log-update/index.js

console.table support

const table = console.table(['apples', 'oranges', 'bananas', message]); logUpdate(table);

if I put the above code in setInterval it prints out a new table every time on a new line. For strings it works fine. Is there any way that you can add support for console.table() so that only the values in the table get updated ?

[FR] Remember and update a session/line only

Test case :

const logUpdate = require('log-update');
let logCount = logUpdate.create(process.stdout);

logCount('count: 1');
logUpdate('foo');
setInterval(() => {
    logCount('count: 2');
}, 2000);

In this scenario, the second logCount replaces 'foo'.

Possible to remember and update the first line and keep the second line intact?

improve previous line count

The previous line count is being tracked by log-update but it does not take the terminal size into account.

For instance, if you log a line of 40 characters while the terminal is only 30 characters, it will be printed on 2 lines. After calling log-update again, it will only erase the 2nd line because the previous line count was only 1 and not 2.

So we have to iterate over all the lines here and calculate the number of printed lines instead.

How to opt out from "Fit lines to terminal height"?

It makes absolutely no sense to not display data that I need to display, I mean any information; User must be able to reach it by scrolling, it really makes no sense, so please tell me is there a way to disable that "feature" and log all I need to and not only what this library allows to?

Leading spaces are removed from output

example.js:

'use strict';
const logUpdate = require('log-update');

const frames = ['-', '\\', '|', '/'];
let i = 0;

setInterval(() => {
	const frame = frames[i = ++i % frames.length];

	logUpdate(
`
        ♥♥
   ${frame} unicorns ${frame}
        ♥♥
`
	);
}, 80);

log-update

This was probably introduced by d0ca53e.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.