|
|
@ -6,9 +6,11 @@ import androidx.compose.runtime.setValue |
|
|
|
import kotlinx.coroutines.* |
|
|
|
import kotlinx.coroutines.* |
|
|
|
import org.jetbrains.codeviewer.util.TextLines |
|
|
|
import org.jetbrains.codeviewer.util.TextLines |
|
|
|
import java.io.FileInputStream |
|
|
|
import java.io.FileInputStream |
|
|
|
|
|
|
|
import java.io.FilenameFilter |
|
|
|
import java.io.IOException |
|
|
|
import java.io.IOException |
|
|
|
import java.io.RandomAccessFile |
|
|
|
import java.io.RandomAccessFile |
|
|
|
import java.nio.channels.FileChannel |
|
|
|
import java.nio.channels.FileChannel |
|
|
|
|
|
|
|
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 |
|
|
@ -17,72 +19,67 @@ fun java.io.File.toProjectFile(): File = object : File { |
|
|
|
|
|
|
|
|
|
|
|
override val children: List<File> |
|
|
|
override val children: List<File> |
|
|
|
get() = this@toProjectFile |
|
|
|
get() = this@toProjectFile |
|
|
|
.listFiles() |
|
|
|
.listFiles(FilenameFilter { _, name -> !name.startsWith(".")}) |
|
|
|
.orEmpty() |
|
|
|
.orEmpty() |
|
|
|
.map { it.toProjectFile() } |
|
|
|
.map { it.toProjectFile() } |
|
|
|
|
|
|
|
|
|
|
|
override val hasChildren: Boolean |
|
|
|
override val hasChildren: Boolean |
|
|
|
get() = isDirectory && listFiles()?.size ?: 0 > 0 |
|
|
|
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> |
|
|
|
override fun readLines(scope: CoroutineScope): TextLines { |
|
|
|
val linePositions = IntList() |
|
|
|
var byteBufferSize: Int |
|
|
|
|
|
|
|
val byteBuffer = RandomAccessFile(this@toProjectFile, "r").use { file -> |
|
|
|
|
|
|
|
byteBufferSize = file.length().toInt() |
|
|
|
|
|
|
|
file.channel |
|
|
|
|
|
|
|
.map(FileChannel.MapMode.READ_ONLY, 0, file.length()) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
val lineStartPositions = IntList() |
|
|
|
|
|
|
|
|
|
|
|
var size by mutableStateOf(0) |
|
|
|
var size by mutableStateOf(0) |
|
|
|
|
|
|
|
|
|
|
|
val refreshJob = backgroundScope.launch { |
|
|
|
val refreshJob = scope.launch { |
|
|
|
delay(100) |
|
|
|
delay(100) |
|
|
|
size = linePositions.size |
|
|
|
size = lineStartPositions.size |
|
|
|
while (true) { |
|
|
|
while (true) { |
|
|
|
delay(1000) |
|
|
|
delay(1000) |
|
|
|
size = linePositions.size |
|
|
|
size = lineStartPositions.size |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
backgroundScope.launch { |
|
|
|
scope.launch(Dispatchers.IO) { |
|
|
|
readLinePositions(linePositions) |
|
|
|
readLinePositions(lineStartPositions) |
|
|
|
refreshJob.cancel() |
|
|
|
refreshJob.cancel() |
|
|
|
size = linePositions.size |
|
|
|
size = lineStartPositions.size |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return object : TextLines { |
|
|
|
return object : TextLines { |
|
|
|
override val size get() = size |
|
|
|
override val size get() = size |
|
|
|
|
|
|
|
|
|
|
|
override suspend fun get(index: Int): String { |
|
|
|
override fun get(index: Int): String { |
|
|
|
return withContext(Dispatchers.IO) { |
|
|
|
val startPosition = lineStartPositions[index] |
|
|
|
val position = linePositions[index] |
|
|
|
val length = if (index + 1 < size) lineStartPositions[index + 1] - startPosition else |
|
|
|
try { |
|
|
|
byteBufferSize - startPosition |
|
|
|
RandomAccessFile(this@toProjectFile, "rws").use { |
|
|
|
// Only JDK since 13 has slice() method we need, so do ugly for now. |
|
|
|
it.seek(position.toLong()) |
|
|
|
byteBuffer.position(startPosition) |
|
|
|
// NOTE: it isn't efficient, but simple |
|
|
|
val slice = byteBuffer.slice() |
|
|
|
String( |
|
|
|
slice.limit(length) |
|
|
|
it.readLine() |
|
|
|
return StandardCharsets.UTF_8.decode(slice).toString() |
|
|
|
.toCharArray() |
|
|
|
|
|
|
|
.map(Char::toByte) |
|
|
|
|
|
|
|
.toByteArray(), |
|
|
|
|
|
|
|
Charsets.UTF_8 |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch (e: IOException) { |
|
|
|
|
|
|
|
e.printStackTrace() |
|
|
|
|
|
|
|
"<Error on opening the file>" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Suppress("BlockingMethodInNonBlockingContext") |
|
|
|
private fun java.io.File.readLinePositions( |
|
|
|
private suspend fun java.io.File.readLinePositions(list: IntList) = withContext(Dispatchers.IO) { |
|
|
|
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" |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
val averageLineLength = 200 |
|
|
|
val averageLineLength = 200 |
|
|
|
list.clear(length().toInt() / averageLineLength) |
|
|
|
starts.clear(length().toInt() / averageLineLength) |
|
|
|
|
|
|
|
|
|
|
|
var isBeginOfLine = true |
|
|
|
|
|
|
|
var position = 0L |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
|
FileInputStream(this@readLinePositions).use { |
|
|
|
FileInputStream(this@readLinePositions).use { |
|
|
@ -90,22 +87,25 @@ private suspend fun java.io.File.readLinePositions(list: IntList) = withContext( |
|
|
|
val ib = channel.map( |
|
|
|
val ib = channel.map( |
|
|
|
FileChannel.MapMode.READ_ONLY, 0, channel.size() |
|
|
|
FileChannel.MapMode.READ_ONLY, 0, channel.size() |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
var isBeginOfLine = true |
|
|
|
|
|
|
|
var position = 0L |
|
|
|
while (ib.hasRemaining()) { |
|
|
|
while (ib.hasRemaining()) { |
|
|
|
val byte = ib.get() |
|
|
|
val byte = ib.get() |
|
|
|
if (isBeginOfLine) { |
|
|
|
if (isBeginOfLine) { |
|
|
|
list.add(position.toInt()) |
|
|
|
starts.add(position.toInt()) |
|
|
|
} |
|
|
|
} |
|
|
|
isBeginOfLine = byte.toChar() == '\n' |
|
|
|
isBeginOfLine = byte.toChar() == '\n' |
|
|
|
position++ |
|
|
|
position++ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
channel.close() |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e: IOException) { |
|
|
|
} catch (e: IOException) { |
|
|
|
e.printStackTrace() |
|
|
|
e.printStackTrace() |
|
|
|
list.clear(1) |
|
|
|
starts.clear(1) |
|
|
|
list.add(0) |
|
|
|
starts.add(0) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
list.compact() |
|
|
|
starts.compact() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|