[Editor] Add support for Prolog language as Logic card

This commit is contained in:
2023-11-16 15:54:46 +01:00
parent 6dc567e054
commit 730faf0e65
12 changed files with 368 additions and 1 deletions

View File

@@ -36,6 +36,7 @@ class DefaultProjectAssembler : ProjectAssembler {
packager.pack(project.fontsDirectory, targetJar, "BOOT-INF/classes/project/fonts")
packager.pack(project.widgetsDirectory, targetJar, "BOOT-INF/classes/project/widgets")
packager.pack(project.audioDirectory, targetJar, "BOOT-INF/classes/project/audio")
packager.pack(project.logicDirectory, targetJar, "BOOT-INF/classes/project/logic")
}
companion object {

View File

@@ -0,0 +1,114 @@
package com.bartlomiejpluta.base.editor.code.component
import com.bartlomiejpluta.base.editor.file.model.FileNode
import com.bartlomiejpluta.base.editor.file.model.FileSystemNode
import com.bartlomiejpluta.base.editor.file.model.FileType
import javafx.scene.control.ContextMenu
import javafx.scene.control.cell.TextFieldTreeCell
import org.kordamp.ikonli.javafx.FontIcon
import tornadofx.action
import tornadofx.enableWhen
import tornadofx.item
import tornadofx.toProperty
class PrologFileTreeCell(onCreate: (FileNode) -> Unit, onDelete: (FileNode) -> Unit) :
TextFieldTreeCell<FileNode>() {
private val isRoot = true.toProperty()
private val isNotRoot = isRoot.not()
private val fileMenu = ContextMenu().apply {
item("Rename") {
enableWhen(isNotRoot)
action {
treeView.isEditable = true
startEdit()
treeView.isEditable = false
}
}
item("Delete") {
enableWhen(isNotRoot)
action {
item.delete()
onDelete(item)
}
}
}
private val directoryMenu = ContextMenu().apply {
item("New File...") {
action { onCreate(item) }
}
item("Refresh") {
action { item.refresh() }
}
item("Rename") {
enableWhen(isNotRoot)
action {
treeView.isEditable = true
startEdit()
treeView.isEditable = false
}
}
item("Delete") {
enableWhen(isNotRoot)
action {
item.delete()
onDelete(item)
}
}
}
init {
converter = ScriptFileStringConverter(this, this::renameFile)
}
private fun renameFile(file: FileNode, name: String) = file.apply {
file.rename(name)
}
override fun updateItem(item: FileNode?, empty: Boolean) {
super.updateItem(item, empty)
if (empty || item == null) {
text = null
graphic = null
contextMenu = null
isRoot.value = true
return
}
text = item.name
graphic = FontIcon(getFileNodeIcon(item))
contextMenu = when {
isEditing -> null
item !is FileSystemNode -> null
item.type == FileType.FILE -> fileMenu
item.type == FileType.DIRECTORY -> directoryMenu
else -> null
}
isRoot.value = (item.parent == null)
}
companion object {
private fun getFileNodeIcon(file: FileNode): String {
if (file.type == FileType.DIRECTORY) {
return "fa-folder"
}
return when (file.extension.toLowerCase()) {
"java" -> "fa-code"
else -> "fa-file"
}
}
}
}

View File

@@ -0,0 +1,61 @@
package com.bartlomiejpluta.base.editor.code.highlighting
import com.bartlomiejpluta.base.editor.code.stylesheet.PrologSyntaxHighlightingStylesheet
import org.fxmisc.richtext.model.StyleSpans
import org.fxmisc.richtext.model.StyleSpansBuilder
import org.springframework.stereotype.Component
@Component
class PrologSyntaxHighlighter : 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["VARIABLE"] != null -> "variable"
result.groups["PAREN"] != null -> "paren"
result.groups["BRACE"] != null -> "brace"
result.groups["BRACKET"] != null -> "bracket"
result.groups["STRING"] != null -> "string"
result.groups["NUMBER"] != null -> "number"
result.groups["OPERATOR"] != null -> "operator"
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()
}
override val stylesheet = PrologSyntaxHighlightingStylesheet()
companion object {
private val VARIABLE_PATTERN = "([A-Z]\\w*)"
private val PAREN_PATTERN = "\\(|\\)"
private val BRACE_PATTERN = "\\{|\\}"
private val BRACKET_PATTERN = "\\[|\\]"
private val STRING_PATTERN = "((\"([^\"\\\\]|\\\\.)*\")|('([^'\\\\]|\\\\.)*'))"
private val NUMBER_PATTERN = "\\b-?([0-9]*\\.[0-9]+|[0-9]+)\\w?\\b"
private val OPERATOR_PATTERN = "[+-/*:|&?<>=!;]"
private val COMMENT_PATTERN = """
%[^
]*|/\*(.|\R)*?\*/
""".trimIndent()
private val PATTERN = (
"(?<VARIABLE>$VARIABLE_PATTERN)"
+ "|(?<PAREN>$PAREN_PATTERN)"
+ "|(?<BRACE>$BRACE_PATTERN)"
+ "|(?<BRACKET>$BRACKET_PATTERN)"
+ "|(?<STRING>$STRING_PATTERN)"
+ "|(?<NUMBER>$NUMBER_PATTERN)"
+ "|(?<COMMENT>$COMMENT_PATTERN)"
+ "|(?<OPERATOR>$OPERATOR_PATTERN)"
).toRegex()
}
}

