• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jcubic / jquery.terminal / 11236377677

08 Oct 2024 01:21PM UTC coverage: 82.693% (-0.1%) from 82.837%
11236377677

push

github

jcubic
version 2.44.0

4760 of 5966 branches covered (79.79%)

5466 of 6610 relevant lines covered (82.69%)

16363.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.5
/js/less.js
1
/**@license
2
 *       __ _____                     ________                              __
3
 *      / // _  /__ __ _____ ___ __ _/__  ___/__ ___ ______ __ __  __ ___  / /
4
 *  __ / // // // // // _  // _// // / / // _  // _//     // //  \/ // _ \/ /
5
 * /  / // // // // // ___// / / // / / // ___// / / / / // // /\  // // / /__
6
 * \___//____ \\___//____//_/ _\_  / /_//____//_/ /_/ /_//_//_/ /_/ \__\_\___/
7
 *           \/              /____/
8
 * http://terminal.jcubic.pl
9
 *
10
 * This is example of how to create less like command for jQuery Terminal
11
 * the code is based on the one from leash shell and written as jQuery plugin
12
 *
13
 * Copyright (c) 2018-2024 Jakub Jankiewicz <https://jcubic.pl/me>
14
 * Released under the MIT license
15
 *
16
 */
17
/* global define */
18
(function(factory, undefined) {
2✔
19
    var root;
20
    if (typeof window !== 'undefined') {
2!
21
        root = window;
2✔
22
    } else if (typeof self !== 'undefined') {
×
23
        root = self;
×
24
    } else if (typeof global !== 'undefined') {
×
25
        root = global;
×
26
    } else {
27
        throw new Error('Unknow context');
×
28
    }
29
    if (typeof define === 'function' && define.amd) {
2!
30
        // AMD. Register as an anonymous module.
31
        // istanbul ignore next
32
        define(['jquery', 'jquery.terminal'], factory);
33
    } else if (typeof module === 'object' && module.exports) {
2!
34
        // Node/CommonJS
35
        module.exports = function(root, jQuery) {
2✔
36
            if (jQuery === undefined) {
2!
37
                // require('jQuery') returns a factory that requires window to
38
                // build a jQuery instance, we normalize how we use modules
39
                // that require this pattern but the window provided is a noop
40
                // if it's defined (how jquery works)
41
                if (window !== undefined) {
2!
42
                    jQuery = require('jquery');
2✔
43
                } else {
44
                    jQuery = require('jquery')(root);
×
45
                }
46
            }
47
            if (!jQuery.fn.terminal) {
2!
48
                if (window !== undefined) {
×
49
                    require('jquery.terminal');
×
50
                } else {
51
                    require('jquery.terminal')(jQuery);
×
52
                }
53
            }
54
            factory(jQuery);
2✔
55
            return jQuery;
2✔
56
        };
57
    } else {
58
        // Browser
59
        // istanbul ignore next
60
        factory(root.jQuery);
61
    }
62
})(function($) {
63
    var img_split_re = /(\[\[(?:[^;]*@[^;]*);[^;]*;[^\]]*\]\s*\])/;
2✔
64
    var img_re = /\[\[(?:[^;]*@[^;]*);[^;]*;[^;]*;([^;]*);([^;]*)(?:;[^;]+)?\]([^\]]*)\]/;
2✔
65
    // -------------------------------------------------------------------------
66
    function find(arr, fn) {
67
        for (var i in arr) {
28✔
68
            if (fn(arr[i])) {
2,010✔
69
                return arr[i];
6✔
70
            }
71
        }
72
    }
73
    // -------------------------------------------------------------------------
74
    // $.when is always async we don't want that for normal non images
75
    // -------------------------------------------------------------------------
76
    function unpromise(args, fn) {
77
        var found = find(args, function(arg) {
28✔
78
            return typeof arg.then === 'function';
2,010✔
79
        });
80
        if (found) {
28✔
81
            return $.when.apply($, args).then(fn);
6✔
82
        } else {
83
            return fn.apply(null, args);
22✔
84
        }
85
    }
86
    // -------------------------------------------------------------------------
87
    // slice images into terminal lines - each line is unique blob url
88
    function slice_image(img_data, width, y1, y2) {
89
        // render slice on canvas and get Blob Data URI
90
        var canvas = new OffscreenCanvas(width, y2 - y1);
172✔
91
        var ctx = canvas.getContext('2d', {willReadFrequently: true});
172✔
92
        ctx.putImageData(img_data, 0, 0);
172✔
93
        return canvas.convertToBlob().then(function(blob) {
172✔
94
            if (blob === null) {
172!
95
                return null;
×
96
            } else {
97
                return URL.createObjectURL(blob);
172✔
98
            }
99
        });
100
    }
