Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
n/a
0 / 0
100.00% covered (success)
100.00%
16 / 16
CRAP
100.00% covered (success)
100.00%
98 / 98
Aviat\Kilo\has_tput
n/a
0 / 0
1
n/a
0 / 0
Aviat\Kilo\get_window_size
n/a
0 / 0
7
n/a
0 / 0
Aviat\Kilo\ctrl_key
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
Aviat\Kilo\is_ascii
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
Aviat\Kilo\is_ctrl
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
2 / 2
Aviat\Kilo\is_digit
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
2 / 2
Aviat\Kilo\is_space
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
7 / 7
Aviat\Kilo\get_ffi
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
Aviat\Kilo\is_separator
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
4 / 4
Aviat\Kilo\read_stdin
n/a
0 / 0
1
n/a
0 / 0
Aviat\Kilo\write_stdout
n/a
0 / 0
2
n/a
0 / 0
Aviat\Kilo\array_replace_range
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
Aviat\Kilo\str_contains
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
5 / 5
Aviat\Kilo\syntax_to_color
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
14 / 14
Aviat\Kilo\tabs_to_spaces
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
Aviat\Kilo\get_file_syntax_map
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
48 / 48
<?php declare(strict_types=1);
namespace Aviat\Kilo;
use FFI;
use Aviat\Kilo\Enum\{C, Color, Highlight, KeyCode};
/**
 * See if tput exists for fallback terminal size detection
 *
 * @return bool
 * @codeCoverageIgnore
 */
function has_tput(): bool
{
    return str_contains(shell_exec('type tput'), ' is ');
}
// ----------------------------------------------------------------------------
// ! Terminal size
// ----------------------------------------------------------------------------
/**
 * Get the size of the current terminal window
 *
 * @codeCoverageIgnore
 * @return array
 */
function get_window_size(): array
{
    // First, try to get the answer from ioctl
    $ffi = get_ffi();
    $ws = $ffi->new('struct winsize');
    $res = $ffi->ioctl(C::STDOUT_FILENO, C::TIOCGWINSZ, FFI::addr($ws));
    if ($res === 0 && $ws->ws_col !== 0 && $ws->ws_row !== 0)
    {
        return [$ws->ws_row, $ws->ws_col];
    }
    // Try using tput
    if (has_tput())
    {
        $rows = (int)trim(shell_exec('tput lines'));
        $cols = (int)trim(shell_exec('tput cols'));
        if ($rows > 0 && $cols > 0)
        {
            return [$rows, $cols];
        }
    }
    // Worst-case, return an arbitrary 'standard' size
    return [25, 80];
}
// ----------------------------------------------------------------------------
// ! C function/macro equivalents
// ----------------------------------------------------------------------------
/**
 * Do bit twiddling to convert a letter into
 * its Ctrl-letter equivalent ordinal ascii value
 *
 * @param string $char
 * @return int
 */
function ctrl_key(string $char): int
{
    if ( ! is_ascii($char))
    {
        return -1;
    }
    // b1,100,001 (a) & b0,011,111 (0x1f) = b0,000,001 (SOH)
    // b1,100,010 (b) & b0,011,111 (0x1f) = b0,000,010 (STX)
    // ...and so on
    return ord($char) & 0x1f;
}
/**
 * Does the one-character string contain an ascii ordinal value?
 *
 * @param string $single_char
 * @return bool
 */
function is_ascii(string $single_char): bool
{
    if (strlen($single_char) > 1)
    {
        return FALSE;
    }
    return ord($single_char) < 0x80;
}
/**
 * Does the one-character string contain an ascii control character?
 *
 * @param string $char
 * @return bool
 */
function is_ctrl(string $char): bool
{
    $c = ord($char);
    return is_ascii($char) && ( $c === 0x7f || $c < 0x20 );
}
/**
 * Does the one-character string contain an ascii number?
 *
 * @param string $char
 * @return bool
 */
function is_digit(string $char): bool
{
    $c = ord($char);
    return is_ascii($char) && ( $c > 0x2f && $c < 0x3a );
}
/**
 * Does the one-character string contain ascii whitespace?
 *
 * @param string $char
 * @return bool
 */
function is_space(string $char): bool
{
    $ws = [
        KeyCode::CARRIAGE_RETURN,
        KeyCode::FORM_FEED,
        KeyCode::NEWLINE,
        KeyCode::SPACE,
        KeyCode::TAB,
        KeyCode::VERTICAL_TAB,
    ];
    return is_ascii($char) && in_array($char, $ws, TRUE);
}
// ----------------------------------------------------------------------------
// ! Helper functions
// ----------------------------------------------------------------------------
/**
 * A 'singleton' function to replace a global variable
 *
 * @return FFI
 */
