1
0
Fork 0

Compare commits

...

3 Commits

5 changed files with 54 additions and 31 deletions

View File

@ -8,7 +8,7 @@ plugins {
} }
group = "org.acejump" group = "org.acejump"
version = "chylex-18" version = "chylex-19"
repositories { repositories {
mavenCentral() mavenCentral()

View File

@ -11,18 +11,8 @@ import org.acejump.matchesAt
/** /**
* Searches editor text for matches of a [SearchQuery], and updates previous results when the user [refineQuery]s a character. * Searches editor text for matches of a [SearchQuery], and updates previous results when the user [refineQuery]s a character.
*/ */
class SearchProcessor private constructor(query: SearchQuery, private val results: MutableMap<Editor, IntArrayList>) { class SearchProcessor private constructor(query: SearchQuery, val boundaries: Boundaries, private val results: MutableMap<Editor, IntArrayList>) {
companion object { internal constructor(editors: List<Editor>, query: SearchQuery, boundaries: Boundaries) : this(query, boundaries, mutableMapOf()) {
fun fromString(editors: List<Editor>, query: String, boundaries: Boundaries): SearchProcessor {
return SearchProcessor(editors, SearchQuery.Literal(query), boundaries)
}
fun fromRegex(editors: List<Editor>, pattern: String, boundaries: Boundaries): SearchProcessor {
return SearchProcessor(editors, SearchQuery.RegularExpression(pattern), boundaries)
}
}
private constructor(editors: List<Editor>, query: SearchQuery, boundaries: Boundaries) : this(query, mutableMapOf()) {
val regex = query.toRegex() val regex = query.toRegex()
if (regex != null) { if (regex != null) {
@ -65,7 +55,7 @@ class SearchProcessor private constructor(query: SearchQuery, private val result
return true return true
} }
else { else {
query = SearchQuery.Literal(query.rawText + char) query = query.refine(char)
removeObsoleteResults() removeObsoleteResults()
return isQueryFinished return isQueryFinished
} }

View File

@ -8,6 +8,11 @@ import org.acejump.countMatchingCharacters
internal sealed class SearchQuery { internal sealed class SearchQuery {
abstract val rawText: String abstract val rawText: String
/**
* Returns a new query with the given character appended.
*/
abstract fun refine(char: Char): SearchQuery
/** /**
* Returns how many characters the search occurrence highlight should cover. * Returns how many characters the search occurrence highlight should cover.
*/ */
@ -19,29 +24,42 @@ internal sealed class SearchQuery {
abstract fun toRegex(): Regex? abstract fun toRegex(): Regex?
/** /**
* Searches for all occurrences of a literal text query. If the first character of the query is lowercase, then the entire query will be * Searches for all occurrences of a literal text query.
* case-insensitive. * If the first character of the query is lowercase, then the entire query will be case-insensitive,
* * and only beginnings of words and camel humps will be matched.
* Each occurrence must either match the entire query, or match the query up to a point so that the rest of the query matches the
* beginning of a tag at the location of the occurrence.
*/ */
class Literal(override val rawText: String) : SearchQuery() { class Literal(override val rawText: String, val excludeMiddlesOfWords: Boolean) : SearchQuery() {
init { init {
require(rawText.isNotEmpty()) require(rawText.isNotEmpty())
} }
override fun refine(char: Char): SearchQuery {
return Literal(rawText + char, excludeMiddlesOfWords)
}
override fun getHighlightLength(text: CharSequence, offset: Int): Int { override fun getHighlightLength(text: CharSequence, offset: Int): Int {
return text.countMatchingCharacters(offset, rawText) return text.countMatchingCharacters(offset, rawText)
} }
override fun toRegex(): Regex { override fun toRegex(): Regex {
val options = mutableSetOf(RegexOption.MULTILINE) val firstChar = rawText.first()
val pattern = if (firstChar.isLowerCase()) {
if (rawText.first().isLowerCase()) { if (excludeMiddlesOfWords) {
options.add(RegexOption.IGNORE_CASE) val firstCharUppercasePattern = Regex.escape(firstChar.uppercaseChar().toString())
val firstCharPattern = Regex.escape(firstChar.toString())
val remainingPattern = if (rawText.length > 1) Regex.escape(rawText.drop(1)) else ""
"(?:$firstCharUppercasePattern|(?<![a-zA-Z])$firstCharPattern)$remainingPattern"
}
else {
val fullPattern = Regex.escape(rawText)
"(?i)$fullPattern"
}
}
else {
Regex.escape(rawText)
} }
return Regex(Regex.escape(rawText), options) return Regex(pattern, setOf(RegexOption.MULTILINE))
} }
} }
@ -51,6 +69,10 @@ internal sealed class SearchQuery {
class RegularExpression(private val pattern: String) : SearchQuery() { class RegularExpression(private val pattern: String) : SearchQuery() {
override val rawText = "" override val rawText = ""
override fun refine(char: Char): SearchQuery {
return Literal(char.toString(), excludeMiddlesOfWords = false)
}
override fun getHighlightLength(text: CharSequence, offset: Int): Int { override fun getHighlightLength(text: CharSequence, offset: Int): Int {
return 1 return 1
} }

View File

@ -75,7 +75,7 @@ class Session(private val mainEditor: Editor, private val jumpEditors: List<Edit
val state = state ?: return val state = state ?: return
editorSettings.startEditing(editor) editorSettings.startEditing(editor)
val result = mode.type(state, AceConfig.layout.characterRemapping.getOrDefault(charTyped, charTyped), acceptedTag) val result = mode.type(state, charTyped, acceptedTag)
editorSettings.stopEditing(editor) editorSettings.stopEditing(editor)
when (result) { when (result) {
@ -128,7 +128,7 @@ class Session(private val mainEditor: Editor, private val jumpEditors: List<Edit
canvas.setMarkers(emptyList()) canvas.setMarkers(emptyList())
} }
val processor = SearchProcessor.fromRegex(jumpEditors, pattern.regex, defaultBoundary) val processor = SearchProcessor(jumpEditors, SearchQuery.RegularExpression(pattern.regex), defaultBoundary)
textHighlighter.renderOccurrences(processor.resultsCopy, processor.query) textHighlighter.renderOccurrences(processor.resultsCopy, processor.query)
state = SessionState.SelectTag(actions, jumpEditors, processor) state = SessionState.SelectTag(actions, jumpEditors, processor)

View File

@ -2,7 +2,9 @@ package org.acejump.session
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import org.acejump.boundaries.Boundaries import org.acejump.boundaries.Boundaries
import org.acejump.config.AceConfig
import org.acejump.search.SearchProcessor import org.acejump.search.SearchProcessor
import org.acejump.search.SearchQuery
import org.acejump.search.Tagger import org.acejump.search.Tagger
import org.acejump.search.TaggingResult import org.acejump.search.TaggingResult
@ -15,7 +17,7 @@ sealed interface SessionState {
private val defaultBoundary: Boundaries, private val defaultBoundary: Boundaries,
) : SessionState { ) : SessionState {
override fun type(char: Char): TypeResult { override fun type(char: Char): TypeResult {
val searchProcessor = SearchProcessor.fromString(jumpEditors, char.toString(), defaultBoundary) val searchProcessor = SearchProcessor(jumpEditors, SearchQuery.Literal(char.toString(), excludeMiddlesOfWords = true), defaultBoundary)
return if (searchProcessor.isQueryFinished) { return if (searchProcessor.isQueryFinished) {
TypeResult.ChangeState(SelectTag(actions, jumpEditors, searchProcessor)) TypeResult.ChangeState(SelectTag(actions, jumpEditors, searchProcessor))
@ -49,8 +51,8 @@ sealed interface SessionState {
class SelectTag internal constructor( class SelectTag internal constructor(
private val actions: SessionActions, private val actions: SessionActions,
jumpEditors: List<Editor>, private val jumpEditors: List<Editor>,
searchProcessor: SearchProcessor, private val searchProcessor: SearchProcessor,
) : SessionState { ) : SessionState {
private val tagger = Tagger(jumpEditors, searchProcessor.resultsCopy) private val tagger = Tagger(jumpEditors, searchProcessor.resultsCopy)
@ -59,7 +61,16 @@ sealed interface SessionState {
} }
override fun type(char: Char): TypeResult { override fun type(char: Char): TypeResult {
return when (val result = tagger.type(char)) { if (char == ' ') {
val query = searchProcessor.query
if (query is SearchQuery.Literal && query.excludeMiddlesOfWords) {
val newQuery = SearchQuery.Literal(query.rawText, excludeMiddlesOfWords = false)
val newSearchProcessor = SearchProcessor(jumpEditors, newQuery, searchProcessor.boundaries)
return TypeResult.ChangeState(SelectTag(actions, jumpEditors, newSearchProcessor))
}
}
return when (val result = tagger.type(AceConfig.layout.characterRemapping.getOrDefault(char, char))) {
is TaggingResult.Nothing -> TypeResult.Nothing is TaggingResult.Nothing -> TypeResult.Nothing
is TaggingResult.Accept -> TypeResult.AcceptTag(result.tag) is TaggingResult.Accept -> TypeResult.AcceptTag(result.tag)
is TaggingResult.Mark -> { is TaggingResult.Mark -> {