mirror of
https://github.com/jlengrand/compose-multiplatform.git
synced 2026-03-10 08:11:20 +00:00
Fix selection in codeviewer example (#2898)
* Fix selection in codeviewer example * Limit line count on view layer * Fix last line ending * Fix missing last empty line in file * Refactor reading file * Add extra endPosition condition * Polish removing line endings
This commit is contained in:
@@ -138,6 +138,7 @@ private fun LineContent(content: Editor.Content, modifier: Modifier, settings: S
|
||||
},
|
||||
fontSize = settings.fontSize,
|
||||
fontFamily = Fonts.jetbrainsMono(),
|
||||
maxLines = 1,
|
||||
modifier = modifier,
|
||||
softWrap = false
|
||||
)
|
||||
|
||||
@@ -9,13 +9,16 @@ import java.io.FileInputStream
|
||||
import java.io.FilenameFilter
|
||||
import java.io.IOException
|
||||
import java.io.RandomAccessFile
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.FileChannel
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
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>
|
||||
get() = this@toProjectFile
|
||||
@@ -23,31 +26,34 @@ fun java.io.File.toProjectFile(): File = object : File {
|
||||
.orEmpty()
|
||||
.map { it.toProjectFile() }
|
||||
|
||||
private val numberOfFiles
|
||||
get() = listFiles()?.size ?: 0
|
||||
|
||||
override val hasChildren: Boolean
|
||||
get() = isDirectory && listFiles()?.size ?: 0 > 0
|
||||
get() = isDirectory && numberOfFiles > 0
|
||||
|
||||
|
||||
override fun readLines(scope: CoroutineScope): TextLines {
|
||||
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())
|
||||
file.channel.map(FileChannel.MapMode.READ_ONLY, 0, file.length())
|
||||
}
|
||||
|
||||
val lineStartPositions = IntList()
|
||||
|
||||
var size by mutableStateOf(0)
|
||||
|
||||
// In case of big files, update number of lines periodically
|
||||
val refreshJob = scope.launch {
|
||||
delay(100)
|
||||
size = lineStartPositions.size
|
||||
while (true) {
|
||||
while (isActive) {
|
||||
delay(1000)
|
||||
size = lineStartPositions.size
|
||||
}
|
||||
}
|
||||
|
||||
// Find indexes where lines starts in background
|
||||
scope.launch(Dispatchers.IO) {
|
||||
readLinePositions(lineStartPositions)
|
||||
refreshJob.cancel()
|
||||
@@ -58,22 +64,37 @@ fun java.io.File.toProjectFile(): File = object : File {
|
||||
override val size get() = size
|
||||
|
||||
override fun get(index: Int): String {
|
||||
val startPosition = lineStartPositions[index]
|
||||
val length = if (index + 1 < size) lineStartPositions[index + 1] - startPosition else
|
||||
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)
|
||||
val position = lineRange(index)
|
||||
val slice = byteBuffer.slice(position.first, position.last - position.first)
|
||||
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(
|
||||
starts: IntList
|
||||
) {
|
||||
// Backport slice from JDK 13
|
||||
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) {
|
||||
"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)
|
||||
|
||||
try {
|
||||
FileInputStream(this@readLinePositions).use {
|
||||
val channel = it.channel
|
||||
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()
|
||||
for (i in readLinePositions()) {
|
||||
starts.add(i)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
@@ -108,6 +115,31 @@ private fun java.io.File.readLinePositions(
|
||||
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)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user