Skip to content

Commit

Permalink
Fix input delay in find/replace
Browse files Browse the repository at this point in the history
Also fixes focusing issue
  • Loading branch information
massivemadness committed Feb 15, 2023
1 parent 96255ed commit 14b95de
Show file tree
Hide file tree
Showing 17 changed files with 266 additions and 161 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
package com.blacksquircle.ui.editorkit.model

data class FindParams(
val query: String,
val regex: Boolean,
val matchCase: Boolean,
val wordsOnly: Boolean,
val query: String = "",
val regex: Boolean = false,
val matchCase: Boolean = false,
val wordsOnly: Boolean = false,
)
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ package com.blacksquircle.ui.editorkit.model
import android.text.style.BackgroundColorSpan
import com.blacksquircle.ui.language.base.span.StyleSpan

data class FindResultSpan(
private val span: StyleSpan,
var start: Int,
var end: Int,
) : BackgroundColorSpan(span.color)
data class FindResult(var start: Int, var end: Int) {

class Span(styleSpan: StyleSpan) : BackgroundColorSpan(styleSpan.color)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ import android.text.Spannable
import android.util.AttributeSet
import androidx.core.text.PrecomputedTextCompat
import androidx.core.text.getSpans
import com.blacksquircle.ui.editorkit.R
import com.blacksquircle.ui.editorkit.model.ErrorSpan
import com.blacksquircle.ui.editorkit.model.FindParams
import com.blacksquircle.ui.editorkit.model.FindResultSpan
import com.blacksquircle.ui.editorkit.model.FindResult
import com.blacksquircle.ui.editorkit.model.TabWidthSpan
import com.blacksquircle.ui.editorkit.setSelectionRange
import com.blacksquircle.ui.editorkit.utils.EditorTheme
Expand Down Expand Up @@ -61,7 +60,7 @@ abstract class SyntaxHighlightEditText @JvmOverloads constructor(
var tabWidth = 4

private val syntaxHighlightSpans = mutableListOf<SyntaxHighlightSpan>()
private val findResultSpans = mutableListOf<FindResultSpan>()
private val findResults = mutableListOf<FindResult>()

private var findResultStyleSpan: StyleSpan? = null
private var task: StylingTask? = null
Expand All @@ -74,7 +73,7 @@ abstract class SyntaxHighlightEditText @JvmOverloads constructor(

override fun setTextContent(textParams: PrecomputedTextCompat) {
syntaxHighlightSpans.clear()
findResultSpans.clear()
findResults.clear()
super.setTextContent(textParams)
syntaxHighlight()
}
Expand Down Expand Up @@ -132,15 +131,6 @@ abstract class SyntaxHighlightEditText @JvmOverloads constructor(
}
}

fun clearFindResultSpans() {
selectedFindResult = 0
findResultSpans.clear()
val spans = text.getSpans<FindResultSpan>(0, text.length)
for (span in spans) {
text.removeSpan(span)
}
}

fun setErrorLine(lineNumber: Int) {
if (lineNumber > 0) {
val lineStart = lines.getIndexForStartOfLine(lineNumber - 1)
Expand All @@ -153,104 +143,107 @@ abstract class SyntaxHighlightEditText @JvmOverloads constructor(
}

fun find(params: FindParams) {
clearFindResultSpans()
if (params.query.isNotEmpty()) {
try {
val pattern = if (params.regex) {
if (params.matchCase) {
Pattern.compile(params.query)
} else {
Pattern.compile(params.query, Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE)
}
} else {
if (params.wordsOnly) {
if (params.matchCase) {
Pattern.compile("\\s${params.query}\\s")
} else {
Pattern.compile(
"\\s" + Pattern.quote(params.query) + "\\s",
Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE,
)
}
} else {
if (params.matchCase) {
Pattern.compile(Pattern.quote(params.query))
} else {
Pattern.compile(
Pattern.quote(params.query),
Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE,
)
}
}
val pattern = when {
params.regex && params.matchCase -> Pattern.compile(params.query)
params.regex && !params.matchCase -> Pattern.compile(
params.query,
Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE,
)
params.wordsOnly && params.matchCase -> Pattern.compile("\\s${params.query}\\s")
params.wordsOnly && !params.matchCase -> Pattern.compile(
"\\s" + Pattern.quote(params.query) + "\\s",
Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE,
)
params.matchCase -> Pattern.compile(Pattern.quote(params.query))
else -> Pattern.compile(
Pattern.quote(params.query),
Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE,
)
}
val matcher = pattern.matcher(text)
while (matcher.find()) {
findResultStyleSpan?.let {
val findResultSpan = FindResultSpan(it, matcher.start(), matcher.end())
findResultSpans.add(findResultSpan)

text.setSpan(
findResultSpan,
findResultSpan.start,
findResultSpan.end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
)
}
val findResult = FindResult(matcher.start(), matcher.end())
findResults.add(findResult)
}
if (findResultSpans.isNotEmpty()) {
if (findResults.isNotEmpty()) {
selectResult()
}
} catch (e: PatternSyntaxException) {
// nothing
}
}
updateSyntaxHighlighting()
}

fun find(findResults: List<FindResult>) {
clearFindResultSpans()
for (findResult in findResults) {
this.findResults.add(findResult)
}
if (findResults.isNotEmpty()) {
selectResult()
}
updateSyntaxHighlighting()
}

fun findNext() {
if (selectedFindResult < findResultSpans.size - 1) {
if (selectedFindResult < findResults.size - 1) {
selectedFindResult += 1
selectResult()
}
}

fun findPrevious() {
if (selectedFindResult > 0 && selectedFindResult < findResultSpans.size) {
if (selectedFindResult > 0 && selectedFindResult < findResults.size) {
selectedFindResult -= 1
selectResult()
}
}

fun replaceFindResult(replaceText: String) {
if (findResultSpans.isNotEmpty()) {
val findResult = findResultSpans[selectedFindResult]
if (findResults.isNotEmpty()) {
val findResult = findResults[selectedFindResult]
text.replace(findResult.start, findResult.end, replaceText)
findResultSpans.remove(findResult)
if (selectedFindResult >= findResultSpans.size) {
findResults.remove(findResult)
if (selectedFindResult >= findResults.size) {
selectedFindResult--
}
}
}

fun replaceAllFindResults(replaceText: String) {
if (findResultSpans.isNotEmpty()) {
if (findResults.isNotEmpty()) {
val stringBuilder = StringBuilder(text)
for (index in findResultSpans.size - 1 downTo 0) {
val findResultSpan = findResultSpans[index]
for (index in findResults.size - 1 downTo 0) {
val findResultSpan = findResults[index]
stringBuilder.replace(findResultSpan.start, findResultSpan.end, replaceText)
findResultSpans.removeAt(index)
findResults.removeAt(index)
}
setText(stringBuilder.toString())
}
}

fun clearFindResultSpans() {
selectedFindResult = 0
findResults.clear()
val spans = text.getSpans<FindResult.Span>(0, text.length)
for (span in spans) {
text.removeSpan(span)
}
}

private fun selectResult() {
val findResult = findResultSpans[selectedFindResult]
val findResult = findResults[selectedFindResult]
setSelectionRange(findResult.start, findResult.end)
scrollToFindResult()
}

private fun scrollToFindResult() {
if (selectedFindResult < findResultSpans.size) {
val findResult = findResultSpans[selectedFindResult]
if (selectedFindResult < findResults.size) {
val findResult = findResults[selectedFindResult]
if (findResult.start >= layout.getLineStart(topVisibleLine) &&
findResult.end <= layout.getLineEnd(bottomVisibleLine)
) {
Expand Down Expand Up @@ -283,7 +276,7 @@ abstract class SyntaxHighlightEditText @JvmOverloads constructor(
syntaxHighlightSpans.remove(span) // FIXME may cause ConcurrentModificationException
}*/
}
for (findResult in findResultSpans) {
for (findResult in findResults) {
/*if (from > findResult.start && from <= findResult.end) {
findResultSpans.remove(findResult) // FIXME may cause IndexOutOfBoundsException
}*/
Expand Down Expand Up @@ -335,22 +328,24 @@ abstract class SyntaxHighlightEditText @JvmOverloads constructor(
}
isSyntaxHighlighting = false

val textFindSpans = text.getSpans<FindResultSpan>(0, text.length)
val textFindSpans = text.getSpans<FindResult.Span>(0, text.length)
for (span in textFindSpans) {
text.removeSpan(span)
}
for (span in findResultSpans) {
val isInText = span.start >= 0 && span.end <= text.length
val isValid = span.start <= span.end
val isVisible = span.start in lineStart..lineEnd ||
span.start <= lineEnd && span.end >= lineStart
if (isInText && isValid && isVisible) {
text.setSpan(
span,
if (span.start < lineStart) lineStart else span.start,
if (span.end > lineEnd) lineEnd else span.end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
)
findResultStyleSpan?.let { styleSpan ->
for (span in findResults) {
val isInText = span.start >= 0 && span.end <= text.length
val isValid = span.start <= span.end
val isVisible = span.start in lineStart..lineEnd ||
span.start <= lineEnd && span.end >= lineStart
if (isInText && isValid && isVisible) {
text.setSpan(
FindResult.Span(styleSpan),
if (span.start < lineStart) lineStart else span.start,
if (span.end > lineEnd) lineEnd else span.end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package com.blacksquircle.ui.feature.editor.domain.repository

import android.net.Uri
import com.blacksquircle.ui.editorkit.model.FindParams
import com.blacksquircle.ui.editorkit.model.FindResult
import com.blacksquircle.ui.feature.editor.domain.model.DocumentContent
import com.blacksquircle.ui.feature.editor.domain.model.DocumentModel
import com.blacksquircle.ui.feature.editor.domain.model.DocumentParams
Expand All @@ -30,4 +32,6 @@ interface DocumentRepository {
suspend fun loadFile(documentModel: DocumentModel): DocumentContent
suspend fun saveFile(content: DocumentContent, params: DocumentParams)
suspend fun saveFileAs(documentModel: DocumentModel, fileUri: Uri)

suspend fun find(text: CharSequence, params: FindParams): List<FindResult>
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import com.blacksquircle.ui.core.data.factory.FilesystemFactory
import com.blacksquircle.ui.core.data.storage.database.AppDatabase
import com.blacksquircle.ui.core.data.storage.keyvalue.SettingsManager
import com.blacksquircle.ui.core.domain.coroutine.DispatcherProvider
import com.blacksquircle.ui.editorkit.model.FindParams
import com.blacksquircle.ui.editorkit.model.FindResult
import com.blacksquircle.ui.editorkit.model.UndoStack
import com.blacksquircle.ui.feature.editor.data.converter.DocumentConverter
import com.blacksquircle.ui.feature.editor.data.utils.charsetFor
Expand All @@ -36,6 +38,7 @@ import com.blacksquircle.ui.filesystem.base.model.FileModel
import com.blacksquircle.ui.filesystem.base.model.FileParams
import com.blacksquircle.ui.filesystem.base.model.LineBreak
import kotlinx.coroutines.withContext
import java.util.regex.Pattern

class DocumentRepositoryImpl(
private val dispatcherProvider: DispatcherProvider,
Expand Down Expand Up @@ -134,6 +137,37 @@ class DocumentRepositoryImpl(
}
}

override suspend fun find(text: CharSequence, params: FindParams): List<FindResult> {
return withContext(dispatcherProvider.io()) {
val findResults = mutableListOf<FindResult>()
if (params.query.isEmpty()) {
return@withContext emptyList()
}
val pattern = when {
params.regex && params.matchCase -> Pattern.compile(params.query)
params.regex && !params.matchCase -> Pattern.compile(
params.query, Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE,
)
params.wordsOnly && params.matchCase -> Pattern.compile("\\s${params.query}\\s")
params.wordsOnly && !params.matchCase -> Pattern.compile(
"\\s" + Pattern.quote(params.query) + "\\s",
Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE,
)
params.matchCase -> Pattern.compile(Pattern.quote(params.query))
else -> Pattern.compile(
Pattern.quote(params.query),
Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE,
)
}
val matcher = pattern.matcher(text)
while (matcher.find()) {
val findResult = FindResult(matcher.start(), matcher.end())
findResults.add(findResult)
}
findResults
}
}

private fun loadUndoStack(document: DocumentModel): UndoStack {
return try {
val undoCacheFile = cacheFile(document, postfix = "undo.cache")
Expand Down
Loading

0 comments on commit 14b95de

Please sign in to comment.