[javafx] IDEA-368755 Inconsistent File-New template dialog

GitOrigin-RevId: 5df1af730d75573159d8cf0eaf8eccd0790df138
This commit is contained in:
Yuriy Artamonov
2025-03-07 01:13:03 +00:00
committed by intellij-monorepo-bot
parent 4f1df9bae6
commit b30ce40d2b
9 changed files with 225 additions and 112 deletions
@@ -529,6 +529,13 @@ f:com.intellij.ide.actions.searcheverywhere.SearchEverywhereUI
- *:selectFirst():V
com.intellij.ide.actions.searcheverywhere.SearchListener
- *:standardSearchFoundNoResults(com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor):V
*a:com.intellij.ide.fileTemplates.actions.CustomCreateFromTemplateAction
- com.intellij.ide.actions.CreateFileFromTemplateAction
- p:<init>(java.lang.String):V
- p:buildDialog(com.intellij.openapi.project.Project,com.intellij.psi.PsiDirectory,com.intellij.ide.actions.CreateFileFromTemplateDialog$Builder):V
- p:getActionName(com.intellij.psi.PsiDirectory,java.lang.String,java.lang.String):java.lang.String
- p:getLiveTemplateDefaults(com.intellij.openapi.actionSystem.DataContext,com.intellij.psi.PsiFile):java.util.Map
- p:postProcess(com.intellij.psi.PsiFile,com.intellij.openapi.actionSystem.DataContext,java.lang.String,java.util.Map):V
f:com.intellij.ide.impl.StructureViewWrapperImpl
- com.intellij.ide.structureView.StructureViewWrapper
- com.intellij.openapi.Disposable
@@ -8210,6 +8210,7 @@ a:com.intellij.ide.actions.CreateFromTemplateAction
- p:getErrorTitle():java.lang.String
- p:isAvailable(com.intellij.openapi.actionSystem.DataContext):Z
- s:moveCaretAfterNameIdentifier(com.intellij.psi.PsiNameIdentifierOwner):V
- p:postProcess(com.intellij.psi.PsiElement,com.intellij.openapi.actionSystem.DataContext,java.lang.String,java.util.Map):V
- p:postProcess(com.intellij.psi.PsiElement,java.lang.String,java.util.Map):V
- update(com.intellij.openapi.actionSystem.AnActionEvent):V
a:com.intellij.ide.actions.CreateTemplateInPackageAction
@@ -9381,6 +9382,10 @@ a:com.intellij.ide.fileTemplates.actions.CreateFromTemplateActionBase
- pa:getTemplate(com.intellij.openapi.project.Project,com.intellij.psi.PsiDirectory):com.intellij.ide.fileTemplates.FileTemplate
- s:startLiveTemplate(com.intellij.psi.PsiFile):V
- s:startLiveTemplate(com.intellij.psi.PsiFile,java.util.Map):V
f:com.intellij.ide.fileTemplates.actions.CreateFromTemplateManager
- <init>():V
- s:startLiveTemplate(com.intellij.psi.PsiFile):V
- s:startLiveTemplate(com.intellij.psi.PsiFile,java.util.Map):V
f:com.intellij.ide.fileTemplates.actions.FileTemplateSeparator
- com.intellij.openapi.actionSystem.ActionGroup
- <init>():V
@@ -108,7 +108,7 @@ public abstract class CreateFromTemplateAction<T extends PsiElement> extends AnA
editor.getCaretModel().moveToOffset(offset);
}
try (var ignored = SlowOperations.startSection(SlowOperations.ACTION_PERFORM)) {
postProcess(createdElement, selectedTemplateName.get(), builder.getCustomProperties());
postProcess(createdElement, dataContext, selectedTemplateName.get(), builder.getCustomProperties());
}
}
});
@@ -133,6 +133,11 @@ public abstract class CreateFromTemplateAction<T extends PsiElement> extends AnA
return CreateFileFromTemplateDialog.createDialog(project);
}
protected void postProcess(@NotNull T createdElement, @NotNull DataContext dataContext,
String templateName, Map<String, String> customProperties) {
postProcess(createdElement, templateName, customProperties);
}
protected void postProcess(@NotNull T createdElement, String templateName, Map<String, String> customProperties) {
}
@@ -1,20 +1,15 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.fileTemplates.actions;
import com.intellij.codeInsight.template.TemplateManager;
import com.intellij.codeInsight.template.impl.TemplateImpl;
import com.intellij.ide.IdeView;
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.ide.fileTemplates.ui.CreateFromTemplateDialog;
import com.intellij.ide.util.DirectoryChooserUtil;
import com.intellij.ide.util.EditorHelper;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NlsActions;
import com.intellij.psi.PsiDirectory;
@@ -24,11 +19,16 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;
import java.util.function.Supplier;
import static com.intellij.util.ObjectUtils.notNull;
/**
* Consider using {@link CustomCreateFromTemplateAction} instead for subclasses.
*/
public abstract class CreateFromTemplateActionBase extends AnAction {
public CreateFromTemplateActionBase() {
super();
@@ -77,35 +77,20 @@ public abstract class CreateFromTemplateActionBase extends AnAction {
}
}
/**
* @deprecated Use {@link CreateFromTemplateManager#startLiveTemplate(PsiFile)} instead
*/
@Deprecated
public static void startLiveTemplate(@NotNull PsiFile file) {
startLiveTemplate(file, Collections.emptyMap());
CreateFromTemplateManager.startLiveTemplate(file, Collections.emptyMap());
}
/**
* @deprecated Use {@link CreateFromTemplateManager#startLiveTemplate(PsiFile, Map)} instead
*/
@Deprecated
public static void startLiveTemplate(@NotNull PsiFile file, @NotNull Map<String, String> defaultValues) {
Editor editor = EditorHelper.openInEditor(file);
if (editor == null) return;
TemplateImpl template = new TemplateImpl("", file.getText(), "");
template.setInline(true);
int count = template.getSegmentsCount();
if (count == 0) return;
// Using LinkedHashSet for a saving variables orders
Set<String> variables = new LinkedHashSet<>();
for (int i = 0; i < count; i++) {
variables.add(template.getSegmentName(i));
}
variables.removeAll(TemplateImpl.INTERNAL_VARS_SET);
for (String variable : variables) {
String defaultValue = defaultValues.getOrDefault(variable, variable);
template.addVariable(variable, null, '"' + defaultValue + '"', true);
}
Project project = file.getProject();
WriteCommandAction.runWriteCommandAction(project, () -> editor.getDocument().setText(template.getTemplateText()));
editor.getCaretModel().moveToOffset(0); // ensures caret at the start of the template
TemplateManager.getInstance(project).startTemplate(editor, template);
CreateFromTemplateManager.startLiveTemplate(file, defaultValues);
}
protected @Nullable PsiDirectory getTargetDirectory(DataContext dataContext, IdeView view) {
@@ -0,0 +1,49 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.fileTemplates.actions;
import com.intellij.codeInsight.template.TemplateManager;
import com.intellij.codeInsight.template.impl.TemplateImpl;
import com.intellij.ide.util.EditorHelper;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
public final class CreateFromTemplateManager {
public static void startLiveTemplate(@NotNull PsiFile file) {
startLiveTemplate(file, Collections.emptyMap());
}
public static void startLiveTemplate(@NotNull PsiFile file, @NotNull Map<String, String> defaultValues) {
Editor editor = EditorHelper.openInEditor(file);
if (editor == null) return;
TemplateImpl template = new TemplateImpl("", file.getText(), "");
template.setInline(true);
int count = template.getSegmentsCount();
if (count == 0) return;
// Using LinkedHashSet for a saving variables orders
Set<String> variables = new LinkedHashSet<>();
for (int i = 0; i < count; i++) {
variables.add(template.getSegmentName(i));
}
variables.removeAll(TemplateImpl.INTERNAL_VARS_SET);
for (String variable : variables) {
String defaultValue = defaultValues.getOrDefault(variable, variable);
template.addVariable(variable, null, '"' + defaultValue + '"', true);
}
Project project = file.getProject();
WriteCommandAction.runWriteCommandAction(project, () -> editor.getDocument().setText(template.getTemplateText()));
editor.getCaretModel().moveToOffset(0); // ensures caret at the start of the template
TemplateManager.getInstance(project).startTemplate(editor, template);
}
}
@@ -0,0 +1,69 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.fileTemplates.actions;
import com.intellij.ide.actions.CreateFileFromTemplateAction;
import com.intellij.ide.actions.CreateFileFromTemplateDialog;
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.ide.fileTemplates.FileTemplateUtil;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.Map;
import static com.intellij.ide.fileTemplates.actions.CreateFromTemplateManager.startLiveTemplate;
import static com.intellij.util.ObjectUtils.notNull;
@ApiStatus.Experimental
public abstract class CustomCreateFromTemplateAction extends CreateFileFromTemplateAction {
@NotNull private final String myTemplateName;
protected CustomCreateFromTemplateAction(@NotNull String name) {
myTemplateName = name;
}
@SuppressWarnings("HardCodedStringLiteral")
@Override
protected void buildDialog(@NotNull Project project,
@NotNull PsiDirectory directory,
@NotNull CreateFileFromTemplateDialog.Builder builder) {
FileTemplateManager fileTemplateManager = FileTemplateManager.getInstance(project);
FileTemplate template = fileTemplateManager.getInternalTemplate(myTemplateName);
builder
.setTitle(myTemplateName)
.addKind(myTemplateName, FileTemplateUtil.getIcon(template), template.getName());
}
@SuppressWarnings("HardCodedStringLiteral")
@Override
protected String getActionName(PsiDirectory directory, @NotNull String newName, String templateName) {
return templateName;
}
@Override
protected void postProcess(@NotNull PsiFile createdElement,
@NotNull DataContext dataContext,
String templateName,
Map<String, String> customProperties) {
super.postProcess(createdElement, dataContext, templateName, customProperties);
var selectedTemplate = FileTemplateManager.getInstance(createdElement.getProject())
.getInternalTemplate(templateName);
if (selectedTemplate.isLiveTemplateEnabled()) {
Map<String, String> defaultValues = getLiveTemplateDefaults(dataContext, createdElement);
startLiveTemplate(createdElement, notNull(defaultValues, Collections.emptyMap()));
}
}
protected @Nullable Map<String, String> getLiveTemplateDefaults(@NotNull DataContext dataContext, @NotNull PsiFile file) {
return null;
}
}
@@ -1,55 +1,38 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.javaFX.actions;
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.ide.fileTemplates.actions.CreateFromTemplateActionBase;
import com.intellij.openapi.actionSystem.*;
import com.intellij.ide.fileTemplates.actions.CustomCreateFromTemplateAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.roots.PackageIndex;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.CachedValueProvider.Result;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.util.PathUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
import org.jetbrains.plugins.javaFX.fxml.JavaFxFileTypeFactory;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import static com.intellij.psi.search.GlobalSearchScope.moduleWithDependenciesAndLibrariesScope;
import static org.jetbrains.plugins.javaFX.fxml.JavaFxCommonNames.JAVAFX_APPLICATION_APPLICATION;
import static org.jetbrains.plugins.javaFX.actions.JavaFxTemplateManager.isJavaFxTemplateAvailable;
public final class CreateFxmlFileAction extends CreateFromTemplateActionBase {
final class CreateFxmlFileAction extends CustomCreateFromTemplateAction implements DumbAware {
private static final String INTERNAL_TEMPLATE_NAME = "FxmlFile.fxml";
@Override
public @NotNull ActionUpdateThread getActionUpdateThread() {
return ActionUpdateThread.BGT;
CreateFxmlFileAction() {
super(INTERNAL_TEMPLATE_NAME);
}
@Override
protected FileTemplate getTemplate(Project project, PsiDirectory dir) {
return FileTemplateManager.getInstance(project).getInternalTemplate(INTERNAL_TEMPLATE_NAME);
}
@Override
protected Map<String, String> getLiveTemplateDefaults(DataContext dataContext, @NotNull PsiFile file) {
protected Map<String, String> getLiveTemplateDefaults(@NotNull DataContext dataContext, @NotNull PsiFile file) {
String packageName = ReadAction.compute(() -> {
PsiDirectory psiDirectory = file.getContainingDirectory();
if (psiDirectory != null) {
@@ -101,42 +84,7 @@ public final class CreateFxmlFileAction extends CreateFromTemplateActionBase {
}
@Override
public void update(final @NotNull AnActionEvent e) {
final DataContext dataContext = e.getDataContext();
final Presentation presentation = e.getPresentation();
presentation.setEnabledAndVisible(isJavaFxTemplateAvailable(dataContext, JavaModuleSourceRootTypes.PRODUCTION));
}
static boolean isJavaFxTemplateAvailable(DataContext dataContext, Set<? extends JpsModuleSourceRootType<?>> requiredRootTypes) {
var project = CommonDataKeys.PROJECT.getData(dataContext);
var view = LangDataKeys.IDE_VIEW.getData(dataContext);
if (project == null || view == null) {
return false;
}
var directories = view.getDirectories();
if (directories.length == 0) return false;
var index = ProjectRootManager.getInstance(project).getFileIndex();
var underRoot = Arrays.stream(directories)
.map(PsiDirectory::getVirtualFile)
.anyMatch(virtualFile -> index.isUnderSourceRootOfType(virtualFile, requiredRootTypes));
if (!underRoot) return false;
// root types check is less expensive on start toolbar update than checking module dependencies
var module = ModuleUtilCore.findModuleForFile(directories[0].getVirtualFile(), project);
return hasJavaFxDependency(module);
}
private static boolean hasJavaFxDependency(@Nullable Module module) {
if (module == null || module.isDisposed()) return false;
return CachedValuesManager.getManager(module.getProject()).getCachedValue(module, () -> {
boolean hasClass = JavaPsiFacade.getInstance(module.getProject())
.findClass(JAVAFX_APPLICATION_APPLICATION, moduleWithDependenciesAndLibrariesScope(module)) != null;
return Result.create(hasClass, ProjectRootManager.getInstance(module.getProject()));
});
public void update(@NotNull AnActionEvent e) {
e.getPresentation().setEnabledAndVisible(isJavaFxTemplateAvailable(e.getDataContext(), JavaModuleSourceRootTypes.PRODUCTION));
}
}
@@ -1,32 +1,21 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.javaFX.actions;
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.ide.fileTemplates.actions.CreateFromTemplateActionBase;
import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.ide.fileTemplates.actions.CustomCreateFromTemplateAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDirectory;
import com.intellij.openapi.project.DumbAware;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.model.java.JavaSourceRootType;
import java.util.Set;
import static org.jetbrains.plugins.javaFX.actions.CreateFxmlFileAction.isJavaFxTemplateAvailable;
final class CreateJavaFxApplicationAction extends CreateFromTemplateActionBase {
import static org.jetbrains.plugins.javaFX.actions.JavaFxTemplateManager.isJavaFxTemplateAvailable;
final class CreateJavaFxApplicationAction extends CustomCreateFromTemplateAction implements DumbAware {
private static final String FILE_TEMPLATE_NAME = "JavaFXApplication.java";
@Override
public @NotNull ActionUpdateThread getActionUpdateThread() {
return ActionUpdateThread.BGT;
}
@Override
protected FileTemplate getTemplate(Project project, PsiDirectory dir) {
return FileTemplateManager.getInstance(project).getTemplate(FILE_TEMPLATE_NAME);
CreateJavaFxApplicationAction() {
super(FILE_TEMPLATE_NAME);
}
@Override
@@ -0,0 +1,56 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.javaFX.actions;
import com.intellij.java.library.JavaLibraryModificationTracker;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.util.CachedValueProvider.Result;
import com.intellij.psi.util.CachedValuesManager;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
import java.util.Arrays;
import java.util.Set;
import static com.intellij.psi.search.GlobalSearchScope.moduleWithDependenciesAndLibrariesScope;
import static org.jetbrains.plugins.javaFX.fxml.JavaFxCommonNames.JAVAFX_APPLICATION_APPLICATION;
final class JavaFxTemplateManager {
static boolean isJavaFxTemplateAvailable(DataContext dataContext, Set<? extends JpsModuleSourceRootType<?>> requiredRootTypes) {
var project = CommonDataKeys.PROJECT.getData(dataContext);
var view = LangDataKeys.IDE_VIEW.getData(dataContext);
if (project == null || view == null) {
return false;
}
var directories = view.getDirectories();
if (directories.length == 0) return false;
var index = ProjectRootManager.getInstance(project).getFileIndex();
var underRoot = Arrays.stream(directories)
.map(PsiDirectory::getVirtualFile)
.anyMatch(virtualFile -> index.isUnderSourceRootOfType(virtualFile, requiredRootTypes));
if (!underRoot) return false;
// root types check is less expensive on start toolbar update than checking module dependencies
var module = ModuleUtilCore.findModuleForFile(directories[0].getVirtualFile(), project);
return hasJavaFxDependency(module);
}
private static boolean hasJavaFxDependency(@Nullable Module module) {
if (module == null || module.isDisposed()) return false;
return CachedValuesManager.getManager(module.getProject()).getCachedValue(module, () -> {
boolean hasClass = JavaPsiFacade.getInstance(module.getProject())
.findClass(JAVAFX_APPLICATION_APPLICATION, moduleWithDependenciesAndLibrariesScope(module)) != null;
return Result.create(hasClass, JavaLibraryModificationTracker.getInstance(module.getProject()));
});
}
}