101
    function slice(src, options) {
102
        var settings = $.extend({
6✔
103
            width: null,
104
            line_height: null
105
        }, options);
106
        var img = new Image();
6✔
107
        var defer = $.Deferred();
6✔
108
        var slices = [];
6✔
109
        img.onload = function() {
6✔
110
            var height, width;
111
            if (settings.width < img.width) {
4!
112
                height = Math.floor((img.height * settings.width) / img.width);
×
113
                width = settings.width;
×
114
            } else {
115
                height = img.height;
4✔
116
                width = img.width;
4✔
117
            }
118
            var canvas = new OffscreenCanvas(width, height);
4✔
119
            var ctx = canvas.getContext('2d', {willReadFrequently: true});
4✔
120
            // scale the image to fit the terminal
121
            ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, width, height);
4✔
122
            (function recur(start) {
4✔
123
                // loop over slices
124
                if (start < height) {
176✔
125
                    var y1 = start, y2 = start + settings.line_height;
172✔
126
                    if (y2 > height) {
172✔
127
                        y2 = height;
4✔
128
                    }
129
                    var img_data = ctx.getImageData(0, y1, width, y2);
172✔
130
                    slice_image(img_data, width, y1, y2).then(function(uri) {
172✔
131
                        slices.push(uri);
172✔
132
                        recur(y2);
172✔
133
                    });
134
                } else {
135
                    defer.resolve(slices);
4✔
136
                }
137
            })(0);
138
        };
139
        img.onerror = function() {
6✔
140
            defer.reject('Error loading the image: ' + src);
2✔
141
        };
142
        // images need to have CORS if on different server,
143
        // without this it will throw error
144
        img.crossOrigin = "anonymous";
6✔
145
        img.src = src;
6✔
146
        return defer.promise();
6✔
147
    }
148
    // -----------------------------------------------------------------------------------
