terminal-kit
Version:
256 colors, keys and mouse, input field, progress bars, screen buffer (including 32-bit composition and image loading), text buffer, and many more... Whether you just need colors and styles, build a simple interactive command line tool or a complexe termi
1,674 lines (1,308 loc) • 2.13 MB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.TerminalKit = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
/*
Terminal Kit
Copyright (c) 2009 - 2022 Cédric Ronvel
The MIT License (MIT)
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.
*/
"use strict" ;
const termkit = require( './termkit.js' ) ;
/*
Custom 256-colors palette for ScreenBuffer, each color is 24 bits.
Enhance ScreenBuffer without relying on ScreenBufferHD.
The original 6x6x6 colors cube shipped with terminal software is rather boring and miss the spot.
Lot of useless colors, lot of over-saturated hideous colors, and useful colors cannot be shaded/hilighted easily.
It also wastes plenty of color registers with 24 its grayscale colors... way too much details on this area that is unlikely to be used.
This new custom palette have 4 regions:
* The first 16 colors are reserved, it always maps to ANSI colors and depends on the terminal settings entirely.
* Then comes a 216-color adaptive palette based upon 12 user-provided colors that represent 12 hue of the wheel,
with auto-generated variation of shades, tint and desaturation.
That is: 12 colors * 6 level of tint/shade * 3 level of desaturation = 216 colors.
For maximum reliance, they are computed using the HCL (Lab) colorspace.
This provides some good hilight and dim effect.
* Then comes 13 extra colors fully defined by the user, they must be precise and beautiful colors that are missing
in the 216-colors part, and that have great added values.
* And finally comes 11 gray scale colors (going 0%, 10%, 20%, ..., 100% of HCL lightness)
*/
/*
Color names scheme:
* ansi colors: the ANSI color, without prefix or suffix
* adaptatives colors: '@' + color name + '~'{0,2} (desaturation level) + '+'{0,2} or '-'{0,3} (lightness or brightness level)
* extra colors: '*' + color name
* grayscale colors: '@' + color name
*/
const defaultAdaptivePaletteDef = [
{ names: [ 'red' ] , code: '#e32322' } ,
{ names: [ 'orange' ] , code: '#f18e1c' } ,
{ names: [ 'gold' , 'yellow-orange' , 'amber' ] , code: '#fdc60b' } ,
{ names: [ 'yellow' ] , code: '#f4e500' } ,
{ names: [ 'chartreuse' , 'yellow-green' ] , code: '#8cbb26' } ,
{ names: [ 'green' ] , code: '#25ad28' } ,
{ names: [ 'turquoise' , 'turquoise-green' ] , code: '#1bc17d' } ,
{ names: [ 'cyan' , 'turquoise-blue' ] , code: '#0dc0cd' } ,
{ names: [ 'blue' ] , code: '#2a60b0' } ,
{ names: [ 'indigo' ] , code: '#3b3ba2' } ,
{ names: [ 'violet' , 'purple' ] , code: '#713795' } ,
{ names: [ 'magenta' ] , code: '#bd0a7d' }
] ;
// 13 extra colors
const defaultExtraPaletteDef = [
{ names: [ 'crimson' ] , code: '#dc143c' } ,
{ names: [ 'vermilion' , 'cinnabar' ] , code: '#e34234' } ,
{ names: [ 'brown' ] , code: '#a52a2a' } ,
{ names: [ 'bronze' ] , code: '#cd7f32' } ,
{ names: [ 'coquelicot' ] , code: '#ff3800' } ,
//{ names: [ 'flame' ] , code: '#e25822' } ,
//{ names: [ 'salmon' ] , code: '#ff8c69' } ,
{ names: [ 'coral-pink' ] , code: '#f88379' } ,
{ names: [ 'see-green' ] , code: '#2e8b57' } ,
{ names: [ 'medium-spring-green' ] , code: '#00fa9a' } ,
{ names: [ 'olivine' ] , code: '#9ab973' } ,
{ names: [ 'royal-blue' ] , code: '#4169e1' } ,
{ names: [ 'purple' ] , code: '#800080' } ,
//{ names: [ 'tyrian-purple' ] , code: '#66023c' } ,
//{ names: [ 'purple-heart' ] , code: '#69359c' } ,
{ names: [ 'lavender-purple' ] , code: '#967bb6' } ,
//{ names: [ 'classic-rose' , 'light-pink' ] , code: '#fbcce7' } ,
{ names: [ 'pink' ] , code: '#ffc0cb' }
//{ names: [ 'lime' , 'lemon-lime' ] , code: '#bfff00' } ,
] ;
const ansiColorIndex = {
black: 0 ,
red: 1 ,
green: 2 ,
yellow: 3 ,
blue: 4 ,
magenta: 5 ,
violet: 5 ,
cyan: 6 ,
white: 7 ,
grey: 8 ,
gray: 8 ,
'bright-black': 8 ,
'bright-red': 9 ,
'bright-green': 10 ,
'bright-yellow': 11 ,
'bright-blue': 12 ,
'bright-magenta': 13 ,
'bright-violet': 13 ,
'bright-cyan': 14 ,
'bright-white': 15
} ;
function Palette( options = {} ) {
this.term = options.term || termkit.terminal ;
this.system = !! options.system ;
this.adaptivePaletteDef = this.system ? null : options.adaptivePaletteDef || defaultAdaptivePaletteDef ;
this.extraPaletteDef = this.system ? null : options.extraPaletteDef || defaultExtraPaletteDef ;
this.escape = [] ;
this.bgEscape = [] ;
this.chromaColors = [] ;
this.colorIndex = {} ;
// Because that function is often passed as argument... easier to bind it here once for all
this.colorNameToIndex = this.colorNameToIndex.bind( this ) ;
this.generate() ;
}
module.exports = Palette ;
Palette.prototype.colorNameToIndex = function( name ) {
name = name.toLowerCase() ;
return this.colorIndex[ name ] || termkit.colorNameToIndex( name ) ;
} ;
Palette.prototype.generate = function() {
this.generateDefaultMapping() ;
this.generateAnsiColorNames() ;
this.generateAdaptive() ;
this.generateExtra() ;
this.generateGrayscale() ;
} ;
// It just generates default terminal mapping for 256 colors
Palette.prototype.generateDefaultMapping = function() {
var register ;
for ( register = 0 ; register < 256 ; register ++ ) {
this.escape[ register ] = this.term.str.color256( register ) ;
this.bgEscape[ register ] = this.term.str.bgColor256( register ) ;
}
} ;
// It just generates default terminal mapping for 256 colors
Palette.prototype.generateAnsiColorNames = function() {
var name , strippedName ;
for ( name in ansiColorIndex ) {
strippedName = name.replace( /-/g , '' ) ;
this.colorIndex[ name ] = ansiColorIndex[ name ] ;
if ( strippedName !== name ) {
this.colorIndex[ strippedName ] = ansiColorIndex[ name ] ;
}
}
} ;
Palette.prototype.generateAdaptive = function() {
if ( this.system ) { return ; }
var i , j , z , register ,
baseChromaColors , chromaColor ,
saturationMark , lightnessMark , suffix ;
baseChromaColors = this.adaptivePaletteDef.map( color => termkit.chroma( color.code ) ) ;
register = 16 ;
for ( z = 0 ; z >= -2 ; z -- ) {
if ( z > 0 ) {
saturationMark = '!'.repeat( z ) ;
}
else if ( z < 0 ) {
saturationMark = '~'.repeat( -z ) ;
}
else {
saturationMark = '' ;
}
for ( j = 2 ; j >= -3 ; j -- ) {
if ( j > 0 ) {
lightnessMark = '+'.repeat( j ) ;
}
else if ( j < 0 ) {
lightnessMark = '-'.repeat( -j ) ;
}
else {
lightnessMark = '' ;
}
suffix = saturationMark + lightnessMark ;
for ( i = 0 ; i < 12 ; i ++ ) {
chromaColor = this.clStep( baseChromaColors[ i ] , z , j ) ;
this.addColor( register , chromaColor , this.adaptivePaletteDef[ i ].names , '@' , suffix ) ;
register ++ ;
}
}
}
} ;
Palette.prototype.generateExtra = function() {
if ( this.system ) { return ; }
var i , register ;
register = 232 ;
for ( i = 0 ; i < 13 && i < this.extraPaletteDef.length ; i ++ ) {
this.addColor( register , termkit.chroma( this.extraPaletteDef[ i ].code ) , this.extraPaletteDef[ i ].names , '*' ) ;
register ++ ;
}
} ;
const grayscaleNames = [
[ 'black' ] ,
[ 'darkest-gray' ] ,
[ 'darker-gray' ] ,
[ 'dark-gray' ] ,
[ 'dark-medium-gray' ] ,
[ 'medium-gray' , 'gray' ] ,
[ 'light-medium-gray' ] ,
[ 'light-gray' ] ,
[ 'lighter-gray' ] ,
[ 'lightest-gray' ] ,
[ 'white' ]
] ;
Palette.prototype.generateGrayscale = function() {
if ( this.system ) { return ; }
var i , register , chromaColor ;
register = 245 ;
for ( i = 0 ; i <= 10 ; i ++ ) {
chromaColor = termkit.chroma( 0 , 0 , 10 * i , 'hcl' ) ;
this.addColor( register , chromaColor , grayscaleNames[ i ] , '@' ) ;
register ++ ;
}
} ;
Palette.prototype.getRgb = function( register ) {
var chromaColor = this.chromaColors[ register ] ;
if ( ! chromaColor ) { return null ; }
var [ r , g , b ] = chromaColor.rgb() ;
return { r , g , b } ;
} ;
Palette.prototype.addColor = function( register , chromaColor , names , prefix = '' , suffix = '' ) {
var targetRegister ,
[ r , g , b ] = chromaColor.rgb() ;
this.chromaColors[ register ] = chromaColor ;
if ( this.term.support.trueColor ) {
this.escape[ register ] = this.term.str.colorRgb( r , g , b ) ;
this.bgEscape[ register ] = this.term.str.bgColorRgb( r , g , b ) ;
}
else if ( this.term.support['256colors'] ) {
targetRegister = this.term.registerForRgb(
{ r , g , b } ,
r === g && g === b ? 232 : 0 , // minRegister is the start of the grayscale if r=g=b
255
) ;
this.escape[ register ] = this.term.str.color256( targetRegister ) ;
this.bgEscape[ register ] = this.term.str.bgColor256( targetRegister ) ;
}
else {
targetRegister = this.term.registerForRgb( { r , g , b } , 0 , 15 ) ;
this.escape[ register ] = this.term.str.color256( targetRegister ) ;
this.bgEscape[ register ] = this.term.str.bgColor256( targetRegister ) ;
}
names.forEach( name => {
var strippedName = prefix + name.replace( /-/g , '' ) + suffix ;
name = prefix + name + suffix ;
this.colorIndex[ name ] = register ;
if ( strippedName !== name ) {
this.colorIndex[ strippedName ] = register ;
}
} ) ;
} ;
const FIX_STEP = 1.1 ;
Palette.prototype.clStep = function( chromaColor , cAdjust , lAdjust , fixRgb = true ) {
var c , l , rgb , avg , sortedChannels , preserveLOverC ;
if ( ! cAdjust && ! lAdjust ) { return chromaColor ; }
c = chromaColor.get( 'hcl.c' ) ;
l = chromaColor.get( 'hcl.l' ) ;
/*
c += c * cAdjust / 3 ;
l += l * lAdjust / 4 ;
//*/
c *= ( cAdjust > 0 ? 1.6 : 1.7 ) ** cAdjust ;
l *= ( lAdjust > 0 ? 1.2 : 1.35 ) ** lAdjust ;
chromaColor = chromaColor.set( 'hcl.c' , c ).set( 'hcl.l' , l ) ;
if ( ! fixRgb || ! chromaColor.clipped ) { return chromaColor ; }
// RGB is clipped and should be fixed.
// The most critical part is when the hue get changed, since it's arguably the most important information.
// Lightness is somewhat important too, but less than hue and a bit more than the Chroma.
// Chroma will be preserved if the adjustement is greater on it than on lightness.
//preserveLOverC = Math.abs( lAdjust ) >= Math.abs( cAdjust ) ;
preserveLOverC = Math.abs( lAdjust ) >= cAdjust ;
for ( ;; ) {
// chromaColor.clipped is not reliable since integer rounding counts as clipping...
rgb = chromaColor._rgb._unclipped ;
rgb.length = 3 ;
if ( rgb.every( channel => channel > -5 && channel < 260 ) ) { return chromaColor ; }
sortedChannels = [ ... rgb ].sort( ( a , b ) => a - b ) ;
//console.log( "Clipped!" , rgb , chromaColor.rgb() ) ;
if ( sortedChannels[ 2 ] >= 256 ) {
// Clipping will affect hue!
avg = ( sortedChannels[ 0 ] + sortedChannels[ 1 ] + sortedChannels[ 2 ] ) / 3 ;
if ( preserveLOverC ) {
// Desaturate a bit and retry
c = chromaColor.get( 'hcl.c' ) ;
c /= FIX_STEP ;
chromaColor = chromaColor.set( 'hcl.c' , c ) ;
}
else {
// Darken a bit and retry
l = chromaColor.get( 'hcl.l' ) ;
l /= FIX_STEP ;
chromaColor = chromaColor.set( 'hcl.l' , l ) ;
}
// It was too bright anyway, let it be clipped
if ( avg > 255 ) { return chromaColor ; }
}
else if ( sortedChannels[ 1 ] < 0 ) {
// Clipping will affect hue!
avg = ( sortedChannels[ 0 ] + sortedChannels[ 1 ] + sortedChannels[ 2 ] ) / 3 ;
if ( preserveLOverC ) {
// Desaturate a bit and retry
c = chromaColor.get( 'hcl.c' ) ;
c /= FIX_STEP ;
chromaColor = chromaColor.set( 'hcl.c' , c ) ;
}
else {
// Lighten a bit and retry
l = chromaColor.get( 'hcl.l' ) ;
l *= FIX_STEP ;
chromaColor = chromaColor.set( 'hcl.l' , l ) ;
}
// It was too dark anyway, let it be clipped
if ( avg < 0 ) { return chromaColor ; }
}
else {
// This clipping (lowest channel below 0) will not affect hue, only lightness, let it be clipped
return chromaColor ;
}
}
} ;
},{"./termkit.js":56}],2:[function(require,module,exports){
/*
Terminal Kit
Copyright (c) 2009 - 2022 Cédric Ronvel
The MIT License (MIT)
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.
*/
"use strict" ;
const termkit = require( './termkit.js' ) ;
/*
Rect: rectangular region, clipping, iterators for blitters, etc...
new Rect( xmin , ymin , xmax , ymax )
new Rect( object ) having properties: xmin , ymin , xmax , ymax
new Rect( Terminal )
new Rect( ScreenBuffer )
*/
function Rect( xmin , ymin , xmax , ymax ) {
var src = xmin ;
this.xmin = 0 ;
this.xmax = 0 ;
this.ymin = 0 ;
this.ymax = 0 ;
this.width = 0 ;
this.height = 0 ;
this.isNull = true ;
if ( src && ( typeof src === 'object' || typeof src === 'function' ) ) {
if ( src instanceof termkit.Terminal ) {
this.set( {
xmin: 1 ,
ymin: 1 ,
xmax: src.width ,
ymax: src.height
} ) ;
}
else if ( src instanceof termkit.ScreenBuffer ) {
this.set( {
xmin: 0 ,
ymin: 0 ,
xmax: src.width - 1 ,
ymax: src.height - 1
} ) ;
}
else if ( src instanceof termkit.TextBuffer ) {
this.set( {
xmin: 0 ,
ymin: 0 ,
xmax: src.width - 1 ,
ymax: src.height - 1
} ) ;
}
else if ( src instanceof Rect ) {
this.set( src ) ;
}
else if ( src.xmin !== undefined || src.ymin !== undefined || src.xmax !== undefined || src.ymax !== undefined ) {
this.set( {
xmin: src.xmin !== undefined ? src.xmin : 0 ,
ymin: src.ymin !== undefined ? src.ymin : 0 ,
xmax: src.xmax !== undefined ? src.xmax : 1 ,
ymax: src.ymax !== undefined ? src.ymax : 1
} ) ;
}
else if ( src.x !== undefined || src.y !== undefined || src.width !== undefined || src.height !== undefined ) {
this.set( {
xmin: src.x !== undefined ? src.x : 0 ,
ymin: src.y !== undefined ? src.y : 0 ,
xmax: src.width !== undefined ? src.x + src.width - 1 : 1 ,
ymax: src.height !== undefined ? src.y + src.height - 1 : 1
} ) ;
}
}
else {
this.set( {
xmin: xmin !== undefined ? xmin : 0 ,
ymin: ymin !== undefined ? ymin : 0 ,
xmax: xmax !== undefined ? xmax : 1 ,
ymax: ymax !== undefined ? ymax : 1
} ) ;
}
}
module.exports = Rect ;
// Backward compatibility
Rect.create = ( ... args ) => new Rect( ... args ) ;
Rect.prototype.set = function( data ) {
if ( data.xmin !== undefined ) { this.xmin = Math.floor( data.xmin ) ; }
if ( data.xmax !== undefined ) { this.xmax = Math.floor( data.xmax ) ; }
if ( data.ymin !== undefined ) { this.ymin = Math.floor( data.ymin ) ; }
if ( data.ymax !== undefined ) { this.ymax = Math.floor( data.ymax ) ; }
this.width = this.xmax - this.xmin + 1 ;
this.height = this.ymax - this.ymin + 1 ;
this.isNull = this.xmin > this.xmax || this.ymin > this.ymax ;
} ;
// Set the reset size, keeping *min and adjusting *max
Rect.prototype.setSize = function( data ) {
if ( data.width !== undefined ) {
this.width = Math.floor( data.width ) ;
this.xmax = this.xmin + this.width - 1 ;
}
if ( data.height !== undefined ) {
this.height = Math.floor( data.height ) ;
this.ymax = this.ymin + this.height - 1 ;
}
this.isNull = this.xmin > this.xmax || this.ymin > this.ymax ;
} ;
Rect.prototype.isInside = function( x , y ) {
return x >= this.xmin && x <= this.xmax && y >= this.ymin && y <= this.ymax ;
} ;
// Clip the src according to the dst, offset* are offsets of the srcRect relative to the dst coordinate system
Rect.prototype.clip = function( dstRect , offsetX , offsetY , dstClipping ) {
var srcRect = this ;
offsetX = offsetX || 0 ;
offsetY = offsetY || 0 ;
srcRect.set( {
xmin: Math.max( srcRect.xmin , dstRect.xmin - offsetX ) ,
ymin: Math.max( srcRect.ymin , dstRect.ymin - offsetY ) ,
xmax: Math.min( srcRect.xmax , dstRect.xmax - offsetX ) ,
ymax: Math.min( srcRect.ymax , dstRect.ymax - offsetY )
} ) ;
if ( dstClipping ) {
dstRect.set( {
xmin: Math.max( dstRect.xmin , srcRect.xmin + offsetX ) ,
ymin: Math.max( dstRect.ymin , srcRect.ymin + offsetY ) ,
xmax: Math.min( dstRect.xmax , srcRect.xmax + offsetX ) ,
ymax: Math.min( dstRect.ymax , srcRect.ymax + offsetY )
} ) ;
}
return this ;
} ;
// Merge with another Rect, enlarge the current Rect so that it includes the Rect argument
Rect.prototype.merge = function( rect ) {
this.set( {
xmin: Math.min( this.xmin , rect.xmin ) ,
ymin: Math.min( this.ymin , rect.ymin ) ,
xmax: Math.max( this.xmax , rect.xmax ) ,
ymax: Math.max( this.ymax , rect.ymax )
} ) ;
return this ;
} ;
/*
Given a srcRect, a dstRect, offsetX and offsetY, return an array of up to 4 objects consisting of the same properties
found in entry, wrapping the src into the dst, i.e. the src is always fully visible in the dst, it is just as if
the dst where circular
Mandatory params:
* dstRect
* srcRect
* offsetX
* offsetY
Optionnal params:
* wrapOnly: 'x' , 'y' (only wrap along that axis)
*/
Rect.wrappingRect = function( p ) {
var regions = [] , nw , ne , sw , se ;
// Originate, North-West region
nw = {
srcRect: new Rect( p.srcRect ) ,
dstRect: new Rect( p.dstRect ) ,
offsetX: p.offsetX ,
offsetY: p.offsetY
} ;
// Modulate offsets so they are in-range
if ( p.wrapOnly !== 'y' ) {
nw.offsetX = nw.offsetX % p.dstRect.width ;
if ( nw.offsetX < 0 ) { nw.offsetX += p.dstRect.width ; }
}
if ( p.wrapOnly !== 'x' ) {
nw.offsetY = nw.offsetY % p.dstRect.height ;
if ( nw.offsetY < 0 ) { nw.offsetY += p.dstRect.height ; }
}
// Mutual clipping
nw.srcRect.clip( nw.dstRect , nw.offsetX , nw.offsetY , true ) ;
if ( ! nw.srcRect.isNull ) { regions.push( nw ) ; }
// Wrap-x North-Est region
if ( nw.srcRect.width < p.srcRect.width && p.wrapOnly !== 'y' ) {
ne = {
srcRect: new Rect( p.srcRect ) ,
dstRect: new Rect( p.dstRect ) ,
offsetX: nw.offsetX - p.dstRect.width ,
offsetY: nw.offsetY
} ;
// Mutual clipping
ne.srcRect.clip( ne.dstRect , ne.offsetX , ne.offsetY , true ) ;
if ( ! ne.srcRect.isNull ) { regions.push( ne ) ; }
}
// Wrap-y South-West region
if ( nw.srcRect.height < p.srcRect.height && p.wrapOnly !== 'x' ) {
sw = {
srcRect: new Rect( p.srcRect ) ,
dstRect: new Rect( p.dstRect ) ,
offsetX: nw.offsetX ,
offsetY: nw.offsetY - p.dstRect.height
} ;
// Mutual clipping
sw.srcRect.clip( sw.dstRect , sw.offsetX , sw.offsetY , true ) ;
if ( ! sw.srcRect.isNull ) { regions.push( sw ) ; }
}
// Wrap-x + wrap-y South-Est region, do it only if it has wrapped already
if ( ne && sw ) {
se = {
srcRect: new Rect( p.srcRect ) ,
dstRect: new Rect( p.dstRect ) ,
offsetX: nw.offsetX - p.dstRect.width ,
offsetY: nw.offsetY - p.dstRect.height
} ;
// Mutual clipping
se.srcRect.clip( se.dstRect , se.offsetX , se.offsetY , true ) ;
if ( ! se.srcRect.isNull ) { regions.push( se ) ; }
}
return regions ;
} ;
/*
This iterator generate synchronous line or cell for dst & src Rect.
It is totally buffer agnostic.
Buffer specificities should be added in p.context by the callee.
Iterator.
Mandatory params:
* dstRect: Rect describing the dst geometry
* srcRect: Rect describing the src geometry
* type: 'line' or 'cell'
Optionnal params:
* context: an object that will be transmitted as is to the iterator
* dstClipRect: a clipping Rect for the dst
* srcClipRect: a clipping Rect for the src
* offsetX: the X-offset of the origin of the srcRect relative to the dst coordinate system
* offsetY: the Y-offset of the origin of the srcRect relative to the dst coordinate system
* multiply: the byte size of a cell by which all offset should be multiplied
*/
Rect.regionIterator = function( p , iterator ) {
var i , j , srcX , srcY , dstX , dstY , srcStart , dstStart , isFullWidth ;
if ( ! p.multiply ) { p.multiply = 1 ; }
if ( ! p.offsetX ) { p.offsetX = 0 ; }
if ( ! p.offsetY ) { p.offsetY = 0 ; }
if ( p.dstClipRect ) { p.dstClipRect.clip( p.dstRect ) ; }
else { p.dstClipRect = new Rect( p.dstRect ) ; }
if ( p.srcClipRect ) { p.srcClipRect.clip( p.srcRect ) ; }
else { p.srcClipRect = new Rect( p.srcRect ) ; }
// Mutual clipping
p.srcClipRect.clip( p.dstClipRect , p.offsetX , p.offsetY , true ) ;
// If out of bounds, or if everything is clipped away, return now
if ( p.dstRect.isNull || p.srcClipRect.isNull || p.dstClipRect.isNull ) { return ; }
switch ( p.type ) {
case 'line' :
for ( j = 0 ; j < p.srcClipRect.height ; j ++ ) {
srcY = p.srcClipRect.ymin + j ;
dstY = p.dstClipRect.ymin + j ;
iterator( {
context: p.context ,
srcXmin: p.srcClipRect.xmin ,
srcXmax: p.srcClipRect.xmax ,
srcY: srcY ,
srcStart: ( srcY * p.srcRect.width + p.srcClipRect.xmin ) * p.multiply ,
srcEnd: ( srcY * p.srcRect.width + p.srcClipRect.xmax + 1 ) * p.multiply ,
dstXmin: p.dstClipRect.xmin ,
dstXmax: p.dstClipRect.xmax ,
dstY: dstY ,
dstStart: ( dstY * p.dstRect.width + p.dstClipRect.xmin ) * p.multiply ,
dstEnd: ( dstY * p.dstRect.width + p.dstClipRect.xmax + 1 ) * p.multiply
//, lastLine: j === p.srcClipRect.height - 1
} ) ;
}
break ;
case 'reversedLine' :
// Same than 'line' but start from the last line.
// Useful for copying overlapping region of the same buffer, when the src rect has a lower Y-offset than the dst rect.
for ( j = p.srcClipRect.height - 1 ; j >= 0 ; j -- ) {
srcY = p.srcClipRect.ymin + j ;
dstY = p.dstClipRect.ymin + j ;
iterator( {
context: p.context ,
srcXmin: p.srcClipRect.xmin ,
srcXmax: p.srcClipRect.xmax ,
srcY: srcY ,
srcStart: ( srcY * p.srcRect.width + p.srcClipRect.xmin ) * p.multiply ,
srcEnd: ( srcY * p.srcRect.width + p.srcClipRect.xmax + 1 ) * p.multiply ,
dstXmin: p.dstClipRect.xmin ,
dstXmax: p.dstClipRect.xmax ,
dstY: dstY ,
dstStart: ( dstY * p.dstRect.width + p.dstClipRect.xmin ) * p.multiply ,
dstEnd: ( dstY * p.dstRect.width + p.dstClipRect.xmax + 1 ) * p.multiply
} ) ;
}
break ;
case 'cell' :
for ( j = 0 ; j < p.srcClipRect.height ; j ++ ) {
for ( i = 0 ; i < p.srcClipRect.width ; i ++ ) {
srcX = p.srcClipRect.xmin + i ;
srcY = p.srcClipRect.ymin + j ;
dstX = p.dstClipRect.xmin + i ;
dstY = p.dstClipRect.ymin + j ;
srcStart = ( srcY * p.srcRect.width + srcX ) * p.multiply ;
dstStart = ( dstY * p.dstRect.width + dstX ) * p.multiply ;
isFullWidth = iterator( {
context: p.context ,
srcX: srcX ,
srcY: srcY ,
srcStart: srcStart ,
srcEnd: srcStart + p.multiply ,
dstX: dstX ,
dstY: dstY ,
dstStart: dstStart ,
dstEnd: dstStart + p.multiply ,
startOfBlitLine: ! i ,
endOfBlitLine: i === p.srcClipRect.width - 1
} ) ;
if ( isFullWidth ) { i ++ ; }
}
}
break ;
}
} ;
/*
This is the tile-variant of the regionIterator.
Iterator.
Mandatory params:
* dstRect
* srcRect
* type: 'line' or 'cell'
Optionnal params:
* context: an object that will be transmitted as is to the iterator
* dstClipRect
* srcClipRect
* offsetX
* offsetY
* multiply
*/
Rect.tileIterator = function( p , iterator ) {
var srcI , srcJ , srcX , srcY , dstI , dstJ , dstX , dstY , streak , srcStart , dstStart ;
if ( ! p.multiply ) { p.multiply = 1 ; }
if ( ! p.offsetX ) { p.offsetX = 0 ; }
if ( ! p.offsetY ) { p.offsetY = 0 ; }
if ( p.dstClipRect ) { p.dstClipRect.clip( p.dstRect ) ; }
else { p.dstClipRect = new Rect( p.dstRect ) ; }
if ( p.srcClipRect ) { p.srcClipRect.clip( p.srcRect ) ; }
else { p.srcClipRect = new Rect( p.srcRect ) ; }
switch ( p.type ) {
case 'cell' :
for ( dstJ = 0 ; dstJ < p.dstClipRect.height ; dstJ ++ ) {
srcJ = ( dstJ - p.offsetY ) % p.srcClipRect.height ;
if ( srcJ < 0 ) { srcJ += p.srcClipRect.height ; }
for ( dstI = 0 ; dstI < p.dstClipRect.width ; dstI ++ ) {
srcI = ( dstI - p.offsetX ) % p.srcClipRect.width ;
if ( srcI < 0 ) { srcI += p.srcClipRect.width ; }
srcX = p.srcClipRect.xmin + srcI ;
srcY = p.srcClipRect.ymin + srcJ ;
dstX = p.dstClipRect.xmin + dstI ;
dstY = p.dstClipRect.ymin + dstJ ;
srcStart = ( srcY * p.srcRect.width + srcX ) * p.multiply ;
dstStart = ( dstY * p.dstRect.width + dstX ) * p.multiply ;
iterator( {
context: p.context ,
srcX: srcX ,
srcY: srcY ,
srcStart: srcStart ,
srcEnd: srcStart + p.multiply ,
dstX: dstX ,
dstY: dstY ,
dstStart: dstStart ,
dstEnd: dstStart + p.multiply
} ) ;
}
}
break ;
case 'line' :
for ( dstJ = 0 ; dstJ < p.dstClipRect.height ; dstJ ++ ) {
srcJ = ( dstJ - p.offsetY ) % p.srcClipRect.height ;
if ( srcJ < 0 ) { srcJ += p.srcClipRect.height ; }
dstI = 0 ;
while ( dstI < p.dstClipRect.width ) {
srcI = ( dstI - p.offsetX ) % p.srcClipRect.width ;
if ( srcI < 0 ) { srcI += p.srcClipRect.width ; }
streak = Math.min( p.srcClipRect.width - srcI , p.dstClipRect.width - dstI ) ;
srcX = p.srcClipRect.xmin + srcI ;
srcY = p.srcClipRect.ymin + srcJ ;
dstX = p.dstClipRect.xmin + dstI ;
dstY = p.dstClipRect.ymin + dstJ ;
srcStart = ( srcY * p.srcRect.width + srcX ) * p.multiply ;
dstStart = ( dstY * p.dstRect.width + dstX ) * p.multiply ;
iterator( {
context: p.context ,
srcXmin: srcX ,
srcXmax: srcX + streak - 1 ,
srcY: srcY ,
srcStart: srcStart ,
srcEnd: srcStart + streak * p.multiply ,
dstXmin: dstX ,
dstXmax: dstX + streak - 1 ,
dstY: dstY ,
dstStart: dstStart ,
dstEnd: dstStart + streak * p.multiply
} ) ;
dstI += streak ;
}
}
break ;
}
} ;
/*
This is the wrap-variant of the regionIterator.
Iterator.
Mandatory params:
* dstRect
* srcRect
* type: 'line' or 'cell'
Optionnal params:
* context: an object that will be transmitted as is to the iterator
* dstClipRect
* srcClipRect
* offsetX
* offsetY
* multiply
* wrapOnly: 'x' , 'y' (only wrap along that axis)
*/
Rect.wrapIterator = function( p , iterator ) {
var i , regions ;
regions = Rect.wrappingRect( {
dstRect: p.dstClipRect ,
srcRect: p.srcClipRect ,
offsetX: p.offsetX ,
offsetY: p.offsetY ,
wrapOnly: p.wrap
} ) ;
for ( i = 0 ; i < regions.length ; i ++ ) {
p.dstClipRect = regions[ i ].dstRect ;
p.srcClipRect = regions[ i ].srcRect ;
p.offsetX = regions[ i ].offsetX ;
p.offsetY = regions[ i ].offsetY ;
Rect.regionIterator( p , iterator ) ;
}
} ;
},{"./termkit.js":56}],3:[function(require,module,exports){
(function (Buffer){(function (){
/*
Terminal Kit
Copyright (c) 2009 - 2022 Cédric Ronvel
The MIT License (MIT)
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.
*/
"use strict" ;
const misc = require( './misc.js' ) ;
const fs = require( 'fs' ) ;
const string = require( 'string-kit' ) ;
const NextGenEvents = require( 'nextgen-events' ) ;
/*
options:
* width: buffer width (default to dst.width)
* height: buffer height (default to dst.height)
* dst: writting destination
* inline: for terminal dst only, draw inline instead of at some position (do not moveTo)
* x: default position in the dst
* y: default position in the dst
* wrap: default wrapping behavior of .put()
* noFill: do not call .fill() with default values at ScreenBuffer creation
* blending: false/null or true or object (blending options): default blending params (can be overriden by .draw())
* palette: Palette instance
*/
function ScreenBuffer( options = {} ) {
this.dst = options.dst ; // a terminal or another screenBuffer
this.inline = !! options.inline ; // it's a terminal and we want to draw inline to it (no moveTo)
this.width = Math.floor( options.width ) || ( options.dst ? options.dst.width : 1 ) ;
this.height = Math.floor( options.height ) || ( options.dst ? options.dst.height : 1 ) ;
this.x = options.x !== undefined ? options.x : ( options.dst && options.dst instanceof termkit.Terminal ? 1 : 0 ) ; // eslint-disable-line
this.y = options.y !== undefined ? options.y : ( options.dst && options.dst instanceof termkit.Terminal ? 1 : 0 ) ; // eslint-disable-line
this.cx = 0 ;
this.cy = 0 ;
this.ch = false ; // cursor hidden
this.lastCh = null ; // cursor hidden on last terminal draw, avoid unecessary escape sequence output
this.lastBuffer = null ;
this.lastBufferUpToDate = false ;
this.blending = options.blending || false ;
this.wrap = !! options.wrap ;
this.buffer = Buffer.allocUnsafe( this.width * this.height * this.ITEM_SIZE ) ;
this.palette = options.palette || ( this.dst && this.dst.palette ) ;
if ( ! options.noFill ) { this.fill() ; }
}
module.exports = ScreenBuffer ;
ScreenBuffer.prototype = Object.create( NextGenEvents.prototype ) ;
ScreenBuffer.prototype.constructor = ScreenBuffer ;
ScreenBuffer.prototype.bitsPerColor = 8 ;
// Backward compatibility
ScreenBuffer.create = ( ... args ) => new ScreenBuffer( ... args ) ;
const termkit = require( './termkit.js' ) ;
const Rect = termkit.Rect ;
/*
options:
* attr: attributes passed to .put()
* transparencyChar: a char that is transparent
* transparencyType: bit flags for the transparency char
*/
ScreenBuffer.createFromString = function( options , data ) {
var x , y , length , attr , attrTrans , width , height , lineWidth , screenBuffer ;
// Manage options
if ( ! options ) { options = {} ; }
if ( typeof data !== 'string' ) {
if ( ! data.toString ) { throw new Error( '[terminal] ScreenBuffer.createFromDataString(): argument #1 should be a string or provide a .toString() method.' ) ; }
data = data.toString() ;
}
// Transform the data into an array of lines
data = termkit.stripControlChars( data , true ).split( '\n' ) ;
// Compute the buffer size
width = 0 ;
height = data.length ;
attr = options.attr !== undefined ? options.attr : ScreenBuffer.prototype.DEFAULT_ATTR ;
if ( attr && typeof attr === 'object' && ! attr.BYTES_PER_ELEMENT ) { attr = ScreenBuffer.object2attr( attr ) ; }
attrTrans = attr ;
if ( options.transparencyChar ) {
if ( ! options.transparencyType ) { attrTrans |= TRANSPARENCY ; }
else { attrTrans |= options.transparencyType & TRANSPARENCY ; }
}
// Compute the width of the screenBuffer
for ( y = 0 ; y < data.length ; y ++ ) {
lineWidth = string.unicode.width( data[ y ] ) ;
if ( lineWidth > width ) { width = lineWidth ; }
}
// Create the buffer with the right width & height
screenBuffer = new ScreenBuffer( { width: width , height: height } ) ;
// Fill the buffer with data
for ( y = 0 ; y < data.length ; y ++ ) {
if ( ! options.transparencyChar ) {
screenBuffer.put( { x: 0 , y: y , attr: attr } , data[ y ] ) ;
}
else {
length = data[ y ].length ;
for ( x = 0 ; x < length ; x ++ ) {
if ( data[ y ][ x ] === options.transparencyChar ) {
screenBuffer.put( { x: x , y: y , attr: attrTrans } , data[ y ][ x ] ) ;
}
else {
screenBuffer.put( { x: x , y: y , attr: attr } , data[ y ][ x ] ) ;
}
}
}
}
return screenBuffer ;
} ;
// Backward compatibility
ScreenBuffer.createFromChars = ScreenBuffer.createFromString ;
// Shared
ScreenBuffer.prototype.setClearAttr = function( attr ) {
this.CLEAR_ATTR = this.object2attr( attr ) ;
this.CLEAR_BUFFER = Buffer.allocUnsafe( this.ITEM_SIZE ) ;
if ( Buffer.isBuffer( this.CLEAR_ATTR ) ) {
// ScreenBufferHD
this.CLEAR_ATTR.copy( this.CLEAR_BUFFER ) ;
}
else { // if ( this.ATTR_SIZE === 4 ) {
this.CLEAR_BUFFER.writeInt32BE( this.CLEAR_ATTR , 0 ) ;
}
this.CLEAR_BUFFER.write( ' \x00\x00\x00' , this.ATTR_SIZE ) ; // space
} ;
/*
options:
attr: optional, the attribute to fill (default to DEFAULT_ATTR)
char: optional, the buffer will be filled with that char (default to space)
region: optional, a Rect compliant object defining the region to fill, instead a filling the whole ScreenBuffer
start: optional (internal), start offset
end: optional (internal), end offset
clearBuffer: optional (internal), a Buffer to use to clear (instead of char+attr)
buffer: optional (internal), used when we want to clear a Buffer instance, not a ScreenBuffer instance
*/
// Shared
ScreenBuffer.prototype.fill = function( options ) {
var i , attr , char , start , end , region ,
srcRect , toRect ,
clearBuffer = this.CLEAR_BUFFER ,
buffer = this.buffer ;
if ( options && typeof options === 'object' ) {
if ( options.char || options.attr ) {
clearBuffer = Buffer.allocUnsafe( this.ITEM_SIZE ) ;
// Write the attributes
attr = options.attr !== undefined ? options.attr : this.DEFAULT_ATTR ;
if ( attr && typeof attr === 'object' && ! attr.BYTES_PER_ELEMENT ) { attr = this.object2attr( attr ) ; }
this.writeAttr( clearBuffer , attr , 0 ) ;
// Write the character
char = options.char && typeof options.char === 'string' ? options.char : ' ' ;
//char = punycode.ucs2.encode( [ punycode.ucs2.decode( termkit.stripControlChars( char ) )[ 0 ] ] ) ;
char = string.unicode.firstChar( termkit.stripControlChars( char ) ) ;
//clearBuffer.write( char , this.ATTR_SIZE , this.CHAR_SIZE ) ;
this.writeChar( clearBuffer , char , 0 ) ;
}
else if ( options.clearBuffer ) {
clearBuffer = options.clearBuffer ;
}
// This option is used when we want to clear a Buffer instance, not a ScreenBuffer instance
if ( options.buffer ) { buffer = options.buffer ; }
start = options.start ? Math.floor( options.start / this.ITEM_SIZE ) : 0 ;
end = options.end ? Math.floor( options.end / this.ITEM_SIZE ) : buffer.length / this.ITEM_SIZE ;
region = options.region ? options.region : null ;
}
else {
start = 0 ;
end = buffer.length / this.ITEM_SIZE ;
}
if ( region ) {
srcRect = new Rect( 0 , 0 , 0 , 0 ) ;
toRect = new Rect( region ) ;
toRect.clip( new Rect( this ) ) ;
if ( toRect.isNull ) { return ; }
// We use the blitter to fill the region
Rect.tileIterator( {
type: 'line' ,
context: { srcBuffer: clearBuffer , dstBuffer: this.buffer } ,
srcRect: srcRect ,
dstRect: new Rect( this ) ,
dstClipRect: toRect ,
multiply: this.ITEM_SIZE
} , this.blitterLineIterator.bind( this ) ) ;
}
else {
for ( i = start ; i < end ; i ++ ) {
clearBuffer.copy( buffer , i * this.ITEM_SIZE ) ;
}
}
} ;
// Clear the buffer: fill it with blank
// Shared
ScreenBuffer.prototype.clear = ScreenBuffer.prototype.fill ;
ScreenBuffer.prototype.preserveMarkupFormat = misc.preserveMarkupFormat ;
ScreenBuffer.prototype.parseMarkup = string.markupMethod.bind( misc.markupOptions ) ;
/*
put( options , str )
put( options , format , [arg1] , [arg2] , ... )
options:
* x: bypass this.cx
* y: bypass this.cy
* markup: boolean or 'ansi' or 'legacyAnsi', true if the text contains markup that should be interpreted,
'ansi' if it contains ansi code, 'legacyAnsi' is bold is bright fg and blink is bright bg
* attr: standard attributes
* resumeAttr: (internal) attr code to resume to
* wrap: text wrapping, when the cursor move beyond the last column, it is moved to the begining of the next line
* newLine: if true, then \r and \n produce new lines, false by default: .put() does not manage lines
* clip: if set, it is object describing the clipping area, nothing is written outside of it,
and if the 'wrap' option is set, wrapping is done on its boundary.
Properties:
* x
* y
* width
* height
* clipChar: a char that is display just before clipping something
* direction: 'right' (default), 'left', 'up', 'down' or 'none'/null (do not move after puting a char)
* dx: x increment after each character (default: 1)
* dy: y increment after each character (default: 0)
*/
// Shared
ScreenBuffer.prototype.put = function( options , str , ... args ) {
var startX , startY , x , y , dx , dy , baseAttr , attr , attrObject , wrap ,
lastValidOffset = -1 ,
xmin = 0 ,
ymin = 0 ,
xmax = this.width - 1 ,
ymax = this.height - 1 ;
// Manage options
if ( ! options ) { options = {} ; }
wrap = options.wrap !== undefined ? options.wrap : this.wrap ;
startX = x = Math.floor( options.x !== undefined ? options.x : this.cx ) ;
startY = y = Math.floor( options.y !== undefined ? options.y : this.cy ) ;
if ( options.clip ) {
xmin = options.clip.x ;
ymin = options.clip.y ;
xmax = xmin + options.clip.width - 1 ;
ymax = ymin + options.clip.height - 1 ;
}
// Process directions/increments
switch ( options.direction ) {
//case 'right' : // not needed, use the default dx & dy
case 'left' :
dx = -1 ;
break ;
case 'up' :
dx = 0 ;
dy = -1 ;
break ;
case 'down' :
dx = 0 ;
dy = 1 ;
break ;
case null :
case 'none' :
dx = 0 ;
dy = 0 ;
break ;
case 'right' :
default :
dx = 1 ;
dy = 0 ;
break ;
}
// Overide
if ( typeof options.dx === 'number' ) { dx = options.dx ; }
if ( typeof options.dy === 'number' ) { dy = options.dy ; }
// Process attributes
attr = options.attr !== undefined ? options.attr : this.DEFAULT_ATTR ;
if ( attr && typeof attr === 'object' && ! attr.BYTES_PER_ELEMENT ) { attr = this.object2attr( attr ) ; }
baseAttr = attr ;
// It's already in the correct format
if ( options.resumeAttr !== undefined ) { attr = options.resumeAttr ; }
// Process the input string
if ( typeof str !== 'string' ) {
if ( str.toString ) { str = str.toString() ; }
else { return ; }
}
if ( args.length ) {
str = options.markup === true ? this.preserveMarkupFormat( str , ... args ) : string.format( str , ... args ) ;
}
// The processing of raw chunk of text
var processRaw = part => {
//part = termkit.stripControlChars( part ) ;
var characters = string.unicode.toArray( part ) ,
iMax = characters.length ;
for ( let i = 0 ; i < iMax ; i ++ ) {
let offset = ( y * this.width + x ) * this.ITEM_SIZE ;
let char = characters[ i ] ;
let charCode = char.charCodeAt( 0 ) ;
if ( charCode < 0x20 || charCode === 0x7f ) {
if ( options.newLine && ( charCode === 0x0a || charCode === 0x0d ) ) {
if ( dx ) {
x = startX ;
y ++ ;
}
else {
y = startY ;
x ++ ;
}
continue ;
}
else {
char = ' ' ; // Space
charCode = 0x20 ;
}
}
let isFullWidth = string.unicode.isFullWidth( char ) ;
let inBounds = false ;
if ( y >= ymin && y <= ymax ) {
if ( isFullWidth ) {
if ( x + isFullWidth * dx >= xmin && x + isFullWidth * ( dx || 1 ) <= xmax ) {
// This is a full-width char! Needs extra care!
if ( dx < 0 ) { offset -= this.ITEM_SIZE ; }
lastValidOffset = offset ;
// Check if we are writing on a fullwidth char
if ( this.hasTrailingFullWidth( this.buffer , offset ) && x ) { this.removeFullWidth( this.buffer , offset - this.ITEM_SIZE ) ; }
// Write the attributes
this.writeAttr( this.buffer , attr , offset , this.LEADING_FULLWIDTH ) ;
// Write the character
this.writeChar( this.buffer , char , offset ) ;
offset += this.ITEM_SIZE ;
// Check if we are writing on a fullwidth char
if ( this.hasLeadingFullWidth( this.buffer , offset ) && x < xmax ) { this.removeFullWidth( this.buffer , offset + this.ITEM_SIZE ) ; }
// Write the attributes
this.writeAttr( this.buffer , attr , offset , this.TRAILING_FULLWIDTH ) ;
// Write a blank character
this.writeChar( this.buffer , ' ' , offset ) ;
inBounds = true ;
}
}
else {
if ( x >= xmin && x <= xmax ) {
// Check if we are writing on a fullwidth char
if ( this.hasLeadingFullWidth( this.buffer , offset ) && x < xmax ) { this.removeFullWidth( this.buffer , offset + this.ITEM_SIZE ) ; }
else if ( this.hasTrailingFullWidth( this.buffer , offset ) && x ) { this.removeFullWidth( this.buffer , offset - this.ITEM_SIZE ) ; }
// Write the attributes
this.writeAttr( this.buffer , attr , offset ) ;
// Write the character
this.writeChar( this.buffer , char , offset ) ;
lastValidOffset = offset ;
inBounds = true ;
}
}
}
else {
// Optimization : early out when there is no chance anymore char would be put...
if ( ( y > ymax && dy >= 0 ) || ( y < ymin && dy < 0 ) ) {
return true ;
}
}
if ( ! inBounds && lastValidOffset >= 0 && options.clipChar ) {
// Check if we are writing on a fullwidth char
if ( this.hasLeadingFullWidth( this.buffer , lastValidOffset ) && x < xmax ) { this.removeFullWidth( this.buffer , lastValidOffset + this.ITEM_SIZE ) ; }
else if ( this.hasTrailingFullWidth( this.buffer , lastValidOffset ) && x ) { this.removeFullWidth( this.buffer , lastValidOffset - this.ITEM_SIZE ) ; }
// Write the character
this.writeChar( this.buffer , options.clipChar , lastValidOffset ) ;
lastValidOffset = -1 ;
}
x += dx * ( 1 + isFullWidth ) ;
y += dy ;
if ( wrap ) {
if ( x < xmin ) {
x = xmax ;
y -- ;
}
else if ( x > xmax ) {
x = xmin ;
y ++ ;
}
}
}
} ;
if ( ! options.markup ) {
processRaw( str ) ;
}
else {
const defaultAttrObject = this.attr2object( this.DEFAULT_ATTR ) ;
const baseAttrObject = this.attr2object( baseAttr ) ;
let legacyColor = false ;
let parts = null ;
switch ( options.markup ) {
case 'ansi' :
parts = string.ansi.parse( str ) ;
break ;
case 'legacyAnsi' :
parts = string.ansi.parse( str ) ;
legacyColor = true ;
break ;
case true :
parts = this.parseMarkup( str ) ;
break ;
}
for ( let part of parts ) {
attrObject = Object.assign( {} , part.specialReset ? defaultAttrObject : baseAttrObject , part ) ;
delete attrObject.text ;
// Remove incompatible flags
if ( attrObject.defaultColor && attrObject.color ) { delete attrObject.defaultColor ; }
if ( attrObject.bgDefaultColor && attrObject.bgColor ) { delete attrObject.bgDefaultColor ; }
attr = this.object2attr( attrObject , undefined , legacyColor ) ;
if ( part.text ) {
if ( processRaw( part.text ) ) { break ; }
}
}
}
this.cx = x ;
this.cy = y ;
return attr ;
} ;
/*
options:
* x: bypass this.cx
* y: bypass this.cy
*/
// Shared
ScreenBuffer.prototype.get = function( options ) {
var x , y , offset ;
// Manage options
if ( ! options ) { options = {} ; }
x = options.x !== undefined ? options.x : this.cx ;
y = options.y !== undefined ? options.y : this.cy ;
if ( typeof x !== 'number' || x < 0 || x >= this.width ) { return null ; }
x = Math.floor( x ) ;
if ( typeof y !== 'number' || y < 0 || y >= this.height ) { return null ; }
y = Math.floor( y ) ;
offset = ( y * this.width + x ) * this.ITEM_SIZE ;
return {
attr: this.attr2object( this.readAttr( this.buffer , offset ) ) ,
char: this.readChar( this.buffer , offset )
} ;
} ;
// Resize a screenBuffer, using a Rect
// Shared
ScreenBuffer.prototype.resize = function( fromRect ) {
// Do not reference directly the userland variable, clone it
fromRect = new Rect( fromRect ) ;
var offsetX = -fromRect.xmin ,
offsetY = -fromRect.ymin ;
// Create the toRect region
var toRect = new Rect( {
xmin: 0 ,
ymin: 0 ,
xmax: fromRect.width - 1 ,
ymax: fromRect.height - 1
} ) ;
fromRect.clip( new Rect( this ) ) ;
if ( toRect.isNull ) { return false ; }
// Generate a new buffer
var resizedBuffer = Buffer.allocUnsafe( toRect.width * toRect.height * this.ITEM_SIZE ) ;
this.fill( { buffer: resizedBuffer } ) ;
// We use the blitter to reconstruct the buffer geometry
Rect.regionIterator( {
type: 'line' ,
context: { srcBuffer: this.buffer , dstBuffer: resizedBuffer } ,
dstRect: toRect ,
dstClipRect: new Rect( toRect ) ,
srcRect: new Rect( this ) ,
srcClipRect: fromRect ,
offsetX: offsetX ,
offsetY: offsetY ,
multiply: this.ITEM_SIZE
} , this.blitterLineIterator.bind( this ) ) ;
// Now, we have to replace the old buffer with the new, and set the width & height
this.width = toRect.width ;
this.height = toRect.height ;
this.buffer = resizedBuffer ;
// Disable the lastBuffer, so `draw( { delta: true } )` will not be bugged
this.lastBuffer = null ;
// This exists to improve compatibilities with the Terminal object
this.emit( 'resize' , this.width , this.height ) ;
return true ;
} ;
// Shared
ScreenBuffer.prototype.draw = function( options ) {
if ( ! options || typeof options !== 'object' ) { options = {} ; }
// Transmitted options (do not edit the user provided options, clone them)
var tr = {
dst: options.dst || this.dst ,
inline: options.inline !== undefined ? !! options.inline : this.inline ,
offsetX: options.x !== undefined ? Math.floor( options.x ) : Math.floor( this.x ) ,
offsetY: options.y !== undefined ? Math.floor( options.y ) : Math.floor( this.y ) ,
dstClipRect: options.dstClipRect ? new Rect( options.dstClipRect ) : undefined ,
srcClipRect: options.srcClipRect ? new Rect( options.srcClipRect ) : undefined ,
delta: options.delta ,
blending: options.blending !== undefined ? options.blending : this.blending ,
wrap: options.wrap ,
tile: options.tile
} ;
if ( tr.dst instanceof ScreenBuffer ) {
return this.blitter( tr ) ;
}
else if ( tr.dst instanceof termkit.Terminal ) {
return this.terminalBlitter( tr ) ;
}
} ;
// Shared
ScreenBuffer.prototype.moveTo = function( x , y ) {
this.cx = Math.max( 0 , Math.min( x , this.width - 1 ) ) ;
this.cy = Math.max( 0 , Math.min( y , this.height - 1 ) ) ;
} ;
// Shared
ScreenBuffer.prototype.drawCursor = function( options ) {
if ( ! options || typeof options !== 'object' ) { options = {} ; }
var dst = options.dst || this.dst ;
if ( dst instanceof ScreenBuffer ) {
if ( this.ch ) {
dst.ch = true ;
}
else {
dst.ch = false ;
dst.moveTo( this.cx + this.x , this.cy + this.y ) ;
}
}
else if ( dst instanceof termkit.Terminal ) {
if ( this.ch ) {
if ( this.ch !== this.lastCh ) { dst.hideCursor() ; }
}
else {
if ( this.ch !== this.lastCh ) { dst.hideCursor( false ) ; }
dst.moveTo(
Math.max( 1 , Math.min( this.cx + this.x , dst.width ) ) ,
Math.max( 1 , Math.min( this.cy + this.y , dst.height ) )
) ;
}
this.lastCh = this.ch ;
}
} ;
// Shared
ScreenBuffer.prototype.blitter = function( p ) {
var tr , iterator , iteratorCallback ;
// Default options & iterator
tr = {
type: 'line' ,
context: { srcBuffer: this.buffer , dstBuffer: p.dst.buffer , blending: p.blending } ,
dstRect: new Rect( p.dst ) ,
srcRect: new Rect( this ) ,
dstClip