Skip to content

Commit 9f29f95

Browse files
committed
add termina::view_ready() #987
1 parent 8a179e7 commit 9f29f95

7 files changed

+252
-20
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* experimental `--cols` CSS variable [#956](https://github.com/jcubic/jquery.terminal/issues/956)
66
* add `terminal::lines()` [#966](https://github.com/jcubic/jquery.terminal/issues/966)
77
* add small ASCII Art to signature [#972](https://github.com/jcubic/jquery.terminal/issues/972)
8+
* add `termina::view_ready()` [#987](https://github.com/jcubic/jquery.terminal/issues/987)
89
### Bugfix
910
* fix `terminal::login()` when user already authenticated [#980](https://github.com/jcubic/jquery.terminal/issues/980)
1011
* improve mobile support

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
[![npm](https://img.shields.io/badge/npm-DEV-blue.svg)](https://www.npmjs.com/package/jquery.terminal)
99
![bower](https://img.shields.io/badge/bower-DEV-yellow.svg)
1010
[![Build and test](https://github.com/jcubic/jquery.terminal/actions/workflows/build.yaml/badge.svg?branch=devel&event=push)](https://github.com/jcubic/jquery.terminal/actions/workflows/build.yaml)
11-
[![Coverage Status](https://coveralls.io/repos/github/jcubic/jquery.terminal/badge.svg?branch=devel&f6f4cbb4305eab7c6a36637dff2538c9)](https://coveralls.io/github/jcubic/jquery.terminal?branch=devel)
11+
[![Coverage Status](https://coveralls.io/repos/github/jcubic/jquery.terminal/badge.svg?branch=devel&07c28c31dc260cd38636ca8bd296cfc4)](https://coveralls.io/github/jcubic/jquery.terminal?branch=devel)
1212
![NPM Downloads](https://img.shields.io/npm/dm/jquery.terminal.svg?style=flat)
1313
[![jsDelivr Downloads](https://data.jsdelivr.com/v1/package/npm/jquery.terminal/badge?style=rounded&n=1)](https://www.jsdelivr.com/package/npm/jquery.terminal)
1414
[![Paid Support](https://img.shields.io/badge/paid-support-354465.svg)](https://support.jcubic.pl/)

__tests__/terminal.spec.js

+77
Original file line numberDiff line numberDiff line change
@@ -1617,6 +1617,63 @@ describe('Terminal utils', function() {
16171617
expect(output).toMatchSnapshot();
16181618
});
16191619
});
1620+
describe('EventEmitter', function() {
1621+
var emitter;
1622+
beforeEach(() => {
1623+
emitter = new $.terminal.EventEmitter();
1624+
});
1625+
it('should fire events', () => {
1626+
var handler = jest.fn();
1627+
emitter.on('test', handler);
1628+
emitter.emit('test');
1629+
emitter.emit('test');
1630+
expect(handler.mock.calls.length).toEqual(2);
1631+
});
1632+
it('should call different handlers', () => {
1633+
var foo = jest.fn();
1634+
var bar = jest.fn();
1635+
emitter.on('foo', foo);
1636+
emitter.on('bar', bar);
1637+
emitter.emit('foo', 10);
1638+
emitter.emit('bar', 20);
1639+
expect(foo).toHaveBeenCalledWith(10);
1640+
expect(bar).toHaveBeenCalledWith(20);
1641+
});
1642+
it('should fire event once', () => {
1643+
var handler = jest.fn();
1644+
emitter.once('test', handler);
1645+
emitter.emit('test');
1646+
emitter.emit('test');
1647+
expect(handler.mock.calls.length).toEqual(1);
1648+
});
1649+
it('should remove event handler', () => {
1650+
var handler = jest.fn();
1651+
emitter.on('test', handler);
1652+
emitter.off('test', handler);
1653+
emitter.emit('test');
1654+
emitter.emit('test');
1655+
expect(handler.mock.calls.length).toEqual(0);
1656+
});
1657+
it('should add mutiple event handlers', () => {
1658+
var foo = jest.fn();
1659+
var bar = jest.fn();
1660+
emitter.on('foo', foo);
1661+
emitter.on('foo', bar);
1662+
emitter.emit('foo');
1663+
expect(foo).toHaveBeenCalledWith();
1664+
expect(bar).toHaveBeenCalledWith();
1665+
});
1666+
it('should remove all event handlers', () => {
1667+
var foo = jest.fn();
1668+
var bar = jest.fn();
1669+
emitter.on('foo', foo);
1670+
emitter.on('foo', foo);
1671+
emitter.off('foo');
1672+
emitter.emit('foo');
1673+
expect(foo).not.toHaveBeenCalled();
1674+
expect(bar).not.toHaveBeenCalled();
1675+
});
1676+
});
16201677
describe('Cycle', function() {
16211678
describe('create', function() {
16221679
it('should create Cycle from init values', function() {
@@ -5184,6 +5241,26 @@ describe('Terminal plugin', function() {
51845241
expect(top.greetings).toEqual(greetings);
51855242
expect(top.completion).toEqual('settings');
51865243
});
5244+
it('it should export/import async echo', function(done) {
5245+
term.clear();
5246+
term.echo(async () => {
5247+
await term.delay(100);
5248+
return 'helo';
5249+
});
5250+
term.echo(async () => {
5251+
await term.delay(50);
5252+
return 'world';
5253+
});
5254+
term.view_ready().then(() => {
5255+
const view = term.export_view();
5256+
term.clear();
5257+
term.import_view(view);
5258+
setTimeout(() => {
5259+
expect(term.get_output()).toEqual('world\nhelo');
5260+
done();
5261+
}, 200);
5262+
});
5263+
});
51875264
it('should import view', function() {
51885265
term.clear().push($.noop).set_prompt('# ')
51895266
.set_mask(false)

js/jquery.terminal-src.js

+84-7
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,59 @@
260260
};
261261
}
262262
// -----------------------------------------------------------------------
263+
// :: EventEmitter class
264+
// -----------------------------------------------------------------------
265+
function EventEmitter() {
266+
this._events = {};
267+
}
268+
// -----------------------------------------------------------------------
269+
EventEmitter.prototype.on = function(event, listener) {
270+
if (!this._events[event]) {
271+
this._events[event] = [];
272+
}
273+
this._events[event].push(listener);
274+
};
275+
// -----------------------------------------------------------------------
276+
EventEmitter.prototype.emit = function(event) {
277+
if (this._events[event]) {
278+
const args = Array.prototype.slice.call(arguments, 1);
279+
this._events[event].forEach(function(listener) {
280+
listener.apply(null, args);
281+
});
282+
}
283+
};
284+
// -----------------------------------------------------------------------
285+
EventEmitter.prototype.off = function(event, listener) {
286+
if (!listener) {
287+
delete this._events[event];
288+
} else if (this._events[event]) {
289+
this._events[event] = this._events[event].filter(function(l) {
290+
return l !== listener;
291+
});
292+
}
293+
};
294+
// -----------------------------------------------------------------------
295+
EventEmitter.prototype.once = function(event, listener) {
296+
const self = this;
297+
298+
function wrapper() {
299+
self.off(event, wrapper);
300+
listener.apply(null, arguments);
301+
}
302+
303+
this.on(event, wrapper);
304+
};
305+
// -----------------------------------------------------------------------
306+
EventEmitter.prototype.wait_for = function(event) {
307+
const deferred = new $.Deferred();
308+
309+
this.once(event, function() {
310+
deferred.resolve();
311+
});
312+
313+
return deferred.promise();
314+
};
315+
// -----------------------------------------------------------------------
263316
// :: map object to object
264317
// -----------------------------------------------------------------------
265318
$.omap = function(o, fn) {
@@ -5385,6 +5438,7 @@
53855438
Cycle: Cycle,
53865439
History: History,
53875440
Stack: Stack,
5441+
EventEmitter: EventEmitter,
53885442
// ---------------------------------------------------------------------
53895443
// :: Validate html color (it can be name or hex)
53905444
// ---------------------------------------------------------------------
@@ -8999,6 +9053,7 @@
89999053
}
90009054
}
90019055
}
9056+
// ---------------------------------------------------------------------
90029057
var scroll_to_view = (function() {
90039058
function scroll_to_view(visible) {
90049059
if (!visible) {
@@ -9762,6 +9817,14 @@
97629817
return self;
97639818
},
97649819
// -------------------------------------------------------------
9820+
// :: Return a promise that is resolved when it's safe to
9821+
// :: call export_view, it wait for all async function echo
9822+
// :: to finish
9823+
// -------------------------------------------------------------
9824+
view_ready: function() {
9825+
return event_hub.wait_for('async_echo_ready');
9826+
},
9827+
// -------------------------------------------------------------
97659828
// :: Return an object that can be used with import_view to
97669829
// :: restore the state
97679830
// -------------------------------------------------------------
@@ -9796,7 +9859,8 @@
97969859
if (view.focus) {
97979860
self.focus();
97989861
}
9799-
lines.import(clone(view.lines).filter(function(line) {
9862+
var cloned_lines = clone(view.lines);
9863+
lines.import(cloned_lines.filter(function(line) {
98009864
return line[0];
98019865
}));
98029866
if (view.interpreters instanceof Stack) {
@@ -11027,16 +11091,24 @@
1102711091
echo: function(arg, options, deferred) {
1102811092
var arg_defined = arguments.length > 0;
1102911093
var d = deferred || new $.Deferred();
11030-
function cont() {
11094+
function cont(value) {
1103111095
echo_promise = false;
1103211096
var original = echo_delay;
1103311097
echo_delay = [];
1103411098
for (var i = 0; i < original.length; ++i) {
1103511099
self.echo.apply(self, original[i]);
1103611100
}
11101+
if (value) {
11102+
async_echo = async_echo.filter(function(promise) {
11103+
return promise !== value;
11104+
});
11105+
if (!async_echo.length) {
11106+
event_hub.emit('async_echo_ready');
11107+
}
11108+
}
1103711109
}
11038-
function error(e) {
11039-
cont();
11110+
function error(e, value) {
11111+
cont(value);
1104011112
display_exception(e, 'ECHO', true);
1104111113
}
1104211114
function echo(arg) {
@@ -11170,6 +11242,7 @@
1117011242
// queue async functions in echo
1117111243
if (is_promise(next)) {
1117211244
echo_promise = true;
11245+
async_echo.push(next);
1117311246
}
1117411247
lines.push([value, locals]);
1117511248
unpromise(next, function() {
@@ -11191,15 +11264,17 @@
1119111264
if (line) {
1119211265
self.update(-1, line[0], line[1]);
1119311266
}
11194-
cont();
11267+
cont(next);
1119511268
});
1119611269
}
1119711270
fire_event('onAfterEcho', [arg]);
1119811271
}
1119911272
if (!contination_run) {
11200-
cont();
11273+
cont(next);
1120111274
}
11202-
}, error);
11275+
}, function(e) {
11276+
error(e, next);
11277+
});
1120311278
}, error);
1120411279
} catch (e) {
1120511280
// if echo throw exception we can't use error to
@@ -11997,6 +12072,8 @@
1199712072
var init_queue = new DelayQueue();
1199812073
var when_ready = ready(init_queue);
1199912074
var cmd_ready = ready(command_queue);
12075+
var async_echo = [];
12076+
var event_hub = new EventEmitter();
1200012077
var is_bottom_detected;
1200112078
var is_bottom_observer;
1200212079
var in_login = false;// some Methods should not be called when login

0 commit comments

Comments
 (0)