Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix highlighting keywords in middle other phrase #42

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,15 @@ internal object CodeAnalyzer {
PHP -> analyzeCodeWithKeywords(code, PHP_KEYWORDS)
}

private fun analyzeCodeWithKeywords(code: String, keywords: List<String>): CodeStructure {
private fun analyzeCodeWithKeywords(code: String, keywords: Set<String>): CodeStructure {
val comments = CommentLocator.locate(code)
val multiLineComments = MultilineCommentLocator.locate(code)
val strings = StringLocator.locate(code)
val commentRanges = (comments + multiLineComments).toRangeSet()

val plainTextRanges = comments + multiLineComments + strings
val strings = StringLocator.locate(code, commentRanges)
val plainTextRanges = (comments + multiLineComments + strings).toRangeSet()

// TODO Apply ignored ranges to other locators
return CodeStructure(
marks = MarkLocator.locate(code),
punctuations = PunctuationLocator.locate(code),
Expand Down
27 changes: 24 additions & 3 deletions src/commonMain/kotlin/dev/snipme/highlights/internal/Extensions.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
package dev.snipme.highlights.internal

import dev.snipme.highlights.model.PhraseLocation

inline operator fun <E> Set<E>.get(i: Int): E? {
this.forEachIndexed { index, t ->
if (i == index) return t
}

return null
}

fun String.indicesOf(
phrase: String,
): Set<Int> {
Expand Down Expand Up @@ -42,18 +52,29 @@ fun String.lengthToEOF(start: Int = 0): Int {
return endIndex - start
}

// TODO Create unit tests for this
// Sometimes keyword can be found in the middle of word.
// This returns information if index points only to the keyword
fun String.isIndependentPhrase(
code: String,
index: Int,
): Boolean {
if (index == 0) return true
if (index == code.lastIndex) return true
if (code.length == this.length) return true

val charBefore = code[maxOf(index - 1, 0)]
val charAfter = code[minOf(index + this.length, code.lastIndex)]

return charBefore.isLetter().not() && charAfter.isDigit().not()
if (index == 0) {
return charAfter.isDigit().not() && charAfter.isLetter().not()
}

return charBefore.isLetter().not() &&
charAfter.isDigit().not() && (charAfter == code.last() || charAfter.isLetter().not())
}

fun Set<PhraseLocation>.toRangeSet(): Set<IntRange> =
this.map { IntRange(it.start, it.end) }.toSet()

operator fun IntRange.contains(range: IntRange): Boolean {
return range.first >= this.first && range.last <= this.last
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ internal object SyntaxTokens {
coroutine,crossinline,data,delegate,dynamic,do,else,enum,expect,external,false,final,
finally,for,fun,get,if,import,in,!in,infix,inline,interface,internal,is,!is,lazy,lateinit,
native,null,object,open,operator,out,override,package,private,protected,public,reified,
return,sealed,set,super,suspend,tailrec,this,throw,true,try,typealias,typeof,val,var,vararg,
when,while,yield
return,sealed,set,super,suspend,tailrec,this,throw,true,try,typealias,typeof,val,value,var,
vararg,when,while,yield
""".toTokenList()

val RUST_KEYWORDS = """
Expand Down Expand Up @@ -145,6 +145,6 @@ internal object SyntaxTokens {
val PUNCTUATION_CHARACTERS = listOf(",", ".", ":", ";")
val MARK_CHARACTERS = listOf("(", ")", "=", "{", "}", "<", ">", "-", "+", "[", "]", "|", "&")

private fun String.toTokenList() = trimIndent().split(",")
private fun String.toTokenList() = trimIndent().split(",").toSet()
}

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import dev.snipme.highlights.internal.indicesOf

internal object AnnotationLocator {

fun locate(code: String): List<PhraseLocation> {
fun locate(code: String): Set<PhraseLocation> {
val foundAnnotations = emptyList<String>()
val locations = mutableSetOf<PhraseLocation>()
code.split(*TOKEN_DELIMITERS.toTypedArray())
Expand All @@ -28,6 +28,6 @@ internal object AnnotationLocator {
}
}

return locations.toList()
return locations.toSet()
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package dev.snipme.highlights.internal.locator

import dev.snipme.highlights.model.PhraseLocation
import dev.snipme.highlights.internal.SyntaxTokens.COMMENT_DELIMITERS
import dev.snipme.highlights.internal.indicesOf
import dev.snipme.highlights.internal.lengthToEOF
import dev.snipme.highlights.model.PhraseLocation

internal object CommentLocator {

fun locate(code: String): List<PhraseLocation> {
fun locate(code: String): Set<PhraseLocation> {
val locations = mutableListOf<PhraseLocation>()
val indices = mutableListOf<Int>()
COMMENT_DELIMITERS.forEach { delimiter ->
Expand All @@ -18,6 +18,7 @@ internal object CommentLocator {
val end = start + code.lengthToEOF(start)
locations.add(PhraseLocation(start, end))
}
return locations

return locations.toSet()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,27 @@ internal object KeywordLocator {

fun locate(
code: String,
keywords: List<String>,
ignoreRanges: List<PhraseLocation> = emptyList(),
): List<PhraseLocation> {
val locations = mutableListOf<PhraseLocation>()
keywords: Set<String>,
ignoreRanges: Set<IntRange> = emptySet(),
): Set<PhraseLocation> {
val locations = mutableSetOf<PhraseLocation>()
val foundKeywords = findKeywords(code, keywords)

val interpretedKeywords = foundKeywords.filterNot { keyword ->
val index = code.indexOf(keyword)
val length = keyword.length
ignoreRanges.any { it.start <= index && it.end >= index + length }
}

interpretedKeywords.forEach { keyword ->
foundKeywords.forEach { keyword ->
val indices = code
.indicesOf(keyword)
.filterNot { index -> ignoreRanges.any { index in it } }
.filter { keyword.isIndependentPhrase(code, it) }

indices.forEach { index ->
locations.add(PhraseLocation(index, index + keyword.length))
}
}

return locations.toList()
return locations
}

private fun findKeywords(code: String, keywords: List<String>): Set<String> =
private fun findKeywords(code: String, keywords: Set<String>): Set<String> =
TOKEN_DELIMITERS.toTypedArray().let { delimiters ->
code.split(*delimiters, ignoreCase = true) // Split into words
.asSequence() // Reduce amount of operations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import dev.snipme.highlights.internal.indicesOf
import dev.snipme.highlights.model.PhraseLocation

internal object MarkLocator {
fun locate(code: String): List<PhraseLocation> {
fun locate(code: String): Set<PhraseLocation> {
val locations = mutableListOf<PhraseLocation>()
code.asSequence()
.toSet()
Expand All @@ -16,6 +16,6 @@ internal object MarkLocator {
}
}

return locations
return locations.toSet()
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package dev.snipme.highlights.internal.locator

import dev.snipme.highlights.model.PhraseLocation
import dev.snipme.highlights.internal.SyntaxTokens.MULTILINE_COMMENT_DELIMITERS
import dev.snipme.highlights.internal.indicesOf
import dev.snipme.highlights.model.PhraseLocation

private const val START_INDEX = 0

internal object MultilineCommentLocator {

fun locate(code: String): List<PhraseLocation> {
fun locate(code: String): Set<PhraseLocation> {
val locations = mutableListOf<PhraseLocation>()
val comments = mutableListOf<Pair<Int, Int>>()
val startIndices = mutableListOf<Int>()
Expand All @@ -30,6 +30,6 @@ internal object MultilineCommentLocator {
locations.add(PhraseLocation(start, end))
}

return locations
return locations.toSet()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ private val NUMBER_SPECIAL_CHARACTERS = listOf('_')

internal object NumericLiteralLocator {

fun locate(code: String): List<PhraseLocation> {
fun locate(code: String): Set<PhraseLocation> {
return findDigitIndices(code)
}

private fun findDigitIndices(code: String): List<PhraseLocation> {
private fun findDigitIndices(code: String): Set<PhraseLocation> {
val foundPhrases = mutableSetOf<String>()
val locations = mutableSetOf<PhraseLocation>()

Expand Down Expand Up @@ -45,7 +45,7 @@ internal object NumericLiteralLocator {
foundPhrases.add(number)
}

return locations.toList()
return locations.toSet()
}

// Returns if given index is the beginning of word (there is no letter before)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import dev.snipme.highlights.internal.indicesOf
import dev.snipme.highlights.model.PhraseLocation

internal object PunctuationLocator {
fun locate(code: String): List<PhraseLocation> {
fun locate(code: String): Set<PhraseLocation> {
val locations = mutableSetOf<PhraseLocation>()
code.asSequence()
.map { it.toString().trim() }
Expand All @@ -21,6 +21,6 @@ internal object PunctuationLocator {
}
}

return locations.toList()
return locations.toSet()
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
package dev.snipme.highlights.internal.locator

import dev.snipme.highlights.model.PhraseLocation
import dev.snipme.highlights.internal.SyntaxTokens.STRING_DELIMITERS
import dev.snipme.highlights.internal.contains
import dev.snipme.highlights.internal.indicesOf
import dev.snipme.highlights.model.PhraseLocation

private const val START_INDEX = 0
private const val TWO_ELEMENTS = 2
private const val QUOTE_ENDING_POSITION = 1

internal object StringLocator {

fun locate(code: String): List<PhraseLocation> = findStrings(code)
fun locate(
code: String,
ignoreRanges: Set<IntRange> = emptySet(),
): Set<PhraseLocation> = findStrings(code, ignoreRanges)

private fun findStrings(code: String): List<PhraseLocation> {
val locations = mutableListOf<PhraseLocation>()
private fun findStrings(
code: String,
ignoreRanges: Set<IntRange>,
): Set<PhraseLocation> {
val locations = mutableSetOf<PhraseLocation>()

// Find index of each string delimiter like " or ' or """
STRING_DELIMITERS.forEach {
Expand All @@ -22,14 +29,19 @@ internal object StringLocator {

// For given indices find words between
for (i in START_INDEX..textIndices.lastIndex step TWO_ELEMENTS) {
if (textIndices.getOrNull(i + 1) != null) {
locations.add(
PhraseLocation(
textIndices[i],
textIndices[i + 1] + QUOTE_ENDING_POSITION
)
if (textIndices.getOrNull(i + 1) == null) continue

// Skip unwanted phrases
val textRange = IntRange(textIndices[i], textIndices[i + 1])
if (ignoreRanges.any { ignored -> textRange in ignored })
continue

locations.add(
PhraseLocation(
textIndices[i],
textIndices[i + 1] + QUOTE_ENDING_POSITION
)
}
)
}
}

Expand Down
Loading