Skip to content

Commit

Permalink
Fix selection in codeviewer example (#2898)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
MatkovIvan authored Mar 24, 2023
1 parent 36616f8 commit 48288ea
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,51 @@ 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
.listFiles(FilenameFilter { _, name -> !name.startsWith(".")})
.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()
Expand All @@ -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"
}
Expand All @@ -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()
Expand All @@ -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)
*/
Expand Down

0 comments on commit 48288ea

Please sign in to comment.