Merge branch 'idea-201' into idea-193

This commit is contained in:
Matthias Gugel 2021-08-07 08:45:16 +02:00
commit f8aeb13980
17 changed files with 241 additions and 155 deletions

View File

@ -23,6 +23,15 @@ intellij {
} }
tasks.getByName<org.jetbrains.intellij.tasks.PatchPluginXmlTask>("patchPluginXml") { tasks.getByName<org.jetbrains.intellij.tasks.PatchPluginXmlTask>("patchPluginXml") {
changeNotes(""" changeNotes("""
<h3>[1.2]</h3>
<ul>
<li>Single New menu action to access filename template configurations</li>
<li>Simplify template configuration with placeholder auto completion</li>
<li>New templates are active by default</li>
<li>Combine name and description into title</li>
</ul>
<h3>[1.1]</h3> <h3>[1.1]</h3>
<ul> <ul>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,4 +1,4 @@
kotlin.code.style=official kotlin.code.style=official
pluginVersion = 1.1.0-193 pluginVersion = 1.2.0-193
platformVersion = 2019.3 platformVersion = 2019.3

View File

@ -4,38 +4,64 @@ import com.intellij.icons.AllIcons
import com.intellij.ide.IdeBundle import com.intellij.ide.IdeBundle
import com.intellij.ide.actions.CreateFileAction import com.intellij.ide.actions.CreateFileAction
import com.intellij.ide.ui.newItemPopup.NewItemPopupUtil import com.intellij.ide.ui.newItemPopup.NewItemPopupUtil
import com.intellij.ide.ui.newItemPopup.NewItemSimplePopupPanel
import com.intellij.lang.LangBundle import com.intellij.lang.LangBundle
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.InputValidatorEx import com.intellij.openapi.ui.InputValidatorEx
import com.intellij.openapi.util.Trinity
import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import de.idetools.filename_template.placeholders.PlaceholderProcessor import de.idetools.filename_template.placeholders.*
import de.idetools.filename_template.settings.FilenameTemplateSettingsState
import java.awt.event.InputEvent import java.awt.event.InputEvent
import java.util.function.Consumer import java.util.function.Consumer
import kotlin.math.max
class CreateFileWithFilenameTemplateAction( class CreateFileWithFilenameTemplateAction :
text: String, CreateFileAction("Filename Template", "Create a new file based on template", AllIcons.FileTypes.AddAny) {
description: String,
private val placeholderProcessor: PlaceholderProcessor private val placeholders: List<Placeholder> = listOf(
) : EpochPlaceholder(),
CreateFileAction(text, description, AllIcons.FileTypes.Unknown) { DatePlaceholder(),
ProjectPlaceholder(),
SequencePlaceholder(),
HostPlaceholder(),
UserPlaceholder(),
DefaultPlaceholder()
)
override fun update(e: AnActionEvent) {
if (FilenameTemplateSettingsState.instance.filenameTemplates.isEmpty()) {
e.presentation.isEnabledAndVisible = false
} else {
super.update(e)
}
}
override fun invokeDialog( override fun invokeDialog(
project: Project, project: Project,
directory: PsiDirectory, directory: PsiDirectory,
elementsConsumer: Consumer<Array<PsiElement?>?> elementsConsumer: Consumer<Array<PsiElement?>?>
) { ) {
val settings: FilenameTemplateSettingsState = FilenameTemplateSettingsState.instance
settings.filenameTemplates.forEach {
if (it.title.isBlank()) {
it.title = if (it.description.isBlank()) it.name else "${it.name} - ${it.description}"
}
it.name = ""
it.description = ""
}
val templates = settings.filenameTemplates
.filter { it.isActive }
.map {
Trinity(it.title, AllIcons.FileTypes.AddAny, processTemplate(it.template, project, directory))
}
val validator: MyInputValidator = MyValidator(project, directory) val validator: MyInputValidator = MyValidator(project, directory)
val contentPanel = NewItemSimplePopupPanel() val contentPanel = CreateWithFilenameTemplateDialogPanel(templates)
val nameField = contentPanel.textField val nameField = contentPanel.textField
val (filename, caretPosition) = placeholderProcessor.processTemplate(project, directory)
nameField.text = filename
nameField.caretPosition = caretPosition
val popup = NewItemPopupUtil.createNewItemPopup(IdeBundle.message("title.new.file"), contentPanel, nameField) val popup = NewItemPopupUtil.createNewItemPopup(IdeBundle.message("title.new.file"), contentPanel, nameField)
contentPanel.setApplyAction { event: InputEvent? -> contentPanel.setApplyAction { event: InputEvent? ->
val name = nameField.text val name = nameField.text
if (validator.checkInput(name) && validator.canClose(name)) { if (validator.checkInput(name) && validator.canClose(name)) {
@ -52,4 +78,46 @@ class CreateFileWithFilenameTemplateAction(
} }
popup.showCenteredInCurrentWindow(project) popup.showCenteredInCurrentWindow(project)
} }
private fun processTemplate(template: String, project: Project, directory: PsiDirectory): String {
var placeholderLevel = 0
var placeholder = ""
val context = PlaceholderContext(project, directory)
for (character in template) {
when (character) {
'{' -> {
if (0 < placeholderLevel) {
placeholder += character
}
placeholderLevel += 1
}
'}' -> {
if (placeholderLevel == 1) {
context.filename += placeholders
.first { it.supports(placeholder) }
.process(placeholder, context)
placeholder = ""
}
placeholderLevel = max(0, placeholderLevel - 1)
if (0 < placeholderLevel) {
placeholder += character
}
}
else -> {
if (0 < placeholderLevel) {
placeholder += character
} else {
context.filename += character
}
}
}
}
return context.filename
}
} }

View File

@ -0,0 +1,32 @@
package de.idetools.filename_template
import com.intellij.ide.actions.newclass.CreateWithTemplatesDialogPanel
import com.intellij.openapi.util.Trinity
import javax.swing.Icon
class CreateWithFilenameTemplateDialogPanel(templates: List<Trinity<String, Icon, String>>) :
CreateWithTemplatesDialogPanel(templates, templates.first().first) {
init {
updateSelectedText()
myTemplatesList.addListSelectionListener { updateSelectedText() }
}
private fun updateSelectedText() {
val selectedValue = myTemplatesList.selectedValue
if (selectedValue != null) {
var filename = selectedValue.third
val caretPosition = determineCaretPosition(filename)
filename = filename.replace("{|}", "")
myTextField.text = filename
nameField.caretPosition = caretPosition
}
}
private fun determineCaretPosition(filename: String): Int = when {
filename.indexOf("{|}") != -1 -> filename.indexOf("{|}")
filename.indexOfFirst { it == '.' } != -1 -> filename.indexOfFirst { it == '.' }
else -> filename.length
}
}

View File

@ -1,58 +0,0 @@
package de.idetools.filename_template
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.Anchor
import com.intellij.openapi.actionSystem.Constraints
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.StartupActivity
import de.idetools.filename_template.placeholders.DatePlaceholder
import de.idetools.filename_template.placeholders.DefaultPlaceholder
import de.idetools.filename_template.placeholders.EpochPlaceholder
import de.idetools.filename_template.placeholders.HostPlaceholder
import de.idetools.filename_template.placeholders.PlaceholderProcessor
import de.idetools.filename_template.placeholders.ProjectPlaceholder
import de.idetools.filename_template.placeholders.SequencePlaceholder
import de.idetools.filename_template.placeholders.UserPlaceholder
import de.idetools.filename_template.settings.FilenameTemplateSettingsState
class InitializeFilenameTemplateActivity : StartupActivity {
override fun runActivity(project: Project) {
initializeActions()
}
internal fun initializeActions() {
val settings: FilenameTemplateSettingsState = FilenameTemplateSettingsState.instance
val placeholders = listOf(
EpochPlaceholder(),
DatePlaceholder(),
ProjectPlaceholder(),
HostPlaceholder(),
SequencePlaceholder(),
UserPlaceholder(),
DefaultPlaceholder()
)
val actionManager = ActionManager.getInstance()
val newGroup = actionManager.getAction("NewGroup") as DefaultActionGroup
actionManager.getActionIds("FilenameTemplate").forEach {
newGroup.remove(actionManager.getAction(it))
actionManager.unregisterAction(it)
}
var anchorId = "NewFile"
settings.filenameTemplates.filter { it.isActive }.forEach {
val action = CreateFileWithFilenameTemplateAction(
it.name,
it.description,
PlaceholderProcessor(it.template, placeholders)
)
val actionId = "FilenameTemplate.${it.name}"
actionManager.registerAction(actionId, action)
newGroup.addAction(action, Constraints(Anchor.AFTER, anchorId))
anchorId = actionId
}
}
}

View File

@ -1,57 +0,0 @@
package de.idetools.filename_template.placeholders
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDirectory
import kotlin.math.max
class PlaceholderProcessor(private val template: String, private val placeholders: List<Placeholder>) {
fun processTemplate(project: Project, directory: PsiDirectory): ProcessedTemplate {
var placeholderLevel = 0
var placeholder = ""
val context = PlaceholderContext(project, directory)
for (character in template) {
when (character) {
'{' -> {
if (0 < placeholderLevel) {
placeholder += character
}
placeholderLevel += 1
}
'}' -> {
if (placeholderLevel == 1) {
context.filename += placeholders
.first { it.supports(placeholder) }
.process(placeholder, context)
placeholder = ""
}
placeholderLevel = max(0, placeholderLevel - 1)
if (0 < placeholderLevel) {
placeholder += character
}
}
else -> {
if (0 < placeholderLevel) {
placeholder += character
} else {
context.filename += character
}
}
}
}
val caretPosition = when {
context.filename.indexOf("{|}") != -1 -> context.filename.indexOf("{|}")
context.filename.indexOfFirst { it == '.' } != -1 -> context.filename.indexOfFirst { it == '.' }
else -> context.filename.length
}
context.filename = context.filename.replace("{|}", "")
return ProcessedTemplate(context.filename, caretPosition)
}
}

View File

@ -2,7 +2,6 @@ package de.idetools.filename_template.settings
import com.intellij.openapi.options.Configurable import com.intellij.openapi.options.Configurable
import com.intellij.openapi.options.ConfigurationException import com.intellij.openapi.options.ConfigurationException
import de.idetools.filename_template.InitializeFilenameTemplateActivity
import org.jetbrains.annotations.Nls import org.jetbrains.annotations.Nls
import javax.swing.JComponent import javax.swing.JComponent
@ -12,8 +11,16 @@ class FilenameTemplateConfigurable : Configurable {
init { init {
val settings: FilenameTemplateSettingsState = FilenameTemplateSettingsState.instance val settings: FilenameTemplateSettingsState = FilenameTemplateSettingsState.instance
settings.filenameTemplates.forEach {
if (it.title.isBlank()) {
it.title = if (it.description.isBlank()) it.name else "${it.name} - ${it.description}"
}
it.name = ""
it.description = ""
}
component = FilenameTemplateSettingsComponent(ArrayList(settings.filenameTemplates.map { component = FilenameTemplateSettingsComponent(ArrayList(settings.filenameTemplates.map {
FilenameTemplateModel(it.isActive, it.name, it.description, it.template) FilenameTemplateModel(it.isActive, it.title, it.name, it.description, it.template)
})) }))
} }
@ -32,9 +39,8 @@ class FilenameTemplateConfigurable : Configurable {
return settings.filenameTemplates.size != component.filenameTemplates.size || return settings.filenameTemplates.size != component.filenameTemplates.size ||
settings.filenameTemplates.withIndex().any { settings.filenameTemplates.withIndex().any {
component.filenameTemplateTable.getValueAt(it.index, 0) != it.value.isActive component.filenameTemplateTable.getValueAt(it.index, 0) != it.value.isActive
|| component.filenameTemplateTable.getValueAt(it.index, 1) != it.value.name || component.filenameTemplateTable.getValueAt(it.index, 1) != it.value.title
|| component.filenameTemplateTable.getValueAt(it.index, 2) != it.value.description || component.filenameTemplateTable.getValueAt(it.index, 2) != it.value.template
|| component.filenameTemplateTable.getValueAt(it.index, 3) != it.value.template
} }
} }
@ -45,12 +51,12 @@ class FilenameTemplateConfigurable : Configurable {
settings.filenameTemplates.addAll(component.filenameTemplates.map { settings.filenameTemplates.addAll(component.filenameTemplates.map {
FilenameTemplateModel( FilenameTemplateModel(
it.isActive, it.isActive,
it.title,
it.name, it.name,
it.description, it.description,
it.template it.template
) )
}) })
InitializeFilenameTemplateActivity().initializeActions()
} }
override fun reset() { override fun reset() {
@ -59,6 +65,7 @@ class FilenameTemplateConfigurable : Configurable {
component.filenameTemplates.addAll(settings.filenameTemplates.map { component.filenameTemplates.addAll(settings.filenameTemplates.map {
FilenameTemplateModel( FilenameTemplateModel(
it.isActive, it.isActive,
it.title,
it.name, it.name,
it.description, it.description,
it.template it.template

View File

@ -1,7 +1,8 @@
package de.idetools.filename_template.settings package de.idetools.filename_template.settings
data class FilenameTemplateModel( data class FilenameTemplateModel(
var isActive: Boolean = false, var isActive: Boolean = true,
var title: String = "",
var name: String = "", var name: String = "",
var description: String = "", var description: String = "",
var template: String = "" var template: String = ""

View File

@ -10,6 +10,7 @@ import com.intellij.ui.table.JBTable
import java.awt.BorderLayout import java.awt.BorderLayout
import java.awt.FlowLayout import java.awt.FlowLayout
import javax.swing.JPanel import javax.swing.JPanel
import javax.swing.table.TableColumn
class FilenameTemplateSettingsComponent(val filenameTemplates: ArrayList<FilenameTemplateModel>) { class FilenameTemplateSettingsComponent(val filenameTemplates: ArrayList<FilenameTemplateModel>) {
private var filenameTemplateTableModel: FilenameTemplateTableModel = FilenameTemplateTableModel(filenameTemplates) private var filenameTemplateTableModel: FilenameTemplateTableModel = FilenameTemplateTableModel(filenameTemplates)
@ -21,12 +22,29 @@ class FilenameTemplateSettingsComponent(val filenameTemplates: ArrayList<Filenam
filenameTemplateTable = JBTable(filenameTemplateTableModel) filenameTemplateTable = JBTable(filenameTemplateTableModel)
filenameTemplateTable.autoCreateRowSorter = false filenameTemplateTable.autoCreateRowSorter = false
val activeColumn = filenameTemplateTable.columnModel.getColumn(0)
activeColumn.resizable = false
activeColumn.maxWidth = determineRequiredWidth(activeColumn)
val templateColumn = filenameTemplateTable.columnModel.getColumn(2)
templateColumn.cellEditor = PlaceholderCompletionCellEditor()
val filenameTemplateTableScrollPane = JBScrollPane(filenameTemplateTable) val filenameTemplateTableScrollPane = JBScrollPane(filenameTemplateTable)
panel.add(filenameTemplateTableScrollPane, BorderLayout.CENTER) panel.add(filenameTemplateTableScrollPane, BorderLayout.CENTER)
panel.add(createToolbar(filenameTemplateTable), BorderLayout.EAST) panel.add(createToolbar(filenameTemplateTable), BorderLayout.EAST)
} }
private fun determineRequiredWidth(column: TableColumn): Int =
filenameTemplateTable.tableHeader.defaultRenderer.getTableCellRendererComponent(
filenameTemplateTable,
column.headerValue,
false,
false,
0,
0
).preferredSize.width + 8
private fun createToolbar(table: JBTable): JPanel { private fun createToolbar(table: JBTable): JPanel {
val actionGroup = DefaultActionGroup() val actionGroup = DefaultActionGroup()
actionGroup.add(object : AnAction("Add", "Add new filename template", AllIcons.General.Add) { actionGroup.add(object : AnAction("Add", "Add new filename template", AllIcons.General.Add) {

View File

@ -5,7 +5,7 @@ import javax.swing.table.AbstractTableModel
class FilenameTemplateTableModel(private val filenameTemplates: ArrayList<FilenameTemplateModel>) : class FilenameTemplateTableModel(private val filenameTemplates: ArrayList<FilenameTemplateModel>) :
AbstractTableModel() { AbstractTableModel() {
private val header: List<String> = listOf("Active", "Name", "Description", "Template") private val header: List<String> = listOf("Active", "Title", "Template")
override fun getRowCount(): Int = filenameTemplates.size override fun getRowCount(): Int = filenameTemplates.size
@ -14,9 +14,8 @@ class FilenameTemplateTableModel(private val filenameTemplates: ArrayList<Filena
override fun getValueAt(row: Int, column: Int): Any { override fun getValueAt(row: Int, column: Int): Any {
return when (column) { return when (column) {
0 -> filenameTemplates[row].isActive 0 -> filenameTemplates[row].isActive
1 -> filenameTemplates[row].name 1 -> filenameTemplates[row].title
2 -> filenameTemplates[row].description 2 -> filenameTemplates[row].template
3 -> filenameTemplates[row].template
else -> throw ArrayIndexOutOfBoundsException() else -> throw ArrayIndexOutOfBoundsException()
} }
} }
@ -24,9 +23,8 @@ class FilenameTemplateTableModel(private val filenameTemplates: ArrayList<Filena
override fun setValueAt(value: Any?, row: Int, column: Int) { override fun setValueAt(value: Any?, row: Int, column: Int) {
when (column) { when (column) {
0 -> filenameTemplates[row].isActive = value as Boolean 0 -> filenameTemplates[row].isActive = value as Boolean
1 -> filenameTemplates[row].name = value as String 1 -> filenameTemplates[row].title = value as String
2 -> filenameTemplates[row].description = value as String 2 -> filenameTemplates[row].template = value as String
3 -> filenameTemplates[row].template = value as String
else -> throw ArrayIndexOutOfBoundsException() else -> throw ArrayIndexOutOfBoundsException()
} }
} }

View File

@ -0,0 +1,68 @@
package de.idetools.filename_template.settings
import com.intellij.codeInsight.completion.CompletionResultSet
import com.intellij.codeInsight.completion.InsertHandler
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.openapi.project.ProjectManager
import com.intellij.util.TextFieldCompletionProviderDumbAware
import com.intellij.util.textCompletion.TextFieldWithCompletion
import java.awt.Component
import javax.swing.AbstractCellEditor
import javax.swing.JTable
import javax.swing.table.TableCellEditor
internal class PlaceholderCompletionCellEditor : AbstractCellEditor(), TableCellEditor {
private var component = TextFieldWithCompletion(
ProjectManager.getInstance().defaultProject,
PlaceholderCompletionProvider(),
"",
true,
true,
true
)
override fun getCellEditorValue(): Any {
return component.text
}
override fun getTableCellEditorComponent(
table: JTable,
value: Any,
isSelected: Boolean,
rowIndex: Int,
vColIndex: Int
): Component {
component.text = value as String
return component
}
class PlaceholderCompletionProvider : TextFieldCompletionProviderDumbAware(true) {
object PlaceholderInsertHandler : InsertHandler<LookupElement> {
override fun handleInsert(context: InsertionContext, lookupElement: LookupElement) {
val text = context.document.text
if (context.tailOffset < text.length && text[context.tailOffset] == '}') {
context.document.replaceString(context.tailOffset - 1, context.tailOffset + 1, "}")
}
if (0 < context.startOffset && text[context.startOffset - 1] == '{') {
context.document.replaceString(context.startOffset - 1, context.startOffset + 1, "{")
}
}
}
override fun getPrefix(currentTextPrefix: String): String {
if (-1 < currentTextPrefix.indexOf('{')) {
return currentTextPrefix.removeRange(0, currentTextPrefix.lastIndexOf('{'))
}
return currentTextPrefix
}
override fun addCompletionVariants(text: String, offset: Int, prefix: String, result: CompletionResultSet) {
listOf("|", "0", "DATE", "EPOCH", "HOST", "PROJECT", "USER").forEach {
result.addElement(LookupElementBuilder.create("{${it}}").withInsertHandler(PlaceholderInsertHandler))
}
}
}
}

View File

@ -13,7 +13,8 @@
<ol> <ol>
<li>Settings -> Tools -> Filename Template</li> <li>Settings -> Tools -> Filename Template</li>
<li>Configure filename templates</li> <li>Configure filename templates</li>
<li>Create new file: New -> <i>Template name</i></li> <li>Create new file: New -> Filename Template</li>
<li>Choose filename template</li>
<li>Complete the file name with return</li> <li>Complete the file name with return</li>
</ol> </ol>
@ -21,13 +22,10 @@
<dl> <dl>
<dt>Active</dt> <dt>Active</dt>
<dd>Only active filename templates will be shown in the menu.</dd> <dd>Only active filename templates are available in the template selection list.</dd>
<dt>Name</dt> <dt>Title</dt>
<dd>The name will be shown in the menu.</dd> <dd>The title is displayed in the template selection list below the file name input field.</dd>
<dt>Description</dt>
<dd>The description will be shown in the status bar when hovering the menu entry.</dd>
<dt>Template</dt> <dt>Template</dt>
<dd> <dd>
@ -92,11 +90,13 @@
instance="de.idetools.filename_template.settings.FilenameTemplateConfigurable" instance="de.idetools.filename_template.settings.FilenameTemplateConfigurable"
id="de.idetools.filename_template.settings.FilenameTemplateConfigurable" id="de.idetools.filename_template.settings.FilenameTemplateConfigurable"
displayName="Filename Template"/> displayName="Filename Template"/>
<backgroundPostStartupActivity
implementation="de.idetools.filename_template.InitializeFilenameTemplateActivity"/>
</extensions> </extensions>
<actions> <actions>
<!-- Add your actions here --> <action id="de.idetools.filename_template.CreateFileWithFilenameTemplateAction"
class="de.idetools.filename_template.CreateFileWithFilenameTemplateAction" text="NewFilenameTemplate"
description="Create a new file based on a filename template" icon="/META-INF/pluginIcon-small.png">
<add-to-group group-id="NewGroup" anchor="after" relative-to-action="NewFile"/>
</action>
</actions> </actions>
</idea-plugin> </idea-plugin>

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B