JSFiddle - React, Tailwind, and code Playground

HTML

<a href="https://github.com/anpur/client-line-navigator">Demo of LineNavigator</a><br><br>

<input id="file-select" type="file" name="files[]"  />
<hr>
<button id="read">Read</button>
<hr>

<button id="search-beginning">Find first</button>
<input type="text" id="find-first-pattern" placeholder="Regexp pattern" />
<button id="search-next">Find next</button>
<hr>

<button id="searchAll">Find all</button>
<input type="text" id="find-all-pattern" placeholder="Regexp pattern" />
(results are limited to 100 lines max)
<hr>

<div id="meta"></div><br>
<div id="output">RESULTS</div>

CSS

input, button {
    margin: 5px;
}
#output {
    border: 1px solid black;	
    padding: 5px;
}

JavaScript

// Allows to navigate given sources lines, saving milestones to optimize random reading
// options = {
//        milestones: [],         // optional: array of milestones, which can be obtained by getMilestones() method and stored to speed up random reading in future
//        chunkSize: 1024 * 4,    // optional: size of chunk to read at once
// }
function LineNavigator(readChunk, decode, options) {
    var self = this;
    
    // verification
    if (typeof (readChunk) != 'function') throw 'readChunk argument must be function(offset, length, callback)'
    if (typeof (decode) != 'function') throw 'decode argument must be function(buffer, callback)'    

    // private    
    options = options ? options : {};
    var milestones =    options.milestones    ? options.milestones    : [];    // { firstLine, lastLine, offset, length }
    var chunkSize =     options.chunkSize     ? options.chunkSize     : 1024 * 4;
    var newLineCode =   '\n'.charCodeAt(0);
    var splitPattern =  /\r?\n/;
    
    // Searches for milestone
    var getPlaceToStart = function (index) {
        for (var i = milestones.length - 1; i >= 0; i--) {
            if (milestones[i].lastLine < index) 
                return { firstLine: milestones[i].lastLine + 1, offset: milestones[i].offset + milestones[i].length };
        }
        return { firstLine: 0, offset: 0 };
    }
    
    // Count lines in chunk and offset of last line, saves milestones
    var examineChunk = function(buffer, offset, bytesRead, firstLine) {
        var saveMilestone = milestones.length == 0 || milestones[milestones.length - 1].offset < offset;
        var lastLine = firstLine - 1;
        var length = 0;

        // Search for delimiters
        for (var i = 0; i < bytesRead; i++) {
            if (buffer[i] == newLineCode) {
                lastLine++;
                length = i + 1;
            }
        }

        // Describe milestone
        var milestone = {
            firstLine: firstLine,
           ...