View File

@@ -3,5 +3,6 @@ package com.bartlomiejpluta.base.editor.code.model
enum class CodeType {
JAVA,
XML,
SQL
SQL,
PROLOG
}

View File

@@ -0,0 +1,19 @@
package com.bartlomiejpluta.base.editor.code.service
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.ApplicationContext
import org.springframework.stereotype.Component
import java.nio.file.Path
@Component
class PrologFileService {
@Autowired
private lateinit var appContext: ApplicationContext
fun toPathString(className: String) = toPath(className).toString() + ".pl"
fun toPath(className: String) = Path.of("", *className.split(".").toTypedArray())
fun ofPath(path: String): String = ofPath(Path.of(path))
fun ofPath(path: Path): String = path.joinToString(".")
}

View File

@@ -0,0 +1,78 @@
package com.bartlomiejpluta.base.editor.code.stylesheet
import javafx.scene.text.FontPosture
import javafx.scene.text.FontWeight
import tornadofx.*
class PrologSyntaxHighlightingStylesheet : CodeEditorStylesheet() {
companion object {
val variable by cssclass()
val argument by cssclass()
val paren by cssclass()
val bracket by cssclass()
val brace by cssclass()
val string by cssclass()
val number by cssclass()
val operator by cssclass()
val field by cssclass()
val comment by cssclass()
val paragraphBox by cssclass()
val paragraphText by cssclass()
val hasCaret by csspseudoclass()
val tabSize by cssproperty<Int>("-fx-tab-size")
}
init {
paren {
fill = c("cadetblue")
fontWeight = FontWeight.BOLD
}
bracket {
fill = c("darkgreen")
fontWeight = FontWeight.BOLD
}
brace {
fill = c("teal")
fontWeight = FontWeight.BOLD
}
string {
fill = c("#008000")
}
number {
fill = c("#0000FF")
}
operator {
fill = c("#CC7832")
}
variable {
fill = c("#BBB529")
fontWeight = FontWeight.BOLD
}
argument {
fill = c("#000080")
fontWeight = FontWeight.BOLD
}
comment {
fill = c("#808080")
fontStyle = FontPosture.ITALIC
}
paragraphBox and hasCaret {
backgroundColor = multi(c("#f2f9fc"))
}
paragraphText {
tabSize.value = 3
}
}
}

View File

