Skip to content
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
Binary file modified .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.gradle
**/build/
!src/**/build/
/.kotlin/

# IDE files
.idea
Expand Down Expand Up @@ -29,4 +30,3 @@ gradle-app.setting
.classpath
# MacOS files
*.DS_Store
/.kotlin/
Comment thread
tmaxxdd marked this conversation as resolved.
Binary file not shown.
Binary file not shown.
Binary file not shown.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## [1.0.0]

### Added
- serialization for public models
- XCFramework output
- `getHighlightsAsync()` with handy `DefaultHighlightsResultListener` adapter
- `getByName()` function for `SyntaxThemes`

### Changed
- Kotlin version to 2.0.20
- direct snapshot field access to `getSnapshot()` function

## [0.9.3]

### Fixed
Expand Down
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
![highlights_banner_opaque](https://github.com/SnipMeDev/Highlights/assets/8405055/e123ce0f-6f58-451a-9e0a-893c0809b909)

[![Maven Central](https://img.shields.io/maven-central/v/dev.snipme/highlights)](https://mvnrepository.com/artifact/dev.snipme)
[![Kotlin](https://img.shields.io/badge/kotlin-1.9.23-blue.svg?logo=kotlin)](http://kotlinlang.org)
[![Kotlin](https://img.shields.io/badge/kotlin-2.0.20-blue.svg?logo=kotlin)](http://kotlinlang.org)
[![GitHub License](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0)

# Highlights
Expand All @@ -15,7 +15,7 @@ repositories {
```

```shell
implementation("dev.snipme:highlights:0.9.3")
implementation("dev.snipme:highlights:1.0.0")
```

## Features ✨
Expand All @@ -25,6 +25,7 @@ implementation("dev.snipme:highlights:0.9.3")
- Text bolding (emphasis)
- Result caching and support for incremental changes
- Written in pure Kotlin, so available for many platforms 📱 💻 🖥️
- Sync or async mode

## Support ☕
Kotlin Multiplatform is a fresh environment and developing for it is neither fast nor easy 🥲
Expand All @@ -48,6 +49,21 @@ Highlights.default().apply {
}
```

There is also a possibility to handle result asynchronously

```kotlin
highlights.getHighlightsAsync(
object : DefaultHighlightsResultListener() {
// onStart
// onError
// onCancel
override fun onSuccess(result: List<CodeHighlight>) {
emitResult(highlights)
}
}
)
```

You can also set language, theme and phrase emphasis.
Language and theme has impact on the ColorHighlight and emphasis is represented by the BoldHighlight.

Expand Down Expand Up @@ -303,7 +319,7 @@ If your project uses this code, please write me or add your info

## TODO 🚧
- [X] Migrate some lists to sets
- [ ] Optimize code analysis
- [X] Optimize code analysis
- [ ] Add more themes and languages
- [ ] Support italic and underline text style

Expand Down
28 changes: 23 additions & 5 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework

apply(from = "publish-root.gradle")

plugins {
kotlin("multiplatform") version "1.9.23"
kotlin("multiplatform") version "2.0.20"
kotlin("plugin.serialization") version "2.0.20"
id("maven-publish")
id("io.github.gradle-nexus.publish-plugin") version "1.3.0"
id("signing")
}

group = "dev.snipme"
version = "0.9.3"
version = "1.0.0"

kotlin {
// Android
Expand All @@ -22,9 +25,16 @@ kotlin {
}
}
// iOS
iosX64()
iosArm64()
iosSimulatorArm64()

val xcf = XCFramework()
val iosTargets = listOf(iosX64(), iosArm64(), iosSimulatorArm64())

iosTargets.forEach {
it.binaries.framework {
baseName = "highlights"
xcf.add(this)
}
}
// Desktop
mingwX64()
linuxX64()
Expand All @@ -39,8 +49,16 @@ kotlin {
wasmJs()
// Dependencies
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
}
}

val commonTest by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0")
implementation(kotlin("test"))
}
}
Expand Down
9 changes: 4 additions & 5 deletions sample/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
kotlin("jvm") version "1.9.23"
kotlin("jvm") version "2.0.20"
application
}

Expand All @@ -15,6 +15,9 @@ repositories {
}

dependencies {
implementation("dev.snipme:highlights:1.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")

testImplementation(kotlin("test"))
}

Expand All @@ -28,8 +31,4 @@ tasks.withType<KotlinCompile> {

application {
mainClass.set("MainKt")
}

dependencies {
implementation(":highlights")
}
62 changes: 53 additions & 9 deletions sample/src/main/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import dev.snipme.highlights.DefaultHighlightsResultListener
import dev.snipme.highlights.Highlights
import dev.snipme.highlights.model.BoldHighlight
import dev.snipme.highlights.model.CodeHighlight
import dev.snipme.highlights.model.PhraseLocation
import dev.snipme.highlights.model.SyntaxLanguage
import dev.snipme.highlights.model.SyntaxThemes
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine

val sampleClass = """
@Serializable
Expand Down Expand Up @@ -30,7 +35,31 @@ val sampleClass = """
}
}
""".trimIndent()

fun main() {
runBlocking {
val highlights = Highlights.Builder()
.code(sampleClass)
.theme(SyntaxThemes.monokai())
.language(SyntaxLanguage.JAVA)
.build()

val syncResult = runSync(highlights)
println("Sync count with emphasis: ${syncResult.size}")

launch {
suspendCancellableCoroutine { continuation ->
runAsync(highlights) { asyncResult ->
assert(syncResult == asyncResult)
println("Async count: ${asyncResult.size}")
continuation.resumeWith(Result.success(Unit))
}
}
}
}
}

fun runSync(highlights: Highlights): List<CodeHighlight> {
println("### HIGHLIGHTS ###")
println()

Expand All @@ -46,28 +75,43 @@ fun main() {
println(sampleClass)
println()

val highlights = Highlights.Builder()
.code(sampleClass)
.theme(SyntaxThemes.monokai())
.language(SyntaxLanguage.JAVA)
.build()

val structure = highlights.getCodeStructure()

println("After analysis there has been found:")
println("${structure.printPhrases(sampleClass)}")
println("${structure.printStructure(sampleClass)}")
println()

val newInstance = highlights.getBuilder()
.emphasis(PhraseLocation(0, 13))
.build()

println("The emphasis was put on the word:")
val emphasisLocation = newInstance
.getHighlights()
val result = newInstance.getHighlights()
val emphasisLocation = result
.filterIsInstance<BoldHighlight>()
.first()
.location

println(sampleClass.substring(emphasisLocation.start, emphasisLocation.end))

return result
}

fun runAsync(
highlights: Highlights,
emitResult: (List<CodeHighlight>) -> Unit,
) {
println()
println("### ASYNC HIGHLIGHTS ###")

highlights.getHighlightsAsync(
object : DefaultHighlightsResultListener() {
// onStart
// onError
// onCancel
override fun onSuccess(result: List<CodeHighlight>) {
emitResult(result)
}
}
)
}
65 changes: 49 additions & 16 deletions src/commonMain/kotlin/dev/snipme/highlights/Highlights.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dev.snipme.highlights

import dev.snipme.highlights.internal.CodeAnalyzer
import dev.snipme.highlights.internal.CodeSnapshot
import dev.snipme.highlights.internal.onCancel
import dev.snipme.highlights.model.BoldHighlight
import dev.snipme.highlights.model.CodeHighlight
import dev.snipme.highlights.model.CodeStructure
Expand All @@ -10,15 +11,22 @@ import dev.snipme.highlights.model.PhraseLocation
import dev.snipme.highlights.model.SyntaxLanguage
import dev.snipme.highlights.model.SyntaxTheme
import dev.snipme.highlights.model.SyntaxThemes
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch

class Highlights private constructor(
private var code: String,
private val language: SyntaxLanguage,
private val theme: SyntaxTheme,
private var emphasisLocations: List<PhraseLocation>
) {
var snapshot: CodeSnapshot? = null
private set
private var coroutineScope = CoroutineScope(Dispatchers.Default)

private var snapshot: CodeSnapshot? = null

companion object {
fun default() = fromBuilder(Builder())
Expand Down Expand Up @@ -60,22 +68,28 @@ class Highlights private constructor(
}

fun getHighlights(): List<CodeHighlight> {
val highlights = mutableListOf<CodeHighlight>()
val structure = getCodeStructure()
with(structure) {
marks.forEach { highlights.add(ColorHighlight(it, theme.mark)) }
punctuations.forEach { highlights.add(ColorHighlight(it, theme.punctuation)) }
keywords.forEach { highlights.add(ColorHighlight(it, theme.keyword)) }
strings.forEach { highlights.add(ColorHighlight(it, theme.string)) }
literals.forEach { highlights.add(ColorHighlight(it, theme.literal)) }
annotations.forEach { highlights.add(ColorHighlight(it, theme.metadata)) }
comments.forEach { highlights.add(ColorHighlight(it, theme.comment)) }
multilineComments.forEach { highlights.add(ColorHighlight(it, theme.multilineComment)) }
}

emphasisLocations.forEach { highlights.add(BoldHighlight(it)) }
return constructHighlights(structure)
}

return highlights
fun getHighlightsAsync(listener: HighlightsResultListener) = with(coroutineScope) {
try {
val errorHandler = CoroutineExceptionHandler { _, exception ->
listener.onError(exception)
}

coroutineContext.cancelChildren()
launch(errorHandler) {
listener.onStart()
ensureActive()
val structure = getCodeStructure()
ensureActive()
val highlights = constructHighlights(structure)
listener.onSuccess(highlights)
}.also { it.onCancel { listener.onCancel() } }
} catch (exception: Exception) {
listener.onError(exception)
}
}

fun getBuilder() = Builder(code, language, theme, emphasisLocations)
Expand All @@ -87,4 +101,23 @@ class Highlights private constructor(
fun getTheme() = theme

fun getEmphasis() = emphasisLocations

fun getSnapshot() = snapshot

fun clearSnapshot() {
snapshot = null
}

private fun constructHighlights(structure: CodeStructure): List<CodeHighlight> =
mutableListOf<CodeHighlight>().apply {
structure.marks.forEach { add(ColorHighlight(it, theme.mark)) }
structure.punctuations.forEach { add(ColorHighlight(it, theme.punctuation)) }
structure.keywords.forEach { add(ColorHighlight(it, theme.keyword)) }
structure.strings.forEach { add(ColorHighlight(it, theme.string)) }
structure.literals.forEach { add(ColorHighlight(it, theme.literal)) }
structure.annotations.forEach { add(ColorHighlight(it, theme.metadata)) }
structure.comments.forEach { add(ColorHighlight(it, theme.comment)) }
structure.multilineComments.forEach { add(ColorHighlight(it, theme.multilineComment)) }
emphasisLocations.forEach { add(BoldHighlight(it)) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.snipme.highlights

import dev.snipme.highlights.model.CodeHighlight

interface HighlightsResultListener {
fun onStart()
fun onSuccess(result: List<CodeHighlight>)
fun onError(exception: Throwable)
fun onCancel()
}

abstract class DefaultHighlightsResultListener : HighlightsResultListener {
override fun onStart() {}
override fun onSuccess(result: List<CodeHighlight>) {}
override fun onError(exception: Throwable) {}
override fun onCancel() {}
}
Loading