[Editor] Create working Java editor

This commit is contained in:
2021-02-23 07:53:30 +01:00
parent a261378a35
commit 5fec390ad8
8 changed files with 203 additions and 5 deletions

View File

@@ -40,6 +40,7 @@ dependencies {
implementation platform("org.kordamp.ikonli:ikonli-bom:${ikonliVersion}")
implementation 'org.kordamp.ikonli:ikonli-javafx'
implementation 'org.kordamp.ikonli:ikonli-fontawesome-pack'
implementation "org.fxmisc.richtext:richtextfx:${richtextfxVersion}"
// Spring
implementation 'org.springframework.boot:spring-boot-starter'

View File

@@ -0,0 +1,61 @@
package com.bartlomiejpluta.base.editor.code.component
import com.bartlomiejpluta.base.editor.code.highlighting.JavaSyntaxHighlighter
import com.bartlomiejpluta.base.editor.code.stylesheet.HighlightingStylesheet
import javafx.beans.property.Property
import javafx.concurrent.Task
import javafx.scene.layout.StackPane
import org.fxmisc.flowless.VirtualizedScrollPane
import org.fxmisc.richtext.CodeArea
import org.fxmisc.richtext.LineNumberFactory
import org.fxmisc.richtext.model.StyleSpans
import java.time.Duration
import java.util.*
import java.util.concurrent.Executors
class CodeEditor(val codeProperty: Property<String>) : StackPane() {
private val editor = CodeArea()
private val executor = Executors.newSingleThreadExecutor()
private val cleanupWhenDone = editor.multiPlainChanges()
private val highlighting = JavaSyntaxHighlighter()
init {
editor.paragraphGraphicFactory = LineNumberFactory.get(editor)
editor.replaceText(0, 0, codeProperty.value)
applyHighlighting(highlighting.highlight(editor.text))
cleanupWhenDone
.successionEnds(Duration.ofMillis(500))
.supplyTask(this::computeHighlightingAsync)
.awaitLatest(editor.multiPlainChanges())
.filterMap {
when {
it.isSuccess -> Optional.of(it.get())
else -> Optional.empty()
}
}
.subscribe(this::applyHighlighting)
children += VirtualizedScrollPane(editor)
}
private fun computeHighlightingAsync(): Task<StyleSpans<Collection<String>>> {
val code = editor.text
val task = object : Task<StyleSpans<Collection<String>>>() {
override fun call() = highlighting.highlight(code)
}
executor.execute(task)
return task
}
private fun applyHighlighting(highlighting: StyleSpans<Collection<String>>) {
editor.setStyleSpans(0, highlighting)
}
override fun getUserAgentStylesheet() = HighlightingStylesheet().base64URL.toExternalForm()
}

View File

@@ -0,0 +1,66 @@
package com.bartlomiejpluta.base.editor.code.highlighting
import org.fxmisc.richtext.model.StyleSpans
import org.fxmisc.richtext.model.StyleSpansBuilder
class JavaSyntaxHighlighter : SyntaxHighlighter {
override fun highlight(code: String): StyleSpans<Collection<String>> = StyleSpansBuilder<Collection<String>>().let {
val lastKeywordEnd = PATTERN.findAll(code).fold(0) { lastKeywordEnd, result ->
val styleClass = when {
result.groups["KEYWORD"] != null -> "keyword"
result.groups["PAREN"] != null -> "paren"
result.groups["BRACE"] != null -> "brace"
result.groups["BRACKET"] != null -> "bracket"
result.groups["SEMICOLON"] != null -> "semicolon"
result.groups["STRING"] != null -> "string"
result.groups["COMMENT"] != null -> "comment"
else -> throw IllegalStateException("Unsupported regex group")
}
it.add(emptyList(), result.range.first - lastKeywordEnd)
it.add(listOf(styleClass), result.range.last - result.range.first + 1)
result.range.last + 1
}
it.add(emptyList(), code.length - lastKeywordEnd)
it.create()
}
companion object {
private val KEYWORDS = arrayOf(
"abstract", "assert", "boolean", "break", "byte",
"case", "catch", "char", "class", "const",
"continue", "default", "do", "double", "else",
"enum", "extends", "final", "finally", "float",
"for", "goto", "if", "implements", "import",
"instanceof", "int", "interface", "long", "native",
"new", "package", "private", "protected", "public",
"return", "short", "static", "strictfp", "super",
"switch", "synchronized", "this", "throw", "throws",
"transient", "try", "void", "volatile", "while"
)
private val KEYWORD_PATTERN = "\\b(" + KEYWORDS.joinToString("|") + ")\\b"
private val PAREN_PATTERN = "\\(|\\)"
private val BRACE_PATTERN = "\\{|\\}"
private val BRACKET_PATTERN = "\\[|\\]"
private val SEMICOLON_PATTERN = "\\;"
private val STRING_PATTERN = "\"([^\"\\\\]|\\\\.)*\""
private val COMMENT_PATTERN = """
//[^
]*|/\*(.|\R)*?\*/
""".trimIndent()
private val PATTERN = (
"(?<KEYWORD>" + KEYWORD_PATTERN + ")"
+ "|(?<PAREN>" + PAREN_PATTERN + ")"
+ "|(?<BRACE>" + BRACE_PATTERN + ")"
+ "|(?<BRACKET>" + BRACKET_PATTERN + ")"
+ "|(?<SEMICOLON>" + SEMICOLON_PATTERN + ")"
+ "|(?<STRING>" + STRING_PATTERN + ")"
+ "|(?<COMMENT>" + COMMENT_PATTERN + ")"
).toRegex()
}
}

View File

@@ -0,0 +1,7 @@
package com.bartlomiejpluta.base.editor.code.highlighting
import org.fxmisc.richtext.model.StyleSpans
interface SyntaxHighlighter {
fun highlight(code: String): StyleSpans<Collection<String>>
}

View File

@@ -0,0 +1,56 @@
package com.bartlomiejpluta.base.editor.code.stylesheet
import javafx.scene.text.FontWeight
import tornadofx.*
class HighlightingStylesheet : Stylesheet() {
companion object {
val keyword by cssclass()
val semicolon by cssclass()
val paren by cssclass()
val bracket by cssclass()
val brace by cssclass()
val string by cssclass()
val comment by cssclass()
val paragraphBox by cssclass()
val hasCaret by csspseudoclass()
}
init {
keyword {
fill = c("purple")
fontWeight = FontWeight.BOLD
}
semicolon {
fontWeight = FontWeight.BOLD
}
paren {
fill = c("firebrick")
fontWeight = FontWeight.BOLD
}
bracket {
fill = c("darkgreen")
fontWeight = FontWeight.BOLD
}
brace {
fill = c("teal")
fontWeight = FontWeight.BOLD
}
string {
fill = c("blue")
}
comment {
fill = c("cadetblue")
}
paragraphBox and hasCaret {
backgroundColor = multi(c("#f2f9fc"))
}
}
}

View File

@@ -1,9 +1,9 @@
package com.bartlomiejpluta.base.editor.code.view
import tornadofx.Fragment
import tornadofx.hbox
class CodeEditorFragment : Fragment() {
private val editorView = find<CodeEditorView>()
override val root = hbox {}
override val root = editorView.root
}

View File

@@ -1,9 +1,15 @@
package com.bartlomiejpluta.base.editor.code.view
import com.bartlomiejpluta.base.editor.code.component.CodeEditor
import com.bartlomiejpluta.base.editor.code.viewmodel.CodeVM
import tornadofx.View
import tornadofx.hbox
import tornadofx.borderpane
class CodeEditorView : View() {
private val codeVM = find<CodeVM>()
private val editor = CodeEditor(codeVM.codeProperty)
override val root = hbox {}
override val root = borderpane {
center = editor
}
}

View File

@@ -6,4 +6,5 @@ guavaVersion=29.0-jre
tornadoFxVersion=2.0.0-SNAPSHOT
ikonliVersion=12.2.0
protobufPluginVersion=0.8.14
protobufVersion=3.14.0
protobufVersion=3.14.0
richtextfxVersion=0.10.5