@@ -2,6 +2,7 @@ package com.bartlomiejpluta.base.editor.code.view.editor
import com.bartlomiejpluta.base.editor.code.component.CodeEditor
import com.bartlomiejpluta.base.editor.code.highlighting.JavaSyntaxHighlighter
import com.bartlomiejpluta.base.editor.code.highlighting.PrologSyntaxHighlighter
import com.bartlomiejpluta.base.editor.code.highlighting.SqlSyntaxHighlighter
import com.bartlomiejpluta.base.editor.code.highlighting.XmlSyntaxHighlighter
import com.bartlomiejpluta.base.editor.code.model.CodeScope
@@ -23,6 +24,7 @@ class CodeEditorView : View() {
private val javaSyntaxHighlighter: JavaSyntaxHighlighter by di()
private val xmlSyntaxHighlighter: XmlSyntaxHighlighter by di()
private val sqlSyntaxHighlighter: SqlSyntaxHighlighter by di()
private val prologSyntaxHighlighter: PrologSyntaxHighlighter by di()
private val codeVM = find<CodeVM>()
@@ -31,6 +33,7 @@ class CodeEditorView : View() {
CodeType.JAVA -> javaSyntaxHighlighter
CodeType.XML -> xmlSyntaxHighlighter
CodeType.SQL -> sqlSyntaxHighlighter
CodeType.PROLOG -> prologSyntaxHighlighter
}
}, codeVM.typeProperty)

View File

@@ -2,6 +2,7 @@ package com.bartlomiejpluta.base.editor.code.view.editor
import com.bartlomiejpluta.base.editor.code.component.CodeEditor
import com.bartlomiejpluta.base.editor.code.highlighting.JavaSyntaxHighlighter
import com.bartlomiejpluta.base.editor.code.highlighting.PrologSyntaxHighlighter
import com.bartlomiejpluta.base.editor.code.highlighting.SqlSyntaxHighlighter
import com.bartlomiejpluta.base.editor.code.highlighting.XmlSyntaxHighlighter
import com.bartlomiejpluta.base.editor.code.model.CodeScope
@@ -21,6 +22,7 @@ class CodeSnippetView : View() {
private val javaSyntaxHighlighter: JavaSyntaxHighlighter by di()
private val xmlSyntaxHighlighter: XmlSyntaxHighlighter by di()
private val sqlSyntaxHighlighter: SqlSyntaxHighlighter by di()
private val prologSyntaxHighlighter: PrologSyntaxHighlighter by di()
private val codeVM = find<CodeVM>()
@@ -29,6 +31,7 @@ class CodeSnippetView : View() {
CodeType.JAVA -> javaSyntaxHighlighter
CodeType.XML -> xmlSyntaxHighlighter
CodeType.SQL -> sqlSyntaxHighlighter
CodeType.PROLOG -> prologSyntaxHighlighter
}
}, codeVM.typeProperty)

View File

@@ -0,0 +1,71 @@
package com.bartlomiejpluta.base.editor.code.view.list
import com.bartlomiejpluta.base.editor.code.api.APIProvider
import com.bartlomiejpluta.base.editor.code.component.PrologFileTreeCell
import com.bartlomiejpluta.base.editor.code.component.ScriptFileTreeCell
import com.bartlomiejpluta.base.editor.code.service.PrologFileService
import com.bartlomiejpluta.base.editor.file.model.FileNode
import com.bartlomiejpluta.base.editor.file.model.FileType
import com.bartlomiejpluta.base.editor.file.model.PseudoFileNode
import com.bartlomiejpluta.base.editor.main.controller.MainController
import com.bartlomiejpluta.base.editor.project.context.ProjectContext
import javafx.scene.control.TextInputDialog
import javafx.scene.control.TreeItem
import javafx.scene.control.TreeView
import javafx.scene.input.MouseButton
import tornadofx.View
import tornadofx.expandAll
import tornadofx.populate
import tornadofx.treeview
class LogicFilesView : View() {
private val projectContext: ProjectContext by di()
private val mainController: MainController by di()
private val prologFileService: PrologFileService by di()
init {
projectContext.projectProperty.addListener { _, _, project ->
project?.let {
val rootNode = PseudoFileNode.emptyRoot().apply {
children += it.logicFSNode
}
treeView.root = TreeItem(rootNode)
treeView.populate { item -> item.value?.children }
root.root.children[0].expandAll()
}
}
}
private val treeView: TreeView<FileNode> = treeview {
isShowRoot = false
setCellFactory {
PrologFileTreeCell(this@LogicFilesView::onCreate, mainController::closeScript)
}
setOnMouseClicked { event ->
if (event.button == MouseButton.PRIMARY && event.clickCount == 2) {
selectionModel?.selectedItem?.value
.takeIf { it?.type == FileType.FILE }
?.let { mainController.openScript(it) }
event.consume()
}
}
}
override val root = treeView
private fun onCreate(fsNode: FileNode) {
TextInputDialog().apply {
width = 300.0
contentText = "File name"
title = "New file"
}
.showAndWait()
.map(prologFileService::toPathString)
.map { fsNode.createNode(it) }
.ifPresent { mainController.openScript(it) }
}
}

