diff --git a/buildSrc/src/main/kotlin/Constants.kt b/buildSrc/src/main/kotlin/Constants.kt index 7879422..3a0734e 100644 --- a/buildSrc/src/main/kotlin/Constants.kt +++ b/buildSrc/src/main/kotlin/Constants.kt @@ -45,6 +45,11 @@ object Constants { const val dependency = "com.github.jengelman.gradle.plugins:shadow:$version" } + object RichTextFX { + const val version = "0.9.3" + const val dependency = "org.fxmisc.richtext:richtextfx:$version" + } + object KNote { const val major = 1 const val minor = 0 diff --git a/core/src/main/kotlin/knote/PageManagerImpl.kt b/core/src/main/kotlin/knote/PageManagerImpl.kt index 8fabec5..88495fa 100644 --- a/core/src/main/kotlin/knote/PageManagerImpl.kt +++ b/core/src/main/kotlin/knote/PageManagerImpl.kt @@ -139,9 +139,6 @@ internal class PageManagerImpl( page.errored = false page.result = null - if (page.text != pageScript.text) { - page.text = pageScript.text - } return page } @@ -149,11 +146,11 @@ internal class PageManagerImpl( val page = pages[id] as? PageImpl ?: return null page.compiledScript?.invalidate() page.compiledScript = null - page.text = "" page.fileInputs.clear() invalidateResult(id) + pages.remove(id) return page.dependencies } @@ -285,11 +282,6 @@ internal class PageManagerImpl( val result = executePageCached(pageId) logger.info("[$pageId] => $result") } -// notebookScript.pageFiles.forEach { -// val id = it.name.substringBeforeLast(".page.kts") -// val result = executePageCached(id) -// logger.info("[$id] => $result") -// } } } } diff --git a/core/src/main/kotlin/knote/api/Page.kt b/core/src/main/kotlin/knote/api/Page.kt index a435ca1..4e60a46 100644 --- a/core/src/main/kotlin/knote/api/Page.kt +++ b/core/src/main/kotlin/knote/api/Page.kt @@ -11,7 +11,6 @@ interface Page { val id: String val fileObject: KObservableObject val fileContentObject: KObservableObject - val textObject: KObservableObject val compiledScriptObject: KObservableObject val reportsObject: KObservableObject?> val resultObject: KObservableObject @@ -20,7 +19,6 @@ interface Page { val file get() = fileObject.value val fileContent get() = fileContentObject.value - val text get() = textObject.value val compiledScript get() = compiledScriptObject.value val reports get() = reportsObject.value val result get() = resultObject.value diff --git a/core/src/main/kotlin/knote/data/PageImpl.kt b/core/src/main/kotlin/knote/data/PageImpl.kt index a315fcf..181f008 100644 --- a/core/src/main/kotlin/knote/data/PageImpl.kt +++ b/core/src/main/kotlin/knote/data/PageImpl.kt @@ -16,7 +16,6 @@ class PageImpl( compiledScript: PageScript? = null, reports: List? = null ) : Page { - override val textObject: MutableKObservableObject = MutableKObservableObject(text) override val fileObject: MutableKObservableObject = MutableKObservableObject(file) override val fileContentObject: MutableKObservableObject = MutableKObservableObject(fileContent) override val compiledScriptObject: MutableKObservableObject = @@ -28,7 +27,6 @@ class PageImpl( MutableKObservableObject(setOf()) override val fileInputs: MutableKObservableList = MutableKObservableList() - override var text by textObject override var file by fileObject override var fileContent by fileContentObject override var compiledScript by compiledScriptObject diff --git a/tornadofx-viewer/build.gradle.kts b/tornadofx-viewer/build.gradle.kts index b238841..41e5dc0 100644 --- a/tornadofx-viewer/build.gradle.kts +++ b/tornadofx-viewer/build.gradle.kts @@ -1,4 +1,5 @@ dependencies { api(project(":core")) api(Constants.TornadoFX.dependency) + api(Constants.RichTextFX.dependency) } \ No newline at end of file diff --git a/tornadofx-viewer/src/main/kotlin/knote/tornadofx/Styles.kt b/tornadofx-viewer/src/main/kotlin/knote/tornadofx/Styles.kt index 9b24794..397a55e 100644 --- a/tornadofx-viewer/src/main/kotlin/knote/tornadofx/Styles.kt +++ b/tornadofx-viewer/src/main/kotlin/knote/tornadofx/Styles.kt @@ -1,17 +1,18 @@ package knote.tornadofx +import javafx.scene.layout.Priority import javafx.scene.paint.Color import tornadofx.* -class Styles: Stylesheet() { - companion object { - val evaluationConsole by cssclass() - } +object Styles: Stylesheet() { + + val evaluationConsole by cssclass() init { evaluationConsole { backgroundColor += Color.WHITE padding = box(10.px) } + } } \ No newline at end of file diff --git a/tornadofx-viewer/src/main/kotlin/knote/tornadofx/controller/NotebookSpaceController.kt b/tornadofx-viewer/src/main/kotlin/knote/tornadofx/controller/NotebookSpaceController.kt new file mode 100644 index 0000000..52e61e1 --- /dev/null +++ b/tornadofx-viewer/src/main/kotlin/knote/tornadofx/controller/NotebookSpaceController.kt @@ -0,0 +1,126 @@ +package knote.tornadofx.controller + +import tornadofx.* +import java.util.regex.Pattern +import javafx.concurrent.Task +import knote.tornadofx.view.NotebookSpace +import org.fxmisc.richtext.model.StyleSpans +import org.fxmisc.richtext.model.StyleSpansBuilder +import java.util.* + +class NotebookSpaceController: Controller() { + + private val view: NotebookSpace by inject() + + fun computeHighlightingAsync(): Task>> { + val text = view.codeArea.text + val task = object : Task>>() { + @Throws(Exception::class) + override fun call(): StyleSpans> { + return computeHighlighting(text) + } + } + view.executor.execute(task) + return task + } + + fun applyHighlighting(highlighting: StyleSpans>) { + view.codeArea.setStyleSpans(0, highlighting) + } + + private fun computeHighlighting(text: String): StyleSpans> { + val matcher = PATTERN.matcher(text) + var lastKwEnd = 0 + val spansBuilder = StyleSpansBuilder>() + while (matcher.find()) { + val styleClass = (when { + matcher.group("KEYWORD") != null -> "keyword" + matcher.group("PAREN") != null -> "paren" + matcher.group("BRACE") != null -> "brace" + matcher.group("BRACKET") != null -> "bracket" + matcher.group("SEMICOLON") != null -> "semicolon" + matcher.group("STRING") != null -> "string" + matcher.group("COMMENT") != null -> "comment" + else -> "" + }) + spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd); + spansBuilder.add(Collections.singleton(styleClass), matcher.end() - matcher.start()) + lastKwEnd = matcher.end() + } + spansBuilder.add(listOf(), text.length - lastKwEnd) + return spansBuilder.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" + ) + + val KEYWORD_PATTERN = "\\b(" + KEYWORDS.joinToString("|") + ")\\b" + const val PAREN_PATTERN = "\\(|\\)" + const val BRACE_PATTERN = "\\{|\\}" + const val BRACKET_PATTERN = "\\[|\\]" + const val SEMICOLON_PATTERN = "\\;" + const val STRING_PATTERN = "\"([^\"\\\\]|\\\\.)*\"" + const val COMMENT_PATTERN = "//[^\n]*" + "|" + "/\\*(.|\\R)*?\\*/" + + private val PATTERN = Pattern.compile( + "(?" + KEYWORD_PATTERN + ")" + + "|(?" + PAREN_PATTERN + ")" + + "|(?" + BRACE_PATTERN + ")" + + "|(?" + BRACKET_PATTERN + ")" + + "|(?" + SEMICOLON_PATTERN + ")" + + "|(?" + STRING_PATTERN + ")" + + "|(?" + COMMENT_PATTERN + ")" + ) + } +} \ No newline at end of file diff --git a/tornadofx-viewer/src/main/kotlin/knote/tornadofx/model/PageModel.kt b/tornadofx-viewer/src/main/kotlin/knote/tornadofx/model/PageModel.kt index bb9aeb4..aa79def 100644 --- a/tornadofx-viewer/src/main/kotlin/knote/tornadofx/model/PageModel.kt +++ b/tornadofx-viewer/src/main/kotlin/knote/tornadofx/model/PageModel.kt @@ -53,15 +53,9 @@ class PageViewModel(val page: Page, dirtyState: Boolean = false) { } -class NotebookScope(val notebook: Notebook, +class NotebookScope(val notebook: Notebook, // TODO make this observable val pageManager: PageManager, val pageViewModels: ObservableList ): Scope() { val model = NotebookViewModel() } - -class PageManagerScope(val pageManager: PageManager, - val pageViewModels: List): Scope() - - - diff --git a/tornadofx-viewer/src/main/kotlin/knote/tornadofx/view/NotebookSpace.kt b/tornadofx-viewer/src/main/kotlin/knote/tornadofx/view/NotebookSpace.kt index 48e6336..6575adb 100644 --- a/tornadofx-viewer/src/main/kotlin/knote/tornadofx/view/NotebookSpace.kt +++ b/tornadofx-viewer/src/main/kotlin/knote/tornadofx/view/NotebookSpace.kt @@ -3,6 +3,7 @@ package knote.tornadofx.view import javafx.beans.property.SimpleStringProperty import javafx.collections.ListChangeListener import javafx.geometry.Side +import javafx.scene.Node import javafx.scene.control.TabPane import javafx.scene.input.KeyCode import javafx.scene.layout.Priority @@ -10,49 +11,106 @@ import javafx.scene.paint.Color import javafx.scene.text.Font import knote.KNote import knote.tornadofx.Styles +import knote.tornadofx.controller.NotebookSpaceController import knote.tornadofx.model.NotebookScope import knote.tornadofx.model.PageViewModel +import knote.util.codearea import mu.KotlinLogging +import org.fxmisc.richtext.CodeArea +import org.fxmisc.richtext.model.StyleSpans import tornadofx.* +import java.time.Duration +import java.util.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + class NotebookSpace : View() { - val logger = KotlinLogging.logger {} + private val logger = KotlinLogging.logger {} private val tools = (1..10).toList() + /** + * TODO - + * 1) Kotlin highlighting + * 2) Create a more flexible input area -- Diagram model design + * 3) Jump to page dependencies and dependents at the drawer navigation + * 4) List file imports + * 5) Declare different result types - SPIKE + * - string + * - plot + * - pie chart + * - bar graph + * (study Jupyter to find other types of input) + * 6) Restructure result output area -- Diagram model design + * 7) Include file inputs as tabs (i.e. csv/editor) + */ + + var codeArea = CodeArea() + var executor: ExecutorService = Executors.newSingleThreadExecutor() + override val scope = super.scope as NotebookScope + private val controller: NotebookSpaceController by inject() + + private fun Node.grow(priority: Priority = Priority.ALWAYS) { + hgrow = priority + vgrow = priority + } - fun TabPane.tabPage(page: PageViewModel) { + private fun TabPane.tabPage(page: PageViewModel) { logger.info("adding tab for page: ${page.pageId}") - val tab = tab(page.pageId) { + + tabClosingPolicy = TabPane.TabClosingPolicy.UNAVAILABLE + tab(page.pageId) { borderpane { center { vbox { vbox { - textarea(page.fileContent) { + grow() + + codearea(page.fileContent) { + grow() + textProperty().addListener { _, _, new -> page.dirtyState = true -// it.fileContent = new val pageManager = KNote.NOTEBOOK_MANAGER.pageManager pageManager.updateSourceCode(page.pageId, new) } - font = Font.font("monospaced", font.size) + + multiPlainChanges() + .successionEnds(Duration.ofMillis(500)) + .supplyTask(controller::computeHighlightingAsync) + .awaitLatest(codeArea.multiPlainChanges()) + .filterMap { t -> + if (t.isSuccess) { + Optional.of(codeArea.multiPlainChanges() + .successionEnds(Duration.ofMillis(500)) + .supplyTask(controller::computeHighlightingAsync) + .awaitLatest(codeArea.multiPlainChanges()) + .filterMap { Optional.of(t.get()) }) + } else { + t.failure.printStackTrace() + Optional.of(codeArea.multiPlainChanges() + .successionEnds(Duration.ofMillis(500)) + .supplyTask(controller::computeHighlightingAsync) + .awaitLatest(codeArea.multiPlainChanges()) + .filterMap { Optional.empty>>() }) + } + } + .subscribe { controller::applyHighlighting } + } - // TODO() redo results to accept any, check NikkyAi's branch update for that + vbox { + grow(Priority.SOMETIMES) + textarea(page.resultStringProperty) { + grow(Priority.SOMETIMES) + isEditable = false font = Font.font("monospaced", font.size) - style { - hgrow = Priority.ALWAYS - vgrow = Priority.ALWAYS - } - } - style { - hgrow = Priority.ALWAYS - vgrow = Priority.ALWAYS } - minHeight = 280.0 + }.addClass(Styles.evaluationConsole) hbox { @@ -71,7 +129,7 @@ class NotebookSpace : View() { right { vbox { - maxWidth = 300.0 + maxWidth = 600.0 drawer(side = Side.RIGHT) { item("Tools", expanded = true) { datagrid(tools) { @@ -93,25 +151,29 @@ class NotebookSpace : View() { } } } - item("JVM Dependencies") { - text("List of JVM dependencies here") + item("Page Dependencies") { + + label("File Imports") + + style { + padding = box(10.px) + } } } } } } } - this.selectionModel.select(tab) } override val root = tabpane { scope.pageViewModels.addListener(ListChangeListener { change -> - while(change.next()) { + while (change.next()) { logger.debug("change: $change") if (change.wasAdded()) { logger.debug("added: ${change.addedSubList}") change.addedSubList.forEach { addedPage -> - tabPage(addedPage) + tabPage(addedPage) } } if (change.wasRemoved()) { @@ -121,15 +183,6 @@ class NotebookSpace : View() { tabs.remove(tab) } } - if(change.wasUpdated()) { - logger.debug("updated: ${change}") - } - if(change.wasReplaced()) { - logger.debug("replaced: ${change}") - } - if(change.wasPermutated()) { - logger.debug("permutated: ${change}") - } } }) scope.pageViewModels.forEach { addedPage -> @@ -155,13 +208,4 @@ class NotebookSpace : View() { } } } - - override fun onDelete() { - logger.info("clicked delete") - val text = root.selectionModel.selectedItem.text - val toRemove = scope.pageViewModels.first { it.pageId == text } - - scope.pageManager.removePage(toRemove.pageId) -// scope.pageViewModels.remove() - } } \ No newline at end of file diff --git a/tornadofx-viewer/src/main/kotlin/knote/tornadofx/view/NotebookWorkbench.kt b/tornadofx-viewer/src/main/kotlin/knote/tornadofx/view/NotebookWorkbench.kt index c85b001..d0a7008 100644 --- a/tornadofx-viewer/src/main/kotlin/knote/tornadofx/view/NotebookWorkbench.kt +++ b/tornadofx-viewer/src/main/kotlin/knote/tornadofx/view/NotebookWorkbench.kt @@ -10,9 +10,16 @@ import knote.util.BindingUtil import knote.util.asObservable import mu.KotlinLogging import tornadofx.* +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import java.util.stream.Collectors +import java.util.stream.IntStream +import java.util.stream.Stream class NotebookWorkbench : Workspace() { val logger = KotlinLogging.logger {} + override fun onBeforeShow() { KNote.NOTEBOOK_MANAGER.compileNotebookCached() logger.info("id: ${KNote.notebookId}") @@ -23,19 +30,24 @@ class NotebookWorkbench : Workspace() { BindingUtil.mapContent(pageViewModels, pageManager.pages.asObservable) { pageId, page -> val result = pageManager.executePageCached(pageId) logger.info("[$pageId]: ${result?.let { "KClass: ${it::class}" }} value: '$result'") - PageViewModel( - page - ).also { - logger.debug("mapped '$pageId'") + + // add any pageViewModels from any input .csv files + page.fileInputs.forEach { file -> + if (file.endsWith(".csv")) { + csvToPageViewModel(file) + } } + + PageViewModel(page).also { logger.debug("mapped '$pageId'") } } + pageViewModels.addListener(ListChangeListener { change -> while(change.next()) { - if(change.wasAdded()) { + if (change.wasAdded()) { logger.info("added: ${change.addedSubList}") } - if(change.wasRemoved()) { + if (change.wasRemoved()) { logger.info("removed: ${change.removed}") } } @@ -44,11 +56,38 @@ class NotebookWorkbench : Workspace() { val notebookModel = NotebookModel(KNote.NOTEBOOK_MANAGER.notebook, pageManager, pageViewModels) val notebookScope = NotebookScope( - notebookModel.notebook, - notebookModel.pageManager, - notebookModel.pageViewModels + notebookModel.notebook, + notebookModel.pageManager, + notebookModel.pageViewModels ) workspace.dock(notebookScope) } + + private fun csvToPageViewModel(path: Path) { + val headers = readCsvHeaders(path) + val result = listOf>() + + lateinit var stream: Stream + try { + stream = Files.lines(path) + /*result = stream + .skip(1) + .map { line -> line.split(",") } + .map { data -> IntStream.range(0, data.size) + .boxed() + } + .collect(Collectors.toList())*/ // TODO fix I got tired + } catch (e: IOException) { + throw e + } + + + + } + + private fun readCsvHeaders(path: Path): List { + val reader = Files.newBufferedReader(path) + return reader.readLine().split(",") + } } diff --git a/tornadofx-viewer/src/main/kotlin/knote/util/RichTextExtensions.kt b/tornadofx-viewer/src/main/kotlin/knote/util/RichTextExtensions.kt new file mode 100644 index 0000000..24a2ecf --- /dev/null +++ b/tornadofx-viewer/src/main/kotlin/knote/util/RichTextExtensions.kt @@ -0,0 +1,12 @@ +package knote.util + +import javafx.event.EventTarget +import javafx.geometry.Pos +import javafx.scene.layout.HBox +import org.fxmisc.richtext.CodeArea +import tornadofx.* + +fun EventTarget.codearea(text: String, op: CodeArea.() -> Unit = {}): CodeArea { + val codeArea = CodeArea(text) + return opcr(this, codeArea, op) +} \ No newline at end of file