function get_ffi(): FFI
{
    static $ffi;
    if ($ffi === NULL)
    {
        $ffi = FFI::load(__DIR__ . '/ffi.h');
    }
    return $ffi;
}
/**
 * Does the one-character string contain a character that separates tokens?
 *
 * @param string $char
 * @return bool
 */
function is_separator(string $char): bool
{
    if ( ! is_ascii($char))
    {
        return FALSE;
    }
    $isSep = str_contains(',.()+-/*=~%<>[];', $char);
    return is_space($char) || $char === KeyCode::NULL || $isSep;
}
/**
 * Pull input from the stdin stream.
 *
 * @codeCoverageIgnore
 * @param int $len
 * @return string
 */
function read_stdin(int $len = 128): string
{
    $handle = fopen('php://stdin', 'rb');
    $input = fread($handle, $len);
    fclose($handle);
    return $input;
}
/**
 * Write to the stdout stream
 *
 * @codeCoverageIgnore
 * @param string $str
 * @param int|NULL $len
 * @return int
 */
function write_stdout(string $str, int $len = NULL): int
{
    $handle = fopen('php://stdout', 'ab');
    $res = (is_int($len))
        ? fwrite($handle, $str, $len)
        : fwrite($handle, $str);
    fclose($handle);
    return $res;
}
/**
 * Replaces a slice of an array with the same value
 *
 * @param array $array The array to update
 * @param int $offset The index of the first location to update
 * @param int $length The number of indices to update
 * @param mixed $value The value to replace in the range
 */
function array_replace_range(array &$array, int $offset, int $length, $value):void
{
    if ($length === 1)
    {
        $array[$offset] = $value;
        return;
    }
    $replacement = array_fill(0, $length, $value);
    array_splice($array, $offset, $length, $replacement);
}
/**
 * Does the string $haystack contain $str, optionally searching from $offset?
 *
 * @param string $haystack
 * @param string $str
 * @param int|null $offset
 * @return bool
 */
function str_contains(string $haystack, string $str, ?int $offset = NULL): bool
{
    if (empty($str))
    {
        return FALSE;
    }
    return ($offset !== NULL)
        ? strpos($haystack, $str, $offset) !== FALSE
        : strpos($haystack, $str) !== FALSE;
}
/**
 * Get the ASCII color escape number for the specified syntax type
 *
 * @param int $hl
 * @return int
 */
function syntax_to_color(int $hl): int
{
    $map = [
        Highlight::COMMENT => Color::FG_CYAN,
        Highlight::ML_COMMENT => Color::FG_BRIGHT_BLACK,
        Highlight::KEYWORD1 => Color::FG_YELLOW,
        Highlight::KEYWORD2 => Color::FG_GREEN,
        Highlight::STRING => Color::FG_MAGENTA,
        Highlight::NUMBER => Color::FG_RED,
        Highlight::OPERATOR => Color::FG_BRIGHT_GREEN,
        Highlight::VARIABLE => Color::FG_BRIGHT_CYAN,
        Highlight::DELIMITER => Color::FG_BLUE,
        Highlight::INVALID => Color::BG_BRIGHT_RED,
        Highlight::MATCH => Color::INVERT,
    ];
    return (array_key_exists($hl, $map))
        ? $map[$hl]
        : Color::FG_WHITE;
}
/**
 * Replace tabs with the specified number of spaces.
 *
 * @param string $str
 * @param int? $number
 * @return string
 */
function tabs_to_spaces(string $str, ?int $number = KILO_TAB_STOP): string
{
    return str_replace(KeyCode::TAB, str_repeat(KeyCode::SPACE, $number), $str);
}
/**
 * Generate/Get the syntax highlighting objects
 *
 * @return array
 */
