[Editor] Create working Java editor
This commit is contained in:
@@ -40,6 +40,7 @@ dependencies {
|
|||||||
implementation platform("org.kordamp.ikonli:ikonli-bom:${ikonliVersion}")
|
implementation platform("org.kordamp.ikonli:ikonli-bom:${ikonliVersion}")
|
||||||
implementation 'org.kordamp.ikonli:ikonli-javafx'
|
implementation 'org.kordamp.ikonli:ikonli-javafx'
|
||||||
implementation 'org.kordamp.ikonli:ikonli-fontawesome-pack'
|
implementation 'org.kordamp.ikonli:ikonli-fontawesome-pack'
|
||||||
|
implementation "org.fxmisc.richtext:richtextfx:${richtextfxVersion}"
|
||||||
|
|
||||||
// Spring
|
// Spring
|
||||||
implementation 'org.springframework.boot:spring-boot-starter'
|
implementation 'org.springframework.boot:spring-boot-starter'
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>>
|
||||||
|
}
|
||||||
@@ -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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package com.bartlomiejpluta.base.editor.code.view
|
package com.bartlomiejpluta.base.editor.code.view
|
||||||
|
|
||||||
import tornadofx.Fragment
|
import tornadofx.Fragment
|
||||||
import tornadofx.hbox
|
|
||||||
|
|
||||||
class CodeEditorFragment : Fragment() {
|
class CodeEditorFragment : Fragment() {
|
||||||
|
private val editorView = find<CodeEditorView>()
|
||||||
|
|
||||||
override val root = hbox {}
|
override val root = editorView.root
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
package com.bartlomiejpluta.base.editor.code.view
|
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.View
|
||||||
import tornadofx.hbox
|
import tornadofx.borderpane
|
||||||
|
|
||||||
class CodeEditorView : View() {
|
class CodeEditorView : View() {
|
||||||
|
private val codeVM = find<CodeVM>()
|
||||||
|
private val editor = CodeEditor(codeVM.codeProperty)
|
||||||
|
|
||||||
override val root = hbox {}
|
override val root = borderpane {
|
||||||
|
center = editor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,3 +7,4 @@ tornadoFxVersion=2.0.0-SNAPSHOT
|
|||||||
ikonliVersion=12.2.0
|
ikonliVersion=12.2.0
|
||||||
protobufPluginVersion=0.8.14
|
protobufPluginVersion=0.8.14
|
||||||
protobufVersion=3.14.0
|
protobufVersion=3.14.0
|
||||||
|
richtextfxVersion=0.10.5
|
||||||
Reference in New Issue
Block a user