From 730faf0e65e03cb60176f42111cc83e082623fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Przemys=C5=82aw=20Pluta?= Date: Thu, 16 Nov 2023 15:54:46 +0100 Subject: [PATCH] [Editor] Add support for Prolog language as Logic card --- .../build/project/DefaultProjectAssembler.kt | 1 + .../code/component/PrologFileTreeCell.kt | 114 ++++++++++++++++++ .../highlighting/PrologSyntaxHighlighter.kt | 61 ++++++++++ .../base/editor/code/model/CodeType.kt | 3 +- .../editor/code/service/PrologFileService.kt | 19 +++ .../PrologSyntaxHighlightingStylesheet.kt | 78 ++++++++++++ .../editor/code/view/editor/CodeEditorView.kt | 3 + .../code/view/editor/CodeSnippetView.kt | 3 + .../editor/code/view/list/LogicFilesView.kt | 71 +++++++++++ .../base/editor/main/view/MainView.kt | 6 + .../project/context/DefaultProjectContext.kt | 1 + .../base/editor/project/model/Project.kt | 9 ++ 12 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/component/PrologFileTreeCell.kt create mode 100644 editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/highlighting/PrologSyntaxHighlighter.kt create mode 100644 editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/service/PrologFileService.kt create mode 100644 editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/stylesheet/PrologSyntaxHighlightingStylesheet.kt create mode 100644 editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/list/LogicFilesView.kt diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/project/DefaultProjectAssembler.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/project/DefaultProjectAssembler.kt index 2c0a93b9..0d7f4a20 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/project/DefaultProjectAssembler.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/build/project/DefaultProjectAssembler.kt @@ -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 { diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/component/PrologFileTreeCell.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/component/PrologFileTreeCell.kt new file mode 100644 index 00000000..76d160ab --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/component/PrologFileTreeCell.kt @@ -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() { + 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" + } + } + } +} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/highlighting/PrologSyntaxHighlighter.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/highlighting/PrologSyntaxHighlighter.kt new file mode 100644 index 00000000..6071a698 --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/highlighting/PrologSyntaxHighlighter.kt @@ -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> = StyleSpansBuilder>().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_PATTERN)" + + "|(?$PAREN_PATTERN)" + + "|(?$BRACE_PATTERN)" + + "|(?$BRACKET_PATTERN)" + + "|(?$STRING_PATTERN)" + + "|(?$NUMBER_PATTERN)" + + "|(?$COMMENT_PATTERN)" + + "|(?$OPERATOR_PATTERN)" + ).toRegex() + } +} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/model/CodeType.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/model/CodeType.kt index 813cf653..ab9f8334 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/model/CodeType.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/model/CodeType.kt @@ -3,5 +3,6 @@ package com.bartlomiejpluta.base.editor.code.model enum class CodeType { JAVA, XML, - SQL + SQL, + PROLOG } \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/service/PrologFileService.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/service/PrologFileService.kt new file mode 100644 index 00000000..f77bd254 --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/service/PrologFileService.kt @@ -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(".") +} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/stylesheet/PrologSyntaxHighlightingStylesheet.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/stylesheet/PrologSyntaxHighlightingStylesheet.kt new file mode 100644 index 00000000..adc3bdc8 --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/stylesheet/PrologSyntaxHighlightingStylesheet.kt @@ -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("-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 + } + } +} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/editor/CodeEditorView.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/editor/CodeEditorView.kt index 036b4b48..c6592a0f 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/editor/CodeEditorView.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/editor/CodeEditorView.kt @@ -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() @@ -31,6 +33,7 @@ class CodeEditorView : View() { CodeType.JAVA -> javaSyntaxHighlighter CodeType.XML -> xmlSyntaxHighlighter CodeType.SQL -> sqlSyntaxHighlighter + CodeType.PROLOG -> prologSyntaxHighlighter } }, codeVM.typeProperty) diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/editor/CodeSnippetView.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/editor/CodeSnippetView.kt index c5cdb995..366c318c 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/editor/CodeSnippetView.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/editor/CodeSnippetView.kt @@ -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() @@ -29,6 +31,7 @@ class CodeSnippetView : View() { CodeType.JAVA -> javaSyntaxHighlighter CodeType.XML -> xmlSyntaxHighlighter CodeType.SQL -> sqlSyntaxHighlighter + CodeType.PROLOG -> prologSyntaxHighlighter } }, codeVM.typeProperty) diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/list/LogicFilesView.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/list/LogicFilesView.kt new file mode 100644 index 00000000..3aaf01eb --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/code/view/list/LogicFilesView.kt @@ -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 = 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) } + } +} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/main/view/MainView.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/main/view/MainView.kt index 7bfe174d..6038875d 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/main/view/MainView.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/main/view/MainView.kt @@ -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() private val assetsView = find() private val scriptFilesView = find() + private val logicFilesView = find() private val buildLogsView = find() private val processLogsView = find() private val projectPropertiesView = find() @@ -113,6 +115,10 @@ class MainView : View("BASE Game Editor") { this += scriptFilesView } + item("Logic", expanded = false) { + this += logicFilesView + } + item("Assets", expanded = false) { this += assetsView } diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/project/context/DefaultProjectContext.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/project/context/DefaultProjectContext.kt index adaf484c..dca4ee7f 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/project/context/DefaultProjectContext.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/project/context/DefaultProjectContext.kt @@ -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") } })) diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/project/model/Project.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/project/model/Project.kt index 0b378c2c..8da1f83b 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/project/model/Project.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/project/model/Project.kt @@ -95,6 +95,12 @@ class Project { val codeFSNodeProperty = createObjectBinding({ FileSystemNode(codeDirectory) }, codeDirectoryProperty) val codeFSNode by codeFSNodeProperty + val logicDirectoryProperty = SimpleObjectProperty() + var logicDirectory by logicDirectoryProperty + private set + val logicFSNodeProperty = createObjectBinding({ FileSystemNode(logicDirectory) }, logicDirectoryProperty) + val logicFSNode by logicFSNodeProperty + // Build directories val buildDirectoryProperty = SimpleObjectProperty() 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"