View File

@@ -5,6 +5,7 @@ import com.bartlomiejpluta.base.editor.asset.view.list.AssetsListView
import com.bartlomiejpluta.base.editor.asset.viewmodel.GraphicAssetVM
import com.bartlomiejpluta.base.editor.code.view.build.BuildLogsView
import com.bartlomiejpluta.base.editor.code.view.editor.CodeEditorFragment
import com.bartlomiejpluta.base.editor.code.view.list.LogicFilesView
import com.bartlomiejpluta.base.editor.code.view.list.ScriptFilesView
import com.bartlomiejpluta.base.editor.code.viewmodel.CodeVM
import com.bartlomiejpluta.base.editor.database.view.list.TablesListView
@@ -34,6 +35,7 @@ class MainView : View("BASE Game Editor") {
private val mainMenuView = find<MainMenuView>()
private val assetsView = find<AssetsListView>()
private val scriptFilesView = find<ScriptFilesView>()
private val logicFilesView = find<LogicFilesView>()
private val buildLogsView = find<BuildLogsView>()
private val processLogsView = find<ProcessLogsView>()
private val projectPropertiesView = find<ProjectParametersView>()
@@ -113,6 +115,10 @@ class MainView : View("BASE Game Editor") {
this += scriptFilesView
}
item("Logic", expanded = false) {
this += logicFilesView
}
item("Assets", expanded = false) {
this += assetsView
}

View File

@@ -363,6 +363,7 @@ class DefaultProjectContext : ProjectContext {
"java" -> CodeType.JAVA
"xml" -> CodeType.XML
"sql" -> CodeType.SQL
"pl" -> CodeType.PROLOG
else -> throw IllegalStateException("Unsupported script type")
}
}))

View File

@@ -95,6 +95,12 @@ class Project {
val codeFSNodeProperty = createObjectBinding({ FileSystemNode(codeDirectory) }, codeDirectoryProperty)
val codeFSNode by codeFSNodeProperty
val logicDirectoryProperty = SimpleObjectProperty<File>()
var logicDirectory by logicDirectoryProperty
private set
val logicFSNodeProperty = createObjectBinding({ FileSystemNode(logicDirectory) }, logicDirectoryProperty)
val logicFSNode by logicFSNodeProperty
// Build directories
val buildDirectoryProperty = SimpleObjectProperty<File>()
var buildDirectory by buildDirectoryProperty
@@ -154,6 +160,7 @@ class Project {
widgetsDirectory = File(it, WIDGETS_DIR)
audioDirectory = File(it, AUDIO_DIR)
codeDirectory = File(it, CODE_DIR)
logicDirectory = File(it, LOGIC_DIR)
buildDirectory = File(it, BUILD_DIR)
buildClassesDirectory = File(it, BUILD_CLASSES_DIR)
buildDependenciesDirectory = File(it, BUILD_DEPENDENCIES_DIR)
@@ -187,6 +194,7 @@ class Project {
widgetsDirectory?.mkdirs()
audioDirectory?.mkdirs()
codeDirectory?.mkdirs()
logicDirectory?.mkdirs()
}
companion object {
@@ -207,6 +215,7 @@ class Project {
const val WIDGETS_DIR = "widgets"
const val AUDIO_DIR = "audio"
const val CODE_DIR = "src/main/java"
const val LOGIC_DIR = "src/main/prolog"
const val BUILD_DIR = "build"
const val BUILD_CLASSES_DIR = "$BUILD_DIR/classes"
const val BUILD_OUT_DIR = "$BUILD_DIR/out"