function get_file_syntax_map(): array
{
    static $db = [];
    if (count($db) === 0)
    {
        $db = [
            Syntax::new(
                'C',
                ['.c', '.h', '.cpp'],
                [
                    'continue', 'typedef', 'switch', 'return', 'static', 'while', 'break', 'struct',
                    'union', 'class', 'else', 'enum', 'for', 'case', 'if',
                ],
                [
                    '#include', 'unsigned', '#define', '#ifndef', 'double', 'signed', '#endif',
                    '#ifdef', 'float', '#error', '#undef', 'long', 'char', 'int', 'void', '#if',
                ],
                '//',
                '/*',
                '*/',
                Syntax::HIGHLIGHT_NUMBERS | Syntax::HIGHLIGHT_STRINGS,
            ),
            Syntax::new(
                'CSS',
                ['.css', '.less', '.sass', 'scss'],
                [],
                [],
                '',
                '/*',
                '*/',
                Syntax::HIGHLIGHT_NUMBERS | Syntax::HIGHLIGHT_STRINGS,
            ),
            Syntax::new(
                'JavaScript',
                ['.js', '.jsx', '.ts', '.tsx', '.jsm', '.mjs', '.es'],
                [
                    'instanceof',
                    'continue',
                    'debugger',
                    'function',
                    'default',
                    'extends',
                    'finally',
                    'delete',
                    'export',
                    'import',
                    'return',
                    'switch',
                    'typeof',
                    'break',
                    'catch',
                    'class',
                    'const',
                    'super',
                    'throw',
                    'while',
                    'yield',
                    'case',
                    'else',
                    'this',
                    'void',
                    'with',
                    'from',
                    'for',
                    'new',
                    'try',
                    'var',
                    'do',
                    'if',
                    'in',
                    'as',
                ],
                [
                    '=>', 'Number', 'String', 'Object', 'Math', 'JSON', 'Boolean',
                ],
                '//',
                '/*',
                '*/',
                Syntax::HIGHLIGHT_NUMBERS | Syntax::HIGHLIGHT_STRINGS,
            ),
            Syntax::new(
                'PHP',
                ['.php', 'kilo'],
                [
                    '?php', '$this', '__halt_compiler', 'abstract', 'and', 'array', 'as', 'break',
                    'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare',
                    'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor',
                    'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends',
                    'final', 'finally', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements',
                    'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'isset', 'list',
                    'namespace', 'new', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once',
                    'return', 'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use', 'var', 'while', 'xor',
                    'yield', 'yield from', '__CLASS__', '__DIR__', '__FILE__', '__FUNCTION__', '__LINE__',
                    '__METHOD__', '__NAMESPACE__', '__TRAIT__',
                ],
                [
                    'int', 'float', 'bool', 'string', 'true', 'TRUE', 'false', 'FALSE', 'null', 'NULL',
                    'void', 'iterable', 'object', 'strict_types'
                ],
                '//',
                '/*',
                '*/',
                Syntax::HIGHLIGHT_NUMBERS | Syntax::HIGHLIGHT_STRINGS,
            ),
            Syntax::new(
                'Rust',
                ['.rs'],
                [
                    'continue', 'return', 'static', 'struct', 'unsafe', 'break', 'const', 'crate',
                    'extern', 'match', 'super', 'trait', 'where', 'else', 'enum', 'false', 'impl',
                    'loop', 'move', 'self', 'type', 'while', 'for', 'let', 'mod', 'pub', 'ref', 'true',
                    'use', 'mut', 'as', 'fn', 'if', 'in',
                ],
                [
                    'DoubleEndedIterator',
                    'ExactSizeIterator',
                    'IntoIterator',
                    'PartialOrd',
                    'PartialEq',
                    'Iterator',
                    'ToString',
                    'Default',
                    'ToOwned',
                    'Extend',
                    'FnOnce',
                    'Option',
                    'String',
                    'AsMut',
                    'AsRef',
                    'Clone',
                    'Debug',
                    'FnMut',
                    'Sized',
                    'Unpin',
                    'array',
                    'isize',
                    'usize',
                    '&str',
                    'Copy',
                    'Drop',
                    'From',
                    'Into',
                    'None',
                    'Self',
                    'Send',
                    'Some',
                    'Sync',
                    'Sync',
                    'bool',
                    'char',
                    'i128',
                    'u128',
                    'Box',
                    'Err',
                    'Ord',
                    'Vec',
                    'dyn',
                    'f32',
                    'f64',
                    'i16',
                    'i32',
                    'i64',
                    'str',
                    'u16',
                    'u32',
                    'u64',
                    'Eq',
                    'Fn',
                    'Ok',
                    'i8',
                    'u8',
                ],
                '//',
                '/*',
                '*/',
                Syntax::HIGHLIGHT_NUMBERS | Syntax::HIGHLIGHT_STRINGS,
            ),
        ];
    }
    return $db;
}