You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

145 lines
4.2 KiB

package org.jetbrains.codeviewer.platform
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import kotlinx.coroutines.*
import org.jetbrains.codeviewer.util.TextLines
import java.io.FileInputStream
import java.io.IOException
import java.io.RandomAccessFile
import java.nio.channels.FileChannel
fun java.io.File.toProjectFile(): File = object : File {
override val name: String get() = this@toProjectFile.name
override val isDirectory: Boolean get() = this@toProjectFile.isDirectory
override val children: List<File>
get() = this@toProjectFile
.listFiles()
.orEmpty()
.map { it.toProjectFile() }
override val hasChildren: Boolean
get() = isDirectory && listFiles()?.size ?: 0 > 0
override suspend fun readLines(backgroundScope: CoroutineScope): TextLines {
// linePositions can be very big, so we are using IntList instead of List<Long>
val linePositions = IntList()
var size by mutableStateOf(0)
val refreshJob = backgroundScope.launch {
delay(100)
size = linePositions.size
while (true) {
delay(1000)
size = linePositions.size
}
}
backgroundScope.launch {
readLinePositions(linePositions)
refreshJob.cancel()
size = linePositions.size
}
return object : TextLines {
override val size get() = size
override suspend fun get(index: Int): String {
return withContext(Dispatchers.IO) {
val position = linePositions[index]
try {
RandomAccessFile(this@toProjectFile, "rws").use {
it.seek(position.toLong())
// NOTE: it isn't efficient, but simple
String(
it.readLine()
.toCharArray()
.map(Char::toByte)
.toByteArray(),
Charsets.UTF_8
)
}
} catch (e: IOException) {
e.printStackTrace()
"<Error on opening the file>"
}
}
}
}
}
}
@Suppress("BlockingMethodInNonBlockingContext")
private suspend fun java.io.File.readLinePositions(list: IntList) = withContext(Dispatchers.IO) {
require(length() <= Int.MAX_VALUE) {
"Files with size over ${Int.MAX_VALUE} aren't supported"
}
val averageLineLength = 200
list.clear(length().toInt() / averageLineLength)
var isBeginOfLine = true
var position = 0L
try {
FileInputStream(this@readLinePositions).use {
val channel = it.channel
val ib = channel.map(
FileChannel.MapMode.READ_ONLY, 0, channel.size()
)
while (ib.hasRemaining()) {
val byte = ib.get()
if (isBeginOfLine) {
list.add(position.toInt())
}
isBeginOfLine = byte.toChar() == '\n'
position++
}
}
} catch (e: IOException) {
e.printStackTrace()
list.clear(1)
list.add(0)
}
list.compact()
}
/**
* Compact version of List<Int> (without unboxing Int and using IntArray under the hood)
*/
private class IntList(initialCapacity: Int = 16) {
@Volatile
private var array = IntArray(initialCapacity)
@Volatile
var size: Int = 0
private set
fun clear(capacity: Int) {
array = IntArray(capacity)
size = 0
}
fun add(value: Int) {
if (size == array.size) {
doubleCapacity()
}
array[size++] = value
}
operator fun get(index: Int) = array[index]
private fun doubleCapacity() {
val newArray = IntArray(array.size * 2 + 1)
System.arraycopy(array, 0, newArray, 0, size)
array = newArray
}
fun compact() {
array = array.copyOfRange(0, size)
}
}