@ -9,13 +9,16 @@ import java.io.FileInputStream
import java.io.FilenameFilter
import java.io.FilenameFilter
import java.io.IOException
import java.io.IOException
import java.io.RandomAccessFile
import java.io.RandomAccessFile
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import java.nio.channels.FileChannel
import java.nio.charset.StandardCharsets
import java.nio.charset.StandardCharsets
fun java . io . File . toProjectFile ( ) : File = object : File {
fun java . io . File . toProjectFile ( ) : File = object : File {
override val name : String get ( ) = this @toProjectFile . name
override val name : String
get ( ) = this @toProjectFile . name
override val isDirectory : Boolean get ( ) = this @toProjectFile . isDirectory
override val isDirectory : Boolean
get ( ) = this @toProjectFile . isDirectory
override val children : List < File >
override val children : List < File >
get ( ) = this @toProjectFile
get ( ) = this @toProjectFile
@ -23,31 +26,34 @@ fun java.io.File.toProjectFile(): File = object : File {
. orEmpty ( )
. orEmpty ( )
. map { it . toProjectFile ( ) }
. map { it . toProjectFile ( ) }
private val numberOfFiles
get ( ) = listFiles ( ) ?. size ?: 0
override val hasChildren : Boolean
override val hasChildren : Boolean
get ( ) = isDirectory && listFiles ( ) ?. size ?: 0 > 0
get ( ) = isDirectory && numberOfFiles > 0
override fun readLines ( scope : CoroutineScope ) : TextLines {
override fun readLines ( scope : CoroutineScope ) : TextLines {
var byteBufferSize : Int
var byteBufferSize : Int
val byteBuffer = RandomAccessFile ( this @toProjectFile , " r " ) . use { file ->
val byteBuffer = RandomAccessFile ( this @toProjectFile , " r " ) . use { file ->
byteBufferSize = file . length ( ) . toInt ( )
byteBufferSize = file . length ( ) . toInt ( )
file . channel
file . channel . map ( FileChannel . MapMode . READ _ONLY , 0 , file . length ( ) )
. map ( FileChannel . MapMode . READ _ONLY , 0 , file . length ( ) )
}
}
val lineStartPositions = IntList ( )
val lineStartPositions = IntList ( )
var size by mutableStateOf ( 0 )
var size by mutableStateOf ( 0 )
// In case of big files, update number of lines periodically
val refreshJob = scope . launch {
val refreshJob = scope . launch {
delay ( 100 )
delay ( 100 )
size = lineStartPositions . size
size = lineStartPositions . size
while ( tru e) {
while ( isActiv e) {
delay ( 1000 )
delay ( 1000 )
size = lineStartPositions . size
size = lineStartPositions . size
}
}
}
}
// Find indexes where lines starts in background
scope . launch ( Dispatchers . IO ) {
scope . launch ( Dispatchers . IO ) {
readLinePositions ( lineStartPositions )
readLinePositions ( lineStartPositions )
refreshJob . cancel ( )
refreshJob . cancel ( )
@ -58,22 +64,37 @@ fun java.io.File.toProjectFile(): File = object : File {
override val size get ( ) = size
override val size get ( ) = size
override fun get ( index : Int ) : String {
override fun get ( index : Int ) : String {
val startPosition = lineStartPositions [ index ]
val position = lineRange ( index )
val length = if ( index + 1 < size ) lineStartPositions [ index + 1 ] - startPosition else
val slice = byteBuffer . slice ( position . first , position . last - position . first )
byteBufferSize - startPosition
// Only JDK since 13 has slice() method we need, so do ugly for now.
byteBuffer . position ( startPosition )
val slice = byteBuffer . slice ( )
slice . limit ( length )
return StandardCharsets . UTF _8 . decode ( slice ) . toString ( )
return StandardCharsets . UTF _8 . decode ( slice ) . toString ( )
}
}
private fun lineRange ( index : Int ) : IntRange {
val startPosition = lineStartPositions [ index ]
val nextLineIndex = index + 1
var endPosition = if ( nextLineIndex < size ) lineStartPositions [ nextLineIndex ] else byteBufferSize
// Remove line endings from the range
while ( endPosition > startPosition ) {
val lastSymbol = byteBuffer [ endPosition - 1 ]
when ( lastSymbol . toInt ( ) . toChar ( ) ) {
'\n' , '\r' -> endPosition --
else -> break
}
}
return startPosition .. endPosition
}
}
}
}
}
}
}
private fun java . io . File . readLinePositions (
// Backport slice from JDK 13
starts : IntList
private fun ByteBuffer . slice ( index : Int , length : Int ) : ByteBuffer {
) {
position ( index )
return slice ( ) . limit ( length )
}
private fun java . io . File . readLinePositions ( starts : IntList ) {
require ( length ( ) <= Int . MAX _VALUE ) {
require ( length ( ) <= Int . MAX _VALUE ) {
" Files with size over ${Int.MAX_VALUE} aren't supported "
" Files with size over ${Int.MAX_VALUE} aren't supported "
}
}
@ -82,22 +103,8 @@ private fun java.io.File.readLinePositions(
starts . clear ( length ( ) . toInt ( ) / averageLineLength )
starts . clear ( length ( ) . toInt ( ) / averageLineLength )
try {
try {
FileInputStream ( this @readLinePositions ) . use {
for ( i in readLinePositions ( ) ) {
val channel = it . channel
starts . add ( i )
val ib = channel . map (
FileChannel . MapMode . READ _ONLY , 0 , channel . size ( )
)
var isBeginOfLine = true
var position = 0L
while ( ib . hasRemaining ( ) ) {
val byte = ib . get ( )
if ( isBeginOfLine ) {
starts . add ( position . toInt ( ) )
}
isBeginOfLine = byte . toInt ( ) . toChar ( ) == '\n'
position ++
}
channel . close ( )
}
}
} catch ( e : IOException ) {
} catch ( e : IOException ) {
e . printStackTrace ( )
e . printStackTrace ( )
@ -108,6 +115,31 @@ private fun java.io.File.readLinePositions(
starts . compact ( )
starts . compact ( )
}
}
private fun java . io . File . readLinePositions ( ) = sequence {
require ( length ( ) <= Int . MAX _VALUE ) {
" Files with size over ${Int.MAX_VALUE} aren't supported "
}
readBuffer {
yield ( position ( ) )
while ( hasRemaining ( ) ) {
val byte = get ( )
if ( byte . isChar ( '\n' ) ) {
yield ( position ( ) )
}
}
}
}
private inline fun java . io . File . readBuffer ( block : ByteBuffer . ( ) -> Unit ) {
FileInputStream ( this ) . use { stream ->
stream . channel . use { channel ->
channel . map ( FileChannel . MapMode . READ _ONLY , 0 , channel . size ( ) ) . block ( )
}
}
}
private fun Byte . isChar ( char : Char ) = toInt ( ) . toChar ( ) == char
/ * *
/ * *
* Compact version of List < Int > ( without unboxing Int and using IntArray under the hood )
* Compact version of List < Int > ( without unboxing Int and using IntArray under the hood )
* /
* /