Skip to content

Commit

Permalink
Merge pull request #3 from Cullen-Shannon/add-file-service
Browse files Browse the repository at this point in the history
Added the `FileInputService` to read from & write back to the config file
  • Loading branch information
Cullen-Shannon authored Jun 1, 2024
2 parents 0c012a4 + bcfe644 commit 0dcaaa7
Show file tree
Hide file tree
Showing 12 changed files with 326 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,64 @@ package org.jetbrains.plugins.template

import com.android.ddmlib.Log
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.project.ProjectManager
import com.intellij.ui.ToolbarDecorator
import com.intellij.ui.components.JBTextField
import com.intellij.ui.dsl.builder.Cell
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.treeStructure.Tree
import org.jetbrains.plugins.template.domain.MyMenuItem
import org.jetbrains.plugins.template.services.FileInputService
import org.jetbrains.plugins.template.services.PluginSettingsService
import org.jetbrains.plugins.template.services.util.FileInputUtil
import java.awt.Dimension
import javax.swing.JComponent
import javax.swing.JPanel
import javax.swing.tree.DefaultMutableTreeNode

class AppSettingsConfigurable: Configurable {
class AppSettingsConfigurable : Configurable {

private val project = ProjectManager.getInstance().openProjects.first()
private val pluginSettingsService = PluginSettingsService.getInstance(project)
private val fileInputService = FileInputService.getInstance(project)

// The tree representing the current `MyMenuItem` config, update this when adding/removing elements
private var currentTree: Tree? = null

// dummy item for dev purposes
val dummyMenuItem = MyMenuItem(
text = "text",
description = "description"
private val dummyMenuItem = MyMenuItem(
text = "text",
description = "description"
)

// should consider moving this to its own class
override fun createComponent(): JComponent? {
// Keep track of the "Path" text field so that we can read the value
private var pathTextFieldCell: Cell<JBTextField>? = null

// `isModified` controls enabling/disabling the "Apply" button
private var isModified = false

override fun createComponent(): JComponent {
return panel {
row("Path: ") {
textField()
pathTextFieldCell = textField()

// Prefill the existing file name by default
pathTextFieldCell?.component?.text = pluginSettingsService.state.fileName

// Add a document listener to enable the Apply button when the text changes
pathTextFieldCell?.component?.document?.addDocumentListener(object : javax.swing.event.DocumentListener {
override fun insertUpdate(e: javax.swing.event.DocumentEvent?) {
isModified = true
}

override fun removeUpdate(e: javax.swing.event.DocumentEvent?) {
isModified = true
}

override fun changedUpdate(e: javax.swing.event.DocumentEvent?) {
isModified = true
}
})
}
row {
cell(myTreeDecorated())
Expand All @@ -31,36 +68,59 @@ class AppSettingsConfigurable: Configurable {
}

private fun myTreeDecorated(): JPanel {
// todo will load from data source when ready
val root = DefaultMutableTreeNode("NFCU")
val adoNode = DefaultMutableTreeNode("ADO")
val riseNode = DefaultMutableTreeNode("Rise Modules")
riseNode.add(DefaultMutableTreeNode("Environmental Setup sdkf dskf dskjf dskj dks jkds fksd"))
riseNode.add(DefaultMutableTreeNode("Emulator Setup"))
root.add(adoNode)
root.add(riseNode)
val tree = Tree(root)
tree.isRootVisible = true
val decoratedTree = ToolbarDecorator.createDecorator(tree)
.setAddAction {
if (MyDialog(dummyMenuItem).showAndGet()) {
Log.e("TAGX", "Got it!")
}
val menuItem = fileInputService.readConfigFileContents()

var rootNode: DefaultMutableTreeNode? = null

// TODO: Error Handling
if (menuItem != null) {
// Update the plugin's model with the menu that was just read in
pluginSettingsService.state.currentMenuItemConfig = menuItem
rootNode = DefaultMutableTreeNode(menuItem.text)
if (menuItem.children != null) {
FileInputUtil.readInTreeChildren(rootNode = rootNode, children = menuItem.children!!)
}
.setRemoveAction { Log.e("TAGX", "TODO remove") }
.setEditAction { Log.e("TAGX", "TODO edit") }
.setPreferredSize(Dimension(400, 300)) // TODO -- can't figure out a way around needing this
.createPanel()
}

currentTree = Tree(rootNode)
currentTree!!.isRootVisible = true

val decoratedTree = ToolbarDecorator.createDecorator(currentTree!!)
.setAddAction {
if (MyDialog(dummyMenuItem).showAndGet()) {
Log.e("TAGX", "Got it!")
}
}
.setRemoveAction { Log.e("TAGX", "TODO remove") }
.setEditAction { Log.e("TAGX", "TODO edit") }
.setPreferredSize(Dimension(400, 300)) // TODO -- can't figure out a way around needing this
.createPanel()
return decoratedTree
}

// TODO everything below
override fun isModified(): Boolean {
return false
return isModified
}

override fun apply() {
// update the settings
// TODO: Error Handling

// Try to read in the updated file name from the "Path" textInput, if not use the existing value from the plugin's state
val updatedFileName = pathTextFieldCell?.component?.text

val updatedMenuItem = FileInputUtil.convertTreeToMyMenuItem(
currentTree,
pluginSettingsService.state.currentMenuItemConfig ?: MyMenuItem(text = "", description = "")
)

if (updatedMenuItem != null) {
// Update the file with the changes that the user made
fileInputService.writeConfigFileContents(updatedMenuItem, newFileName = updatedFileName)
}

// Disable the "Apply" button after the updates are made
isModified = false

}

override fun getDisplayName(): String {
Expand Down
45 changes: 27 additions & 18 deletions src/main/java/org/jetbrains/plugins/template/DynamicActionGroup.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package org.jetbrains.plugins.template

import com.google.gson.Gson
import com.intellij.openapi.actionSystem.ActionGroup
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.Separator
import com.intellij.openapi.fileEditor.impl.LoadTextUtil
import com.intellij.psi.search.FilenameIndex
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.openapi.project.ProjectManager
import com.intellij.ui.treeStructure.Tree
import org.jetbrains.plugins.template.domain.MyMenuItem
import org.jetbrains.plugins.template.services.FileInputService
import org.jetbrains.plugins.template.services.PluginSettingsService
import javax.swing.tree.DefaultMutableTreeNode

/* TODOs
replace pluginIcon.svg with something more meaningful
Expand All @@ -20,25 +22,32 @@ import com.intellij.psi.search.GlobalSearchScope
* Represents a group of menu items. menuItem will be null for the top level menu defined in plugin.xml.
* Otherwise we can pass in the child object recursively to allow for infinite nesting based on user configuration.
*/
class DynamicActionGroup(var menuItem: MyMenuItem? = null): ActionGroup() {
class DynamicActionGroup(var menuItem: MyMenuItem? = null, ) : ActionGroup() {

override fun update(e: AnActionEvent) {
val project = e.project
e.presentation.isEnabledAndVisible = project != null
if (project == null) return
private val project = ProjectManager.getInstance().openProjects.first()
private val fileInputService = FileInputService.getInstance(project)
private val pluginSettingsService = PluginSettingsService.getInstance(project)

override fun update(e: AnActionEvent) {
// TODO: Error Handling
if (menuItem == null) {
// todo: error handling -- missing file, malformed json, etc., changing file name, etc.
val config = FilenameIndex.getVirtualFilesByName("config.json", GlobalSearchScope.allScope(project))
if (config.size != 1) throw Exception("Unexpected number of config files")
val text = LoadTextUtil.loadText(config.first())
menuItem = Gson().fromJson(text.toString(), MyMenuItem::class.java)
menuItem!!.isTopLevel = true
// Read in top level menu item
menuItem = fileInputService.readConfigFileContents()

if (menuItem != null) {
// Update the current model with the menu that was just read in
pluginSettingsService.state.currentMenuItemConfig = menuItem
}
}
e.presentation.isPopupGroup = true
e.presentation.text = menuItem!!.text
e.presentation.description = menuItem!!.description

val rootNode = DefaultMutableTreeNode(menuItem?.text)

val tree = Tree(rootNode)

tree.isRootVisible = true
e.presentation.isPopupGroup = true
e.presentation.text = menuItem?.text
e.presentation.description = menuItem?.description
}

override fun getChildren(e: AnActionEvent?): Array<AnAction> {
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/jetbrains/plugins/template/MyAction.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.intellij.ide.browsers.BrowserLauncher
import com.intellij.ide.browsers.WebBrowserManager
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import org.jetbrains.plugins.template.domain.MyMenuItem

/**
* Represents a single actionable menu item. This will only be invoked once we've reached the bottom of the recursive
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/jetbrains/plugins/template/MyDialog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.intellij.openapi.ui.DialogWrapper
import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.dsl.builder.*
import com.intellij.ui.layout.not
import org.jetbrains.plugins.template.domain.MyMenuItem
import javax.swing.JComponent

class MyDialog(private val myMenuItem: MyMenuItem): DialogWrapper(true) {
Expand Down
11 changes: 0 additions & 11 deletions src/main/java/org/jetbrains/plugins/template/MyMenuItem.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.jetbrains.plugins.template.domain

/*
Stores the state for the currently installed plugin
`fileName`: the name of the file that can be configured by the user within `AppSettingsConfigurable`
`currentMenuItemConfig`: `MyMenuItem` corresponding to the existing menu item data that was previously read in from the user's config file
*/
data class FilePluginState(
var fileName: String = "config.json",
var currentMenuItemConfig: MyMenuItem? = null
)
11 changes: 11 additions & 0 deletions src/main/java/org/jetbrains/plugins/template/domain/MyMenuItem.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.jetbrains.plugins.template.domain

data class MyMenuItem(
var text: String,
var description: String,
var url: String? = null,
var children: List<MyMenuItem>? = null,
var addSeparatorBefore: Boolean = false,
var addSeparatorAfter: Boolean = false,
var isTopLevel: Boolean = false
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.jetbrains.plugins.template.services

import com.google.gson.Gson
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.components.Service
import com.intellij.openapi.fileEditor.impl.LoadTextUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.FilenameIndex
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.plugins.template.domain.MyMenuItem
import java.io.IOException

@Service(Service.Level.PROJECT)
class FileInputService {

private val project = ProjectManager.getInstance().openProjects.first()
private val pluginSettingsService = PluginSettingsService.getInstance(project)

fun readConfigFileContents(): MyMenuItem? {
return try {
val configFile = getCurrentConfigFile()
var menuItem: MyMenuItem? = null

if (configFile != null) {
val text = LoadTextUtil.loadText(configFile)
menuItem = Gson().fromJson(text.toString(), MyMenuItem::class.java)
}

return menuItem
} catch (e: IOException) {
null // TODO: Error Handling
}
}

fun writeConfigFileContents(newMenu: MyMenuItem, newFileName: String?) {
WriteCommandAction.runWriteCommandAction(project) {
try {
val updatedJSON = Gson().toJson(newMenu, MyMenuItem::class.java)
val configFile = getCurrentConfigFile()

if (newFileName?.isNotEmpty() == true) {
// Update the plugin's model to remember this new file name
pluginSettingsService.loadState(state = pluginSettingsService.state.copy(fileName = newFileName))
// Update the file's name with the new value that the user entered, or the existing name
// TODO: Add input validation and verify that this works for changing the file's path (not just renaming)
configFile!!.rename(this, newFileName)
}
// Ensure that the file is writable
configFile!!.isWritable = true

// Write the new content to the file
// TODO: Check if there is an easy way to have the updated JSON be formatted instead of all on one line
VfsUtil.saveText(configFile, updatedJSON)
} catch (e: IOException) {
// TODO: Error Handling
e.printStackTrace()
}
}
}

private fun getCurrentConfigFile(): VirtualFile? {
val fileName = pluginSettingsService.state.fileName
return FilenameIndex.getVirtualFilesByName(fileName, GlobalSearchScope.allScope(project)).firstOrNull()
}

companion object {
fun getInstance(project: Project): FileInputService {
return project.getService(FileInputService::class.java)
}
}
}

This file was deleted.

Loading

0 comments on commit 0dcaaa7

Please sign in to comment.