149
    function less(term, text, options) {
150
        var export_data = term.export_view();
28✔
151
        var cols, rows;
152
        var pos = 0;
28✔
153
        var original_lines;
154
        var lines;
155
        var prompt = '';
28✔
156
        var left = 0;
28✔
157
        var $output = term.find('.terminal-output');
28✔
158
        var had_cache = term.option('useCache');
28✔
159
        if (!had_cache) {
28!
160
            term.option('useCache', true);
×
161
        }
162
        var cmd = term.cmd();
28✔
163
        var scroll_by = 3;
28✔
164
        //term.on('mousewheel', wheel);
165
        var in_search = false, last_found, search_string;
28✔
166
        // -------------------------------------------------------------------------------
167
        function print() {
168
            // performance optimization
169
            term.find('.terminal-output').css('visibilty', 'hidden');
94✔
170
            term.clear();
94✔
171
            if (lines.length - pos > rows - 1) {
94✔
172
                prompt = ':';
82✔
173
            } else {
174
                prompt = '[[;;;cmd-inverted](END)]';
12✔
175
            }
176
            term.set_prompt(prompt);
94✔
177
            var to_print = lines.slice(pos, pos + rows - 1);
94✔
178
            var should_substring = options.wrap ? false : to_print.filter(function(line) {
94!
179
                var len = $.terminal.length(line);
2,248✔
180
                return len > cols;
2,248✔
181
            }).length;
182
            if (should_substring) {
94!
183
                to_print = to_print.map(function(line) {
×
184
                    return $.terminal.substring(line, left, left + cols - 1);
×
185
                });
186
            }
187
            if (to_print.length < rows - 1) {
94✔
188
                while (rows - 1 > to_print.length) {
12✔
189
                    to_print.push('~');
108✔
190
                }
191
            }
192
            var finalize;
193
            if (options.ansi) {
94!
194
                finalize = function(div) {
×
195
                    div.addClass('ansi');
×
196
                };
197
            }
198
            term.echo(to_print.join('\n'), {
94✔
199
                finalize: finalize,
200
                externalPause: false
201
            });
202
            if (term.find('.terminal-output').is(':empty')) {
94!
203
                // sometimes the output is not flushed not idea why
204
                // TODO: investigate
205
                term.flush();
×
206
            }
207
        }
208
        // -------------------------------------------------------------------------------
209
        function quit() {
210
            term.pop().import_view(export_data);
4✔
211
            clear_cache();
4✔
212
            term.removeClass('terminal-less');
4✔
213
            $output.css('height', '');
4✔
214
            var exit = options.exit || options.onExit;
4✔
215
            if (typeof exit === 'function') {
4!
216
                exit();
4✔
217
            }
218
        }
219
        // -------------------------------------------------------------------------------
220
        var cache = {};
28✔
221
        function clear_cache() {
222
            if (!had_cache) {
4!
223
                term.option('useCache', false).clear_cache();
×
224
            }
225
            Object.keys(cache).forEach(function(width) {
4✔
226
                Object.keys(cache[width]).forEach(function(img) {
2✔
227
                    cache[width][img].forEach(function(uri) {
2✔
228
                        URL.revokeObjectURL(uri);
86✔
229
                    });
230
                });
231
            });
232
            cache = {};
4✔
233
        }
234
        // -------------------------------------------------------------------------------
235
        function fixed_output() {
236
            // this will not change on resize, but the font size may change
237
            var height = cmd.outerHeight(true);
28✔
238
            term.addClass('terminal-less');
28✔
239
            $output.css('height', 'calc(100% - ' + height + 'px)');
28✔
240
        }
241
        // -------------------------------------------------------------------------------
242
        function refresh_view() {
243
            cols = term.cols();
28✔
244
            rows = term.rows();
28✔
245
            fixed_output();
28✔
246
            function cont(l) {
247
                original_lines = process_optional_wrap(l);
28✔
248
                lines = original_lines.slice();
28✔
249
                if (in_search) {
28!
250
                    search(last_found);
×
251
                } else {
252
                    print();
28✔
253
                }
254
            }
255
            function process_optional_wrap(arg) {
256
                if (arg instanceof Array) {
28!
257
                    if (options.wrap) {
28!
258
                        arg = arg.join('\n');
×
259
                        return $.terminal.split_equal(arg, cols, options.keepWords);
×
260
                    }
261
                    return arg;
28✔
262
                } else if (options.wrap) {
×
263
                    return $.terminal.split_equal(arg, cols, options.keepWords);
×
264
                } else {
265
                    return [arg];
×
266
                }
267
            }
268
            function run(arg) {
269
                var text;
270
                if (arg instanceof Array) {
28✔
271
                    if (options.formatters) {
20!
272
                        text = arg.join('\n');
×
273
                    } else {
274
                        original_lines = arg;
20✔
275
                    }
276
                } else {
277
                    text = arg;
8✔
278
                }
279
                if (text) {
28✔
280
                    if (options.formatters) {
8!
281
                        text = $.terminal.apply_formatters(text);
×
282
                    } else {
283
                        // prism text will be boken when there are nestings (xml)
284
                        // and empty formattings
285
                        text = $.terminal.nested_formatting(text);
8✔
286
                        text = $.terminal.normalize(text);
8✔
287
                    }
288
                    unpromise([image_formatter(text)], cont);
8✔
289
                } else {
290
                    unpromise(original_lines.map(image_formatter), function() {
20✔
291
                        var l = Array.prototype.concat.apply([], arguments);
20✔
292
                        cont(l);
20✔
293
                    });
294
                }
295
            }
296
            if (typeof text === 'function') {
28!
297
                text(cols, run);
×
298
            } else {
299
                run(text);
28✔
300
            }
301
        }
302
        // -------------------------------------------------------------------------------
303
        function cursor_size() {
304
            var cursor = term.find('.cmd-cursor')[0];
6✔
305
            return cursor.getBoundingClientRect();
6✔
306
        }
307
        // -------------------------------------------------------------------------------
308
        function image_formatter(text) {
309
            var defer = $.Deferred();
2,010✔
310
            if (!text.match(img_re)) {
2,010✔
311
                return text.split('\n');
2,004✔
312
            }
313
            var parts = text.split(img_split_re).filter(Boolean);
6✔
314
            var width = term.find('.terminal-output').width();
6✔
315
            var result = [];
6✔
316
            (function recur() {
6✔
317
                function concat_slices(slices) {
318
                    cache[width][img] = slices;
4✔
319
                    result = result.concat(slices.map(function(uri) {
4✔
320
                        return '[[@;;;' + cls + ';' + uri + ']' + alt + ']';
172✔
321
                    }));
322
                    recur();
4✔
323
                }
324
                if (!parts.length) {
24✔
325
                    return defer.resolve(result);
6✔
326
                }
327
                var part = parts.shift();
18✔
328
                var m = part.match(img_re);
18✔
329
                if (m) {
18✔
330
                    var img = m[2];
6✔
331
                    var cls = m[1].trim();
6✔
332
                    if (cls) {
6!
333
                        cls += ' terminal-less';
×
334
                    } else {
335
                        cls = 'terminal-less';
6✔
336
                    }
337
                    var alt = m[3];
6✔
338
                    var rect = cursor_size();
6✔
339
                    var opts = {
6✔
340
                        width: width,
341
                        line_height: Math.floor(rect.height)
342
                    };
343
                    cache[width] = cache[width] || {};
6✔
344
                    if (cache[width][img]) {
6!
345
                        concat_slices(cache[width][img]);
×
346
                    } else {
347
                        slice(img, opts).then(concat_slices).catch(function() {
6✔
348
                            var msg = $.terminal.escape_brackets('[BROKEN IMAGE]');
2✔
349
                            var cls = 'terminal-broken-image';
2✔
350
                            result.push('[[;#c00;;' + cls + ']' + msg + ']');
2✔
351
                            recur();
2✔
352
                        });
353
                    }
354
                } else {
355
                    if (part !== '\n') {
12!
356
                        result = result.concat(part.split('\n'));
12✔
357
                    }
358
                    recur();
12✔
359
                }
360
            })();
361
            return defer.promise();
6✔
362
        }
363
        // -------------------------------------------------------------------------------
364
        function search(start, reset) {
365
            var escape = $.terminal.escape_brackets(search_string);
44✔
366
            var flag = search_string.toLowerCase() === search_string ? 'i' : '';
44✔
367
            var start_re = new RegExp('^(' + escape + ')', flag);
44✔
368
            var index = -1;
44✔
369
            var prev_format = '';
44✔
370
            var formatting = false;
44✔
371
            var in_text = false;
44✔
372
            var count = 0;
44✔
373
            lines = original_lines.slice();
44✔
374
            if (reset) {
44✔
375
                index = pos = 0;
2✔
376
            }
377
            for (var i = start; i < lines.length; ++i) {
44✔
378
                var line = lines[i];
3,970✔
379
                for (var j = 0, jlen = line.length; j < jlen; ++j) {
3,970✔
380
                    if (line[j] === '[' && line[j + 1] === '[') {
25,210✔
381
                        formatting = true;
2✔
382
                        in_text = false;
2✔
383
                        start = j;
2✔
384
                    } else if (formatting && line[j] === ']') {
25,208✔
385
                        if (in_text) {
2!
386
                            formatting = false;
×
387
                            in_text = false;
×
388
                        } else {
389
                            in_text = true;
2✔
390
                            prev_format = line.substring(start, j + 1);
2✔
391
                        }
392
                    } else if (formatting && in_text || !formatting) {
25,206✔
393
                        if (line.substring(j).match(start_re)) {
25,194✔
394
                            var rep;
395
                            if (formatting && in_text) {
410✔
396
                                var style = prev_format.match(/\[\[([^;]+)/);
2✔
397
                                var new_format = ';;;terminal-inverted';
2✔
398
                                style = style ? style[1] : '';
2!
399
                                if (style.match(/!/)) {
2!
400
                                    new_format = style + new_format + ';';
×
401
                                    new_format += prev_format.replace(/]$/, '')
×
402
                                        .split(';').slice(4).join(';');
403
                                }
404
                                rep = '][[' + new_format + ']$1]' + prev_format;
2✔
405
                            } else {
406
                                rep = '[[;;;terminal-inverted]$1]';
408✔
407
                            }
408
                            line = line.substring(0, j) +
410✔
409
                                line.substring(j).replace(start_re, rep);
410
                            j += rep.length - 2;
410✔
411
                            if (i >= pos && index === -1) {
410✔
412
                                index = pos = i;
42✔
413
                            }
414
                            count++;
410✔
415
                        }
416
                    }
417
                }
418
                lines[i] = line;
3,970✔
419
            }
420
            print();
44✔
421
            term.set_command('');
44✔
422
            term.set_prompt(prompt);
44✔
423
            if (count === 1) {
44✔
424
                return -1;
16✔
425
            }
426
            return index;
28✔
427
        }
428
        // -------------------------------------------------------------------------------
429
        function scroll(delta, scroll_by) {
430
            if (delta > 0) {
×
431
                pos -= scroll_by;
×
432
                if (pos < 0) {
×
433
                    pos = 0;
×
434
                }
435
            } else if (pos <= lines.length - rows) {
×
436
                pos += scroll_by;
×
437
                if (pos - 1 > lines.length - rows) {
×
438
                    pos = lines.length - rows + 1;
×
439
                }
440
            }
441
            print();
×
442
            return false;
×
443
        }
444
        term.push($.noop, {
28✔
445
            onResize: refresh_view,
446
            touchscroll: function(event, delta) {
447
                var offset = Math.abs(delta);
×
448
                scroll(delta, Math.round(offset / 14));
×
449
                return false;
×
450
            },
451
            onPaste: function() {
452
                if (term.get_prompt() !== '/') {
×
453
                    return false;
×
454
                }
455
            },
456
            mousewheel: function(event, delta) {
457
                return scroll(delta, scroll_by);
×
458
            },
459
            name: 'less',
460
            keydown: function(e) {
461
                var command = term.get_command();
154✔
462
                var key = e.key.toUpperCase();
154✔
463
                if (term.get_prompt() !== '/') {
154✔
464
                    if (key === '/') {
72✔
465
                        term.set_prompt('/');
12✔
466
                    } else if (in_search &&
60✔
467
                               $.inArray(e.which, [78, 80]) !== -1) {
468
                        if (key === 'N') { // search_string
34✔
469
                            if (last_found !== -1) {
32!
470
                                var ret = search(last_found + 1);
32✔
471
                                if (ret !== -1) {
32✔
472
                                    last_found = ret;
22✔
473
                                }
474
                            }
475
                        } else if (key === 'P') {
2!
476
                            last_found = search(0, true);
2✔
477
                        }
478
                    } else if (key === 'Q') {
26✔
479
                        quit();
4✔
480
                    } else if (key === 'ARROWRIGHT') {
22!
481
                        if (!options.wrap) {
×
482
                            left += Math.round(cols / 2);
×
483
                            print();
×
484
                        }
485
                    } else if (key === 'ARROWLEFT') {
22!
486
                        if (!options.wrap) {
×
487
                            left -= Math.round(cols / 2);
×
488
                            if (left < 0) {
×
489
                                left = 0;
×
490
                            }
491
                            print();
×
492
                            // scroll
493
                        }
494
                    } else if (lines.length > rows) {
22!
495
                        if (key === 'ARROWUP') { //up
22✔
496
                            if (pos > 0) {
6!
497
                                --pos;
6✔
498
                                print();
6✔
499
                            }
500
                        } else if (key === 'ARROWDOWN') { //down
16✔
501
                            if (pos <= lines.length - rows) {
8!
502
                                ++pos;
8✔
503
                                print();
8✔
504
                            }
505
                        } else if (key === 'PAGEDOWN') {
8✔
506
                            pos += rows - 1;
4✔
507
                            var limit = lines.length - rows + 1;
4✔
508
                            if (pos > limit) {
4!
509
                                pos = limit;
×
510
                            }
511
                            print();
4✔
512
                        } else if (key === 'PAGEUP') {
4!
513
                            //Page Down
514
                            pos -= rows - 1;
4✔
515
                            if (pos < 0) {
4!
516
                                pos = 0;
×
517
                            }
518
                            print();
4✔
519
                        }
520
                    }
521
                    if (!e.ctrlKey && !e.alKey) {
72!
522
                        return false;
72✔
523
                    }
524
                    // search
525
                } else if (e.which === 8 && command === '') {
82✔
526
                    // backspace
527
                    term.set_prompt(prompt);
2✔
528
                } else if (e.which === 13) { // enter
80✔
529
                    // basic search find only first
530
                    if (command.length > 0) {
10!
531
                        in_search = true;
10✔
532
                        pos = 0;
10✔
533
                        search_string = command;
10✔
534
                        last_found = search(0);
10✔
535
                    }
536
                    // this will disable history
537
                    return false;
10✔
538
                }
539
            },
540
            prompt: prompt
541
        });
542
        // -------------------------------------------------------------------------------
543
        refresh_view();
28✔
544
    }
545
    // -----------------------------------------------------------------------------------
546
    $.fn.less = function(text, options) {
2✔
547
        var settings = $.extend({
28✔
548
            onExit: $.noop,
549
            ansi: false,
550
            formatters: false
551
        }, options);
552
        if (!(this instanceof $.fn.init && this.terminal)) {
28!
553
            throw new Error('This plugin require jQuery Terminal');
×
554
        }
555
        var term = this.terminal();
28✔
556
        if (!term) {
28!
557
            throw new Error(
×
558
                'You need to invoke this plugin on selector that have ' +
559
                'jQuery Terminal or on jQuery Terminal instance'
560
            );
561
        }
562
        less(term, text, settings);
28✔
563
        return term;
28✔
564
    };
565
});
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc