diff --git a/build.gradle.kts b/build.gradle.kts index 132b815..4981fec 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } group = "at.alirezamoh.whisperer-for-laravel" -version = "1.1.6" +version = "1.2.0" repositories { mavenCentral() @@ -33,7 +33,7 @@ tasks { } patchPluginXml { - sinceBuild.set("232") + sinceBuild.set("241") untilBuild.set("243.*") } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/GenerateHelperMethodsAction.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/GenerateHelperMethodsAction.java index 970fb8d..6d6575d 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/GenerateHelperMethodsAction.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/GenerateHelperMethodsAction.java @@ -10,7 +10,6 @@ import at.alirezamoh.whisperer_for_laravel.support.utils.DirectoryUtils; import at.alirezamoh.whisperer_for_laravel.support.notification.Notify; import at.alirezamoh.whisperer_for_laravel.support.utils.PluginUtils; -import at.alirezamoh.whisperer_for_laravel.support.utils.StrUtils; import at.alirezamoh.whisperer_for_laravel.support.TemplateLoader; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.application.ApplicationManager; @@ -61,7 +60,7 @@ public class GenerateHelperMethodsAction extends BaseAction { public void actionPerformed(@NotNull AnActionEvent anActionEvent) { project = anActionEvent.getProject(); - if (PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { Notify.notifyWarning(project, "Laravel Framework is not installed"); return; } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/InertiaPageAction.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/InertiaPageAction.java new file mode 100644 index 0000000..35d585c --- /dev/null +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/InertiaPageAction.java @@ -0,0 +1,23 @@ +package at.alirezamoh.whisperer_for_laravel.actions; + +import at.alirezamoh.whisperer_for_laravel.actions.views.InertiaPageView; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; + +public class InertiaPageAction extends BaseAction { + @Override + public void actionPerformed(@NotNull AnActionEvent anActionEvent) { + Project project = anActionEvent.getProject(); + InertiaPageView inertiaPageView = new InertiaPageView(project); + + if (inertiaPageView.showAndGet()) { + this.create( + inertiaPageView.getInertiaPageModel(), + "inertiaPage.ftl", + true, + project + ); + } + } +} diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/StartPluginAction.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/StartPluginAction.java index 769bb8f..35396b6 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/StartPluginAction.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/StartPluginAction.java @@ -16,15 +16,13 @@ public class StartPluginAction extends DefaultActionGroup { @Override public void update(@NotNull AnActionEvent e) { Project project = e.getProject(); - if (project != null && !PluginUtils.isDumbMode(project)) { - if (!PluginUtils.isLaravelProject(project)) { - e.getPresentation().setEnabledAndVisible(false); - } - } - if (project == null) { - e.getPresentation().setEnabledAndVisible(false); - } + boolean isVisible = + project != null + && !PluginUtils.isDumbMode(project) + && PluginUtils.isLaravelProject(project); + + e.getPresentation().setEnabledAndVisible(isVisible); } @Override diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/models/InertiaPageModel.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/models/InertiaPageModel.java new file mode 100644 index 0000000..1261ce2 --- /dev/null +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/models/InertiaPageModel.java @@ -0,0 +1,62 @@ +package at.alirezamoh.whisperer_for_laravel.actions.models; + +import at.alirezamoh.whisperer_for_laravel.settings.SettingsState; + +/** + * Model representing an inertia page + */ +public class InertiaPageModel extends BaseModel { + private String pageName; + + private boolean withOptionsApi; + + private boolean vue; + + /** + * @param name The name of the inertia page + * @param unformattedModuleFullPath The unformatted module full path + * @param formattedModuleFullPath The formatted module full path + */ + public InertiaPageModel( + SettingsState settingsState, + String name, + String unformattedModuleFullPath, + String formattedModuleFullPath, + String defaultDestination, + boolean withOptionsApi, + String pageType + ) + { + super( + settingsState, + name, + unformattedModuleFullPath, + formattedModuleFullPath, + defaultDestination, + "", + pageType, + "" + ); + + this.pageName = getName(); + this.withOptionsApi = withOptionsApi; + this.vue = pageType.equals(".vue"); + } + + @Override + public void setWithoutModuleSrc() { + this.withoutModuleSrcPath = true; + } + + public String getPageName() { + return pageName; + } + + public boolean isWithOptionsApi() { + return withOptionsApi; + } + + public boolean isVue() { + return vue; + } +} diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/models/codeGenerationHelperModels/LaravelDbBuilder.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/models/codeGenerationHelperModels/LaravelDbBuilder.java index df4ea85..5c642da 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/models/codeGenerationHelperModels/LaravelDbBuilder.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/models/codeGenerationHelperModels/LaravelDbBuilder.java @@ -16,13 +16,13 @@ public LaravelDbBuilder(List methods, SettingsState settingsState) { this.slug = ""; this.name = "whisperer_for_laravel_base_db_query_builder"; this.extension = ".php"; - this.destination = "/" + ProjectDefaultPaths.WHISPERER_FOR_LARAVEL_DIR_PATH; + this.destination = settingsState.getProjectDirectoryPath() + ProjectDefaultPaths.WHISPERER_FOR_LARAVEL_DIR_PATH; if (settingsState.isProjectDirectoryEmpty()) { this.filePath = ProjectDefaultPaths.WHISPERER_FOR_LARAVEL_DIR_PATH + this.name + ".php"; } else { - this.filePath = ProjectDefaultPaths.WHISPERER_FOR_LARAVEL_DIR_PATH + settingsState.getProjectDirectoryPath() + "/" + this.name + ".php"; + this.filePath = settingsState.getProjectDirectoryPath() + ProjectDefaultPaths.WHISPERER_FOR_LARAVEL_DIR_PATH + this.name + ".php"; } } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/models/codeGenerationHelperModels/LaravelModelGeneration.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/models/codeGenerationHelperModels/LaravelModelGeneration.java index ac895c1..31cea4e 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/models/codeGenerationHelperModels/LaravelModelGeneration.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/models/codeGenerationHelperModels/LaravelModelGeneration.java @@ -18,13 +18,13 @@ public LaravelModelGeneration(String namespace, List laravelModels this.slug = ""; this.name = "whisperer_for_laravel_models_" + shortUuid; this.extension = ".php"; - this.destination = "/" + ProjectDefaultPaths.WHISPERER_FOR_LARAVEL_DIR_PATH; + this.destination = settingsState.getProjectDirectoryPath() + ProjectDefaultPaths.WHISPERER_FOR_LARAVEL_DIR_PATH; if (settingsState.isProjectDirectoryEmpty()) { this.filePath = ProjectDefaultPaths.WHISPERER_FOR_LARAVEL_DIR_PATH + this.name + ".php"; } else { - this.filePath = ProjectDefaultPaths.WHISPERER_FOR_LARAVEL_DIR_PATH + settingsState.getProjectDirectoryPath() + "/" + this.name + ".php"; + this.filePath = settingsState.getProjectDirectoryPath() + ProjectDefaultPaths.WHISPERER_FOR_LARAVEL_DIR_PATH + this.name + ".php"; } } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/views/InertiaPageView.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/views/InertiaPageView.java new file mode 100644 index 0000000..bc36e4e --- /dev/null +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/actions/views/InertiaPageView.java @@ -0,0 +1,141 @@ +package at.alirezamoh.whisperer_for_laravel.actions.views; + +import at.alirezamoh.whisperer_for_laravel.actions.models.InertiaPageModel; +import at.alirezamoh.whisperer_for_laravel.packages.inertia.InertiaPageCollector; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.ui.ValidationInfo; +import com.intellij.util.ui.JBUI; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.util.Objects; + +public class InertiaPageView extends BaseDialog { + /** + * Text field for entering the inertia page name + */ + private JTextField inertiaPageNameTextField; + + /** + * Select the destination path + */ + protected ComboBox resourcePageDirectoryComboBox; + + /** + * Select the page variant + */ + protected ComboBox pageVariantComboBox; + + /** + * Select the page type + */ + protected ComboBox pageTypeComboBox; + + /** + * @param project The current project + */ + public InertiaPageView(Project project) { + super(project); + + setTitle("Create Inertia Page"); + setSize(500, 200); + setResizable(false); + init(); + } + + /** + * Returns the Inertia page model representing an inertia page + * @return The inertia page model + */ + public InertiaPageModel getInertiaPageModel() { + return new InertiaPageModel( + projectSettingState, + inertiaPageNameTextField.getText(), + "", + "", + resourcePageDirectoryComboBox.getItem(), + Objects.equals(pageVariantComboBox.getItem(), "Options API"), + pageTypeComboBox.getItem() + ); + } + + /** + * Returns the focused component when the dialog opens + * @return The preferred focused component + */ + @Override + public @Nullable JComponent getPreferredFocusedComponent() { + return inertiaPageNameTextField; + } + + /** + * Validates the dialog input + * @return Validation info if there are errors, null otherwise + */ + @Override + protected @Nullable ValidationInfo doValidate() { + String text = inertiaPageNameTextField.getText().trim(); + + if (text.isEmpty()) { + return new ValidationInfo("", inertiaPageNameTextField); + } + return null; + } + + /** + * Creates the center panel of the dialog + * @return The center panel + */ + @Override + protected JComponent createCenterPanel() { + contentPane = new JPanel(); + contentPane.setLayout(new GridBagLayout()); + + inertiaPageNameTextField = new JTextField(); + resourcePageDirectoryComboBox = new ComboBox<>(InertiaPageCollector.getInertiaPaths(project).toArray(new String[0])); + pageVariantComboBox = new ComboBox<>(new String[]{"Options API", "Composition API"}); + pageTypeComboBox = new ComboBox<>(new String[]{".vue", ".jsx"}); + + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + + gbc.gridx = 0; + gbc.gridy = 0; + + gbc.insets = JBUI.insetsLeft(3); + contentPane.add(new JLabel("Enter page name:"), gbc); + gbc.insets = JBUI.insetsLeft(0); + gbc.insets = JBUI.insetsBottom(15); + gbc.gridy++; + contentPane.add(inertiaPageNameTextField, gbc); + inertiaPageNameTextField.requestFocusInWindow(); + + gbc.gridy++; + gbc.insets = JBUI.insetsLeft(3); + contentPane.add(new JLabel("Page type:"), gbc); + gbc.insets = JBUI.insetsLeft(0); + gbc.insets = JBUI.insetsBottom(15); + gbc.gridy++; + contentPane.add(pageTypeComboBox, gbc); + + gbc.gridy++; + gbc.insets = JBUI.insetsLeft(3); + contentPane.add(new JLabel("Page variant: (Ignore if it's not a Vue file)"), gbc); + gbc.insets = JBUI.insetsLeft(0); + gbc.insets = JBUI.insetsBottom(15); + gbc.gridy++; + contentPane.add(pageVariantComboBox, gbc); + + gbc.gridy++; + this.gbc.insets = JBUI.insetsLeft(3); + this.contentPane.add(new JLabel("Page resource directory:"), this.gbc); + this.gbc.gridy++; + this.gbc.insets = JBUI.insetsLeft(0); + this.gbc.insets = JBUI.insetsBottom(15); + this.contentPane.add(resourcePageDirectoryComboBox, this.gbc); + + return contentPane; + } +} diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/blade/component/BladeXComponentCompletionContributor.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/blade/component/BladeXComponentCompletionContributor.java index 283296d..55a8a3a 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/blade/component/BladeXComponentCompletionContributor.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/blade/component/BladeXComponentCompletionContributor.java @@ -43,7 +43,7 @@ protected void addCompletions(@NotNull CompletionParameters completionParameters PsiElement element = completionParameters.getPosition(); Project project = element.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return; } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/blade/component/BladeXComponentGotoDeclarationHandler.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/blade/component/BladeXComponentGotoDeclarationHandler.java index f30df10..b299edc 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/blade/component/BladeXComponentGotoDeclarationHandler.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/blade/component/BladeXComponentGotoDeclarationHandler.java @@ -35,7 +35,7 @@ public class BladeXComponentGotoDeclarationHandler implements GotoDeclarationHan Project project = sourceElement.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return null; } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/blade/viewName/BladeReferenceContributor.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/blade/viewName/BladeReferenceContributor.java index 47e0990..1f72c3b 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/blade/viewName/BladeReferenceContributor.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/blade/viewName/BladeReferenceContributor.java @@ -70,7 +70,7 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferen public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) { Project project = psiElement.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return PsiReference.EMPTY_ARRAY; } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/config/ConfigReferenceContributor.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/config/ConfigReferenceContributor.java index d1dee4f..bc65a01 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/config/ConfigReferenceContributor.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/config/ConfigReferenceContributor.java @@ -56,7 +56,7 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferen public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) { Project project = psiElement.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return PsiReference.EMPTY_ARRAY; } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/config/util/ConfigKeyCollector.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/config/util/ConfigKeyCollector.java index 41973fe..07b2347 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/config/util/ConfigKeyCollector.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/config/util/ConfigKeyCollector.java @@ -7,6 +7,7 @@ import com.intellij.openapi.project.Project; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.util.indexing.FileBasedIndex; +import com.intellij.util.indexing.IdFilter; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -39,10 +40,14 @@ private void getKeysFromConfigIndex(@NotNull Project project, FileBasedIndex fil return true; }, - GlobalSearchScope.projectScope(project) + GlobalSearchScope.projectScope(project), + IdFilter.getProjectIdFilter(project, false) ); return true; - }, project); + }, + GlobalSearchScope.projectScope(project), + IdFilter.getProjectIdFilter(project, false) + ); } private void getVariantsFromServiceProviderIndex(@NotNull Project project, FileBasedIndex fileBasedIndex, List variants) { @@ -70,11 +75,15 @@ private void getVariantsFromServiceProviderIndex(@NotNull Project project, FileB return true; }, - GlobalSearchScope.projectScope(project) + GlobalSearchScope.projectScope(project), + IdFilter.getProjectIdFilter(project, true) ); return true; - }, project); + }, + GlobalSearchScope.projectScope(project), + IdFilter.getProjectIdFilter(project, true) + ); } private @NotNull String buildKeyValue(String value) { diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/config/util/ConfigKeyResolver.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/config/util/ConfigKeyResolver.java index fcebfd6..fa1c79c 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/config/util/ConfigKeyResolver.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/config/util/ConfigKeyResolver.java @@ -12,6 +12,7 @@ import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.indexing.FileBasedIndex; +import com.intellij.util.indexing.IdFilter; import com.jetbrains.php.lang.psi.elements.ArrayCreationExpression; import com.jetbrains.php.lang.psi.elements.ArrayHashElement; import com.jetbrains.php.lang.psi.elements.PhpPsiElement; @@ -50,11 +51,14 @@ public class ConfigKeyResolver { key, null, (file, serviceProvider) -> checkServiceProviderForConfigKey(text, project, serviceProvider, foundElement), - GlobalSearchScope.projectScope(project) + GlobalSearchScope.allScope(project) ); return foundElement.get() == null; - }, project); + }, + GlobalSearchScope.projectScope(project), + IdFilter.getProjectIdFilter(project, true) + ); return foundElement.get(); } @@ -112,7 +116,7 @@ public class ConfigKeyResolver { Collection configFiles = FileBasedIndex.getInstance().getContainingFiles( ConfigIndex.INDEX_ID, text, - GlobalSearchScope.allScope(project) + GlobalSearchScope.projectScope(project) ); for (VirtualFile configFile : configFiles) { @@ -134,6 +138,12 @@ public class ConfigKeyResolver { (file, value) -> { PsiFile psiFile = psiManager.findFile(file); if (psiFile != null) { + String relativePath = getRelativeConfigFilePath(file); + if (relativePath != null && relativePath.equals(text)) { + foundElement.set(psiFile); + return false; + } + String keyPath = extractKeyPath(text, file); if (keyPath != null) { PsiElement element = findConfigKeyInFile(psiFile, keyPath); @@ -145,13 +155,30 @@ public class ConfigKeyResolver { } return true; }, - GlobalSearchScope.projectScope(project) + GlobalSearchScope.projectScope(project), + IdFilter.getProjectIdFilter(project, false) ); return foundElement.get(); } private @Nullable String extractKeyPath(@NotNull String fullKey, @NotNull VirtualFile configFile) { + String relativePath = getRelativeConfigFilePath(configFile); + if (relativePath == null) { + return null; + } + + String prefixWithDot = relativePath + "."; + if (fullKey.startsWith(prefixWithDot)) { + return fullKey.substring(prefixWithDot.length()); + } else if (fullKey.equals(relativePath)) { + return ""; + } else { + return null; + } + } + + private @Nullable String getRelativeConfigFilePath(@NotNull VirtualFile configFile) { String filePath = configFile.getPath().replace('\\', '/'); int configIndex = filePath.lastIndexOf("/config/"); if (configIndex == -1) { @@ -163,15 +190,7 @@ public class ConfigKeyResolver { relativePath = relativePath.substring(0, relativePath.length() - ".php".length()); } - String configKeyPrefix = relativePath.replace('/', '.'); - String prefixWithDot = configKeyPrefix + "."; - if (fullKey.startsWith(prefixWithDot)) { - return fullKey.substring(prefixWithDot.length()); - } else if (fullKey.equals(configKeyPrefix)) { - return ""; - } else { - return null; - } + return relativePath.replace('/', '.'); } private boolean checkServiceProviderForConfigKey( diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/config/util/ConfigUtil.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/config/util/ConfigUtil.java index 87d751e..062d2ea 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/config/util/ConfigUtil.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/config/util/ConfigUtil.java @@ -13,7 +13,7 @@ public static void iterateOverFileChildren(String dirName, PsiFile configFile, M ArrayReturnPsiRecursiveVisitor visitor = new ArrayReturnPsiRecursiveVisitor(dirName); configFile.acceptChildren(visitor); - variants.put(dirName, null); + variants.put(dirName, ""); variants.putAll(visitor.getVariants()); } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/eloquent/table/TableReference.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/eloquent/table/TableReference.java index 9047102..3e9401b 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/eloquent/table/TableReference.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/eloquent/table/TableReference.java @@ -10,6 +10,7 @@ import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.util.indexing.FileBasedIndex; +import com.intellij.util.indexing.IdFilter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -70,13 +71,17 @@ public TableReference(@NotNull PsiElement element, TextRange rangeInElement) { @Override public Object @NotNull [] getVariants() { List variants = new ArrayList<>(); - Collection tables = FileBasedIndex.getInstance().getAllKeys(TableIndex.INDEX_ID, project); - - for (String table : tables) { - variants.add( - PsiElementUtils.buildSimpleLookupElement(table) - ); - } + FileBasedIndex.getInstance().processAllKeys( + TableIndex.INDEX_ID, + key -> { + if (key instanceof String table) { + variants.add(PsiElementUtils.buildSimpleLookupElement(table)); + } + return true; + }, + GlobalSearchScope.projectScope(project), + IdFilter.getProjectIdFilter(project, false) + ); return variants.toArray(); } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/eloquent/table/TableReferenceContributor.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/eloquent/table/TableReferenceContributor.java index 9b13e58..3608c83 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/eloquent/table/TableReferenceContributor.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/eloquent/table/TableReferenceContributor.java @@ -31,7 +31,7 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferen public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) { Project project = psiElement.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return PsiReference.EMPTY_ARRAY; } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/gate/GateReferenceContributor.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/gate/GateReferenceContributor.java index dce14b6..0b2d33b 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/gate/GateReferenceContributor.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/gate/GateReferenceContributor.java @@ -40,7 +40,7 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) { Project project = element.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return PsiReference.EMPTY_ARRAY; } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/indexes/ConfigIndex.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/indexes/ConfigIndex.java index b94ff19..31545b2 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/indexes/ConfigIndex.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/indexes/ConfigIndex.java @@ -34,7 +34,7 @@ public class ConfigIndex extends FileBasedIndexExtension { return inputData -> { Project project = inputData.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return Collections.emptyMap(); } @@ -84,7 +84,7 @@ public String read(@NotNull DataInput dataInput) throws IOException { @Override public int getVersion() { - return 1; + return 2; } @Override @@ -108,6 +108,11 @@ public boolean dependsOnFileContent() { return true; } + @Override + public boolean traceKeyHashToVirtualFileMapping() { + return true; + } + private boolean isConfigFile(VirtualFile file, Project project) { String basePath = project.getBasePath(); if (basePath == null) { diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/indexes/RouteIndex.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/indexes/RouteIndex.java index 4d2d04a..c09a415 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/indexes/RouteIndex.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/indexes/RouteIndex.java @@ -63,7 +63,7 @@ public class RouteIndex extends FileBasedIndexExtension { return inputData -> { Project project = inputData.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return Collections.emptyMap(); } @@ -100,7 +100,7 @@ public class RouteIndex extends FileBasedIndexExtension { @Override public int getVersion() { - return 1; + return 2; } @Override @@ -113,6 +113,11 @@ public boolean dependsOnFileContent() { return true; } + @Override + public boolean traceKeyHashToVirtualFileMapping() { + return true; + } + private boolean isLaravelRouteMethod(MethodReference methodReference) { PhpExpression routeClassReference = methodReference.getClassReference(); diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/indexes/ServiceProviderIndex.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/indexes/ServiceProviderIndex.java index 44db78d..9fb7099 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/indexes/ServiceProviderIndex.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/indexes/ServiceProviderIndex.java @@ -38,7 +38,7 @@ public class ServiceProviderIndex extends FileBasedIndexExtension { Project project = inputData.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return Collections.emptyMap(); } @@ -77,7 +77,7 @@ public class ServiceProviderIndex extends FileBasedIndexExtension classReferences = extendsList.getReferenceElements(); diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/indexes/TableIndex.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/indexes/TableIndex.java index 2674141..de1472e 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/indexes/TableIndex.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/indexes/TableIndex.java @@ -39,7 +39,7 @@ public class TableIndex extends FileBasedIndexExtension { return inputData -> { Project project = inputData.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return Collections.emptyMap(); } @@ -76,7 +76,7 @@ public class TableIndex extends FileBasedIndexExtension { @Override public int getVersion() { - return 1; + return 2; } @Override @@ -89,6 +89,11 @@ public boolean dependsOnFileContent() { return true; } + @Override + public boolean traceKeyHashToVirtualFileMapping() { + return true; + } + private boolean shouldScanMethod(MethodReference methodReference) { PhpExpression schemaClassReference = methodReference.getClassReference(); diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/InertiaMethodValidator.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/InertiaMethodValidator.java new file mode 100644 index 0000000..4bf84ba --- /dev/null +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/InertiaMethodValidator.java @@ -0,0 +1,139 @@ +package at.alirezamoh.whisperer_for_laravel.packages.inertia; + +import at.alirezamoh.whisperer_for_laravel.support.utils.MethodUtils; +import at.alirezamoh.whisperer_for_laravel.support.utils.PhpClassUtils; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.jetbrains.php.lang.psi.elements.FunctionReference; +import com.jetbrains.php.lang.psi.elements.MethodReference; +import com.jetbrains.php.lang.psi.elements.PhpClass; +import com.jetbrains.php.lang.psi.elements.impl.PhpClassImpl; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class InertiaMethodValidator { + /** + * The names of the methods in the 'View' facade that can reference Blade files + */ + private static final Map INERTIA_METHODS = new HashMap<>() {{ + put("render", 0); + }}; + + /** + * The names of the methods in the 'View' facade that can reference Blade files + */ + private static final Map ROUTE_INERTIA_METHODS = new HashMap<>() {{ + put("inertia", 0); + }}; + + /** + * The FQN of the 'Route' facade + */ + private static final String INERTIA = "\\Inertia\\Inertia"; + + /** + * The FQN of the 'Route' facade + */ + private static final String ROUTE = "\\Illuminate\\Support\\Facades\\Route"; + + private InertiaMethodValidator() {} + + /** + * Checks if a given PSI element is inside a valid Inertia method, Route method, or function. + * + * @param psiElement The PSI element to check. + * @return True if the element is inside a valid method or function, false otherwise. + */ + public static boolean isInsideCorrectMethod(@NotNull PsiElement psiElement) { + MethodReference method = MethodUtils.resolveMethodReference(psiElement, 10); + Project project = psiElement.getProject(); + + if (method != null) { + return isInertiaMethod(method, psiElement, project) || isRouteMethod(method, psiElement, project); + } + + return isInertiaFunction(psiElement); + } + + /** + * Checks if a given method reference matches an Inertia method. + * + * @param methodReference The method reference. + * @param position The PSI element position. + * @param project The current project. + * @return true or false + */ + private static boolean isInertiaMethod(MethodReference methodReference, PsiElement position, Project project) { + return isExpectedMethod(methodReference, position, project, INERTIA_METHODS, INERTIA); + } + + /** + * Checks if a given method reference matches a Route method. + * + * @param methodReference The method reference. + * @param position The PSI element position. + * @param project The current project. + * @return true or false + */ + private static boolean isRouteMethod(MethodReference methodReference, PsiElement position, Project project) { + return isExpectedMethod(methodReference, position, project, ROUTE_INERTIA_METHODS, ROUTE); + } + + /** + * Generalized logic for matching method references to a specific class and parameter criteria. + * + * @param methodReference The method reference. + * @param position The PSI element position. + * @param project The current project. + * @param methodMap Map of method names and their parameter indices. + * @param expectedClassFQN Fully qualified name of the expected class. + * @return true or false + */ + private static boolean isExpectedMethod( + MethodReference methodReference, + PsiElement position, + Project project, + Map methodMap, + String expectedClassFQN + ) { + Integer expectedParamIndex = methodMap.get(methodReference.getName()); + + if (expectedParamIndex == null) { + return false; + } + + List resolvedClasses = getPhpClassesForMethod(methodReference, project); + PhpClass expectedClass = PhpClassUtils.getClassByFQN(project, expectedClassFQN); + + return expectedClass != null + && resolvedClasses.stream().anyMatch(clazz -> PhpClassUtils.isChildOf(clazz, expectedClass)) + && expectedParamIndex == MethodUtils.findParamIndex(position, false); + } + + /** + * Checks if the given method reference is a view or route method + * @param position psi element + * @return true or false + */ + private static boolean isInertiaFunction(PsiElement position) { + FunctionReference functionReference = MethodUtils.resolveFunctionReference(position, 10); + if (functionReference == null) { + return false; + } + + Integer expectedParamIndex = ROUTE_INERTIA_METHODS.get(functionReference.getName()); + + if (expectedParamIndex == null) { + return false; + } + + return expectedParamIndex == MethodUtils.findParamIndex(position, false); + } + + private static @NotNull List getPhpClassesForMethod(MethodReference methodReference, Project project) { + return MethodUtils.resolveMethodClasses(methodReference, project); + } +} diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/InertiaPageCollector.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/InertiaPageCollector.java new file mode 100644 index 0000000..60d139c --- /dev/null +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/InertiaPageCollector.java @@ -0,0 +1,139 @@ +package at.alirezamoh.whisperer_for_laravel.packages.inertia; + +import at.alirezamoh.whisperer_for_laravel.settings.SettingsState; +import at.alirezamoh.whisperer_for_laravel.support.utils.DirectoryUtils; +import at.alirezamoh.whisperer_for_laravel.support.utils.PluginUtils; +import at.alirezamoh.whisperer_for_laravel.support.utils.StrUtils; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class InertiaPageCollector { + /** + * Collect all Inertia pages based on the paths configured in SettingsState + * + * @param withFile Whether to include the PsiFile reference in the InertiaPage objects. + * @return A list of discovered Inertia pages + */ + public static List collectPages(Project project, boolean withFile) { + List references = new ArrayList<>(); + SettingsState settings = SettingsState.getInstance(project); + + String defaultPath = ""; + if (!settings.isProjectDirectoryEmpty()) { + defaultPath = StrUtils.removeDoubleForwardSlashes( + StrUtils.addSlashes(settings.getProjectDirectoryPath()) + defaultPath + ); + } + + String inertiaPaths = settings.getInertiaPageRootPath(); + if (inertiaPaths == null) { + return references; + } + + String[] paths = inertiaPaths.split(";"); + paths = Arrays.stream(paths) + .filter(path -> !path.isEmpty()) + .toArray(String[]::new); + + for (String path : paths) { + PsiDirectory potentialDir = DirectoryUtils.getDirectory( + project, + StrUtils.removeDoubleForwardSlashes( + defaultPath + + StrUtils.addSlashes(path.replace("\\", "/")) + ) + ); + + if (potentialDir != null) { + references.addAll(getPagesInternal(potentialDir, withFile, "")); + } + } + + return references; + } + + /** + * Retrieves the available Inertia paths from the project settings + * + * @param project The current project + * @return A list of inertia paths or an empty list if none are set + */ + public static List getInertiaPaths(@NotNull Project project) { + SettingsState settingsState = SettingsState.getInstance(project); + String inertiaPaths = settingsState.getInertiaPageRootPath(); + if (inertiaPaths == null) { + return new ArrayList<>(); + } + + String[] paths = inertiaPaths.split(";"); + return Arrays.stream(paths) + .filter(path -> !path.isEmpty()) + .toList(); + } + + /** + * Check if the project is using the inertia package + * @param project The current project + */ + public static boolean doNotCompleteOrNavigate(Project project) { + return !PluginUtils.isLaravelProject(project) + || PluginUtils.isLaravelFrameworkNotInstalled(project) + || !PluginUtils.doesPackageExists(project, "inertiajs/inertia-laravel") + || DirectoryUtils.getDirectory(project, "/vendor/inertiajs/inertia-laravel") == null; + } + + /** + * Recursively collect pages from the given directory + * + * @param dir The directory to scan + * @param withFile Whether to include a PsiFile reference in each InertiaPage + * @param parentPath The accumulated path from root directories + * @return A list of Inertia pages found in this directory and its subdirectories + */ + private static List getPagesInternal(PsiDirectory dir, boolean withFile, String parentPath) { + + List pages = new ArrayList<>(buildPagePath(dir.getFiles(), withFile, parentPath)); + + for (PsiDirectory subDir : dir.getSubdirectories()) { + String newParentPath = parentPath.isEmpty() ? subDir.getName() : parentPath + "/" + subDir.getName(); + pages.addAll(getPagesInternal(subDir, withFile, newParentPath)); + } + + return pages; + } + + /** + * Builds the path for all files in a single directory to detect Vue or JSX files representing Inertia pages. + * + * @param files The array of files in this directory + * @param withFile Whether to include a PsiFile reference in each InertiaPage + * @param parentPath The accumulated parent path segments + * @return A list of Inertia pages + */ + private static List buildPagePath(PsiFile[] files, boolean withFile, String parentPath) { + List pages = new ArrayList<>(); + + for (PsiFile psiFile : files) { + String fileName = psiFile.getName(); + + if (fileName.endsWith(".vue") || fileName.endsWith(".jsx")) { + String pageName = parentPath.isEmpty() ? "" : parentPath + "/"; + pageName += fileName.replaceFirst("\\.(vue|jsx)$", ""); + + if (withFile) { + pages.add(new InertiaPage(pageName, psiFile)); + } else { + pages.add(new InertiaPage(pageName)); + } + } + } + + return pages; + } +} diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/InertiaReference.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/InertiaReference.java index 9796fb1..44b8a24 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/InertiaReference.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/InertiaReference.java @@ -1,8 +1,5 @@ package at.alirezamoh.whisperer_for_laravel.packages.inertia; -import at.alirezamoh.whisperer_for_laravel.settings.SettingsState; -import at.alirezamoh.whisperer_for_laravel.support.utils.DirectoryUtils; -import at.alirezamoh.whisperer_for_laravel.support.utils.PluginUtils; import at.alirezamoh.whisperer_for_laravel.support.utils.PsiElementUtils; import at.alirezamoh.whisperer_for_laravel.support.utils.StrUtils; import com.intellij.codeInsight.lookup.LookupElementBuilder; @@ -13,7 +10,6 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; public class InertiaReference extends PsiReferenceBase { @@ -35,7 +31,7 @@ public InertiaReference(@NotNull PsiElement element, TextRange rangeInElement) { @Override public @Nullable PsiElement resolve() { String text = StrUtils.removeQuotes(myElement.getText()); - List pages = collectPages(true); + List pages = InertiaPageCollector.collectPages(project, true); for (InertiaPage page : pages) { if (page.getPath().equals(text)) { @@ -48,7 +44,7 @@ public InertiaReference(@NotNull PsiElement element, TextRange rangeInElement) { @Override public Object @NotNull [] getVariants() { - List pages = collectPages(false); + List pages = InertiaPageCollector.collectPages(project, false); List variants = new ArrayList<>(); for (InertiaPage page : pages) { @@ -59,97 +55,4 @@ public InertiaReference(@NotNull PsiElement element, TextRange rangeInElement) { return variants.toArray(); } - - /** - * Collect all Inertia pages based on the paths configured in SettingsState - * - * @param withFile Whether to include the PsiFile reference in the InertiaPage objects. - * @return A list of discovered Inertia pages - */ - public List collectPages(boolean withFile) { - List references = new ArrayList<>(); - SettingsState settings = SettingsState.getInstance(project); - - String defaultPath = ""; - if (!settings.isProjectDirectoryEmpty()) { - defaultPath = StrUtils.removeDoubleForwardSlashes( - StrUtils.addSlashes(settings.getProjectDirectoryPath()) + defaultPath - ); - } - - String inertiaPaths = settings.getInertiaPageRootPath(); - if (inertiaPaths == null) { - return references; - } - - String[] paths = inertiaPaths.split(";"); - paths = Arrays.stream(paths) - .filter(path -> !path.isEmpty()) - .toArray(String[]::new); - - for (String path : paths) { - PsiDirectory potentialDir = DirectoryUtils.getDirectory( - project, - StrUtils.removeDoubleForwardSlashes( - defaultPath + - StrUtils.addSlashes(path.replace("\\", "/")) - ) - ); - - if (potentialDir != null) { - references.addAll(getPagesInternal(potentialDir, withFile, "")); - } - } - - return references; - } - - /** - * Recursively collect pages from the given directory - * - * @param dir The directory to scan - * @param withFile Whether to include a PsiFile reference in each InertiaPage - * @param parentPath The accumulated path from root directories - * @return A list of Inertia pages found in this directory and its subdirectories - */ - private List getPagesInternal(PsiDirectory dir, boolean withFile, String parentPath) { - - List pages = new ArrayList<>(buildPagePath(dir.getFiles(), withFile, parentPath)); - - for (PsiDirectory subDir : dir.getSubdirectories()) { - String newParentPath = parentPath.isEmpty() ? subDir.getName() : parentPath + "/" + subDir.getName(); - pages.addAll(getPagesInternal(subDir, withFile, newParentPath)); - } - - return pages; - } - - /** - * Builds the path for all files in a single directory to detect Vue or JSX files representing Inertia pages. - * - * @param files The array of files in this directory - * @param withFile Whether to include a PsiFile reference in each InertiaPage - * @param parentPath The accumulated parent path segments - * @return A list of Inertia pages - */ - private List buildPagePath(PsiFile[] files, boolean withFile, String parentPath) { - List pages = new ArrayList<>(); - - for (PsiFile psiFile : files) { - String fileName = psiFile.getName(); - - if (fileName.endsWith(".vue") || fileName.endsWith(".jsx")) { - String pageName = parentPath.isEmpty() ? "" : parentPath + "/"; - pageName += fileName.replaceFirst("\\.(vue|jsx)$", ""); - - if (withFile) { - pages.add(new InertiaPage(pageName, psiFile)); - } else { - pages.add(new InertiaPage(pageName)); - } - } - } - - return pages; - } } \ No newline at end of file diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/InertiaReferenceContributor.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/InertiaReferenceContributor.java index 15eeecd..332c34b 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/InertiaReferenceContributor.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/InertiaReferenceContributor.java @@ -1,48 +1,15 @@ package at.alirezamoh.whisperer_for_laravel.packages.inertia; -import at.alirezamoh.whisperer_for_laravel.support.utils.MethodUtils; -import at.alirezamoh.whisperer_for_laravel.support.utils.PhpClassUtils; -import at.alirezamoh.whisperer_for_laravel.support.utils.PluginUtils; import at.alirezamoh.whisperer_for_laravel.support.utils.PsiElementUtils; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.util.ProcessingContext; import com.jetbrains.php.lang.psi.elements.*; -import com.jetbrains.php.lang.psi.elements.impl.PhpClassImpl; import org.jetbrains.annotations.NotNull; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import static com.intellij.patterns.PlatformPatterns.psiElement; public class InertiaReferenceContributor extends PsiReferenceContributor { - /** - * The names of the methods in the 'View' facade that can reference Blade files - */ - public static Map INERTIA_METHODS = new HashMap<>() {{ - put("render", 0); - }}; - - /** - * The names of the methods in the 'View' facade that can reference Blade files - */ - public static Map ROUTE_INERTIA_METHODS = new HashMap<>() {{ - put("inertia", 0); - }}; - - /** - * The FQN of the 'Route' facade - */ - private final String INERTIA = "\\Inertia\\Inertia"; - - /** - * The FQN of the 'Route' facade - */ - private final String ROUTE = "\\Illuminate\\Support\\Facades\\Route"; - @Override public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferenceRegistrar) { psiReferenceRegistrar.registerReferenceProvider( @@ -51,9 +18,7 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferen @Override public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) { - Project project = psiElement.getProject(); - - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (InertiaPageCollector.doNotCompleteOrNavigate(psiElement.getProject())) { return PsiReference.EMPTY_ARRAY; } @@ -61,7 +26,7 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferen return PsiReference.EMPTY_ARRAY; } - if (isInsideCorrectMethod(psiElement)) { + if (InertiaMethodValidator.isInsideCorrectMethod(psiElement)) { return new PsiReference[]{new InertiaReference( stringLiteralExpression, new TextRange( @@ -76,100 +41,4 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferen } ); } - - /** - * Checks if a given PSI element is inside a valid Inertia method, Route method, or function. - * - * @param psiElement The PSI element to check. - * @return True if the element is inside a valid method or function, false otherwise. - */ - private boolean isInsideCorrectMethod(@NotNull PsiElement psiElement) { - MethodReference method = MethodUtils.resolveMethodReference(psiElement, 10); - Project project = psiElement.getProject(); - - if (method != null) { - return isInertiaMethod(method, psiElement, project) || isRouteMethod(method, psiElement, project); - } - - return isInertiaFunction(psiElement); - } - - /** - * Checks if a given method reference matches an Inertia method. - * - * @param methodReference The method reference. - * @param position The PSI element position. - * @param project The current project. - * @return true or false - */ - private boolean isInertiaMethod(MethodReference methodReference, PsiElement position, Project project) { - return isExpectedMethod(methodReference, position, project, INERTIA_METHODS, INERTIA); - } - - /** - * Checks if a given method reference matches a Route method. - * - * @param methodReference The method reference. - * @param position The PSI element position. - * @param project The current project. - * @return true or false - */ - private boolean isRouteMethod(MethodReference methodReference, PsiElement position, Project project) { - return isExpectedMethod(methodReference, position, project, ROUTE_INERTIA_METHODS, ROUTE); - } - - /** - * Generalized logic for matching method references to a specific class and parameter criteria. - * - * @param methodReference The method reference. - * @param position The PSI element position. - * @param project The current project. - * @param methodMap Map of method names and their parameter indices. - * @param expectedClassFQN Fully qualified name of the expected class. - * @return true or false - */ - private boolean isExpectedMethod( - MethodReference methodReference, - PsiElement position, - Project project, - Map methodMap, - String expectedClassFQN - ) { - Integer expectedParamIndex = methodMap.get(methodReference.getName()); - - if (expectedParamIndex == null) { - return false; - } - - List resolvedClasses = getPhpClassesForMethod(methodReference, project); - PhpClass expectedClass = PhpClassUtils.getClassByFQN(project, expectedClassFQN); - - return expectedClass != null - && resolvedClasses.stream().anyMatch(clazz -> PhpClassUtils.isChildOf(clazz, expectedClass)) - && expectedParamIndex == MethodUtils.findParamIndex(position, false); - } - - private @NotNull List getPhpClassesForMethod(MethodReference methodReference, Project project) { - return MethodUtils.resolveMethodClasses(methodReference, project); - } - - /** - * Checks if the given method reference is a view or route method - * @param position psi element - * @return true or false - */ - private boolean isInertiaFunction(PsiElement position) { - FunctionReference functionReference = MethodUtils.resolveFunctionReference(position, 10); - if (functionReference == null) { - return false; - } - - Integer expectedParamIndex = ROUTE_INERTIA_METHODS.get(functionReference.getName()); - - if (expectedParamIndex == null) { - return false; - } - - return expectedParamIndex == MethodUtils.findParamIndex(position, false); - } } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/annotator/CreateInertiaPageIntention.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/annotator/CreateInertiaPageIntention.java new file mode 100644 index 0000000..7d5e3ed --- /dev/null +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/annotator/CreateInertiaPageIntention.java @@ -0,0 +1,166 @@ +package at.alirezamoh.whisperer_for_laravel.packages.inertia.annotator; + +import at.alirezamoh.whisperer_for_laravel.actions.models.InertiaPageModel; +import at.alirezamoh.whisperer_for_laravel.packages.inertia.InertiaPageCollector; +import at.alirezamoh.whisperer_for_laravel.settings.SettingsState; +import at.alirezamoh.whisperer_for_laravel.support.TemplateLoader; +import at.alirezamoh.whisperer_for_laravel.support.utils.StrUtils; +import com.intellij.codeInsight.intention.impl.BaseIntentionAction; +import com.intellij.codeInspection.util.IntentionFamilyName; +import com.intellij.codeInspection.util.IntentionName; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.ui.popup.IconButton; +import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.psi.PsiFile; +import com.intellij.ui.JBColor; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.util.List; + +/** + * Intention action to create a new Inertia page + */ +public class CreateInertiaPageIntention extends BaseIntentionAction { + /** + * The raw page path where the new page will be created + */ + private final String pagePath; + + /** + * The project settings + */ + private SettingsState settingsState; + + /** + * @param pagePath The target page path + */ + public CreateInertiaPageIntention(String pagePath) { + this.pagePath = StrUtils.removeQuotes(pagePath); + } + + + /** + * Determines if this intention action is available + * In this case, the action is always available + * + * @param project The current project + * @param editor The editor instance + * @param psiFile The current PSI file + * @return true or false + */ + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile psiFile) { + return true; + } + + /** + * Execute the intention action + * + * @param project The current project. + * @param editor The editor instance. + * @param psiFile The current PSI file. + * @throws IncorrectOperationException If the operation cannot be completed + */ + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile psiFile) throws IncorrectOperationException { + List options = InertiaPageCollector.getInertiaPaths(project); + if (options == null) { + return; + } + + JPanel contentPanel = new JPanel(new BorderLayout()); + contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + JPanel comboPanel = new JPanel(); + comboPanel.setLayout(new BoxLayout(comboPanel, BoxLayout.Y_AXIS)); + + ComboBox pathsComboBox = new ComboBox<>(options.toArray(new String[0])); + pathsComboBox.setAlignmentX(Component.LEFT_ALIGNMENT); + + ComboBox pageVariantComboBox = new ComboBox<>(new String[]{"Options API", "Composition API"}); + pageVariantComboBox.setAlignmentX(Component.LEFT_ALIGNMENT); + + ComboBox pageTypeComboBox = new ComboBox<>(new String[]{".vue", ".jsx"}); + pageTypeComboBox.setAlignmentX(Component.LEFT_ALIGNMENT); + + + JLabel destinationLabel = new JLabel("Destination directory:"); + destinationLabel.setAlignmentX(Component.LEFT_ALIGNMENT); + comboPanel.add(destinationLabel); + comboPanel.add(pathsComboBox); + comboPanel.add(Box.createVerticalStrut(10)); + + JLabel pageTypeLabel = new JLabel("Page type:"); + pageTypeLabel.setAlignmentX(Component.LEFT_ALIGNMENT); + comboPanel.add(pageTypeLabel); + comboPanel.add(pageTypeComboBox); + comboPanel.add(Box.createVerticalStrut(10)); + + JLabel pageVariantLabel = new JLabel("Page variant: (Ignore if it's not a Vue file)"); + pageVariantLabel.setAlignmentX(Component.LEFT_ALIGNMENT); + comboPanel.add(pageVariantLabel); + comboPanel.add(pageVariantComboBox); + + contentPanel.add(comboPanel, BorderLayout.CENTER); + + JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + JButton createButton = new JButton("Create"); + JButton cancelButton = new JButton("Cancel"); + createButton.setForeground(JBColor.blue); + cancelButton.setForeground(JBColor.gray); + buttonsPanel.add(createButton); + buttonsPanel.add(cancelButton); + contentPanel.add(buttonsPanel, BorderLayout.SOUTH); + + JBPopup popup = JBPopupFactory.getInstance() + .createComponentPopupBuilder(contentPanel, comboPanel) + .setTitle("Create Inertia Page") + .setResizable(false) + .setMovable(true) + .setRequestFocus(true) + .setCancelButton(new IconButton(null, null)) + .createPopup(); + + createButton.addActionListener(e -> { + TemplateLoader templateProcessor = new TemplateLoader( + project, + "inertiaPage.ftl", + new InertiaPageModel( + SettingsState.getInstance(project), + pagePath, + "", + "", + pathsComboBox.getItem(), + pageVariantComboBox.getItem().equals("Options API"), + pageTypeComboBox.getItem() + ) + ); + + WriteCommandAction.runWriteCommandAction(project, () -> { + templateProcessor.createTemplateWithDirectory(true); + }); + + popup.closeOk(null); + }); + cancelButton.addActionListener(e -> popup.cancel()); + + popup.showInBestPositionFor(editor); + } + + @Override + public @NotNull @IntentionFamilyName String getFamilyName() { + return "Create inertia page"; + } + + @Override + public @NotNull @IntentionName String getText() { + return "Create inertia page"; + } +} diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/annotator/InertiaPageExistenceAnnotator.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/annotator/InertiaPageExistenceAnnotator.java new file mode 100644 index 0000000..3ca6bd8 --- /dev/null +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/packages/inertia/annotator/InertiaPageExistenceAnnotator.java @@ -0,0 +1,61 @@ +package at.alirezamoh.whisperer_for_laravel.packages.inertia.annotator; + +import at.alirezamoh.whisperer_for_laravel.packages.inertia.InertiaMethodValidator; +import at.alirezamoh.whisperer_for_laravel.packages.inertia.InertiaPageCollector; +import at.alirezamoh.whisperer_for_laravel.support.utils.StrUtils; +import com.intellij.lang.annotation.AnnotationHolder; +import com.intellij.lang.annotation.Annotator; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; +import org.jetbrains.annotations.NotNull; + +/** + * Annotator that checks if an Inertia page exists + *

+ * This annotator looks for an inertia page path within a PHP method call to "render" + * It then checks whether the referenced page (based on its path) exists in the project + * If the page is not found, it creates a warning annotation and provides a quick fix + * to create the missing Inertia page + */ +public class InertiaPageExistenceAnnotator implements Annotator { + /** + * Inspects the given PSI element and adds a warning annotation if an Inertia page is missing + * + * @param psiElement the PSI element to inspect + * @param annotationHolder the holder to register annotations + */ + @Override + public void annotate(@NotNull PsiElement psiElement, @NotNull AnnotationHolder annotationHolder) { + if (psiElement == null) { + return; + } + + Project project = psiElement.getProject(); + if (InertiaPageCollector.doNotCompleteOrNavigate(project)) { + return; + } + + if (!(psiElement instanceof StringLiteralExpression stringLiteralExpression)) { + return; + } + + if (!InertiaMethodValidator.isInsideCorrectMethod(stringLiteralExpression)) { + return; + } + + if (!doesPageExists(stringLiteralExpression, project)) { + annotationHolder.newAnnotation(HighlightSeverity.WARNING, "Inertia page not found") + .range(stringLiteralExpression.getTextRange()) + .withFix(new CreateInertiaPageIntention(stringLiteralExpression.getText())) + .create(); + } + } + + private boolean doesPageExists(StringLiteralExpression stringLiteralExpression, Project project) { + return InertiaPageCollector.collectPages(project, false) + .stream() + .anyMatch(page -> page.getPath().equals(StrUtils.removeQuotes(stringLiteralExpression.getText()))); + } +} diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/request/requestField/RequestFieldCompletionContributor.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/request/requestField/RequestFieldCompletionContributor.java index 244721c..75cbc1d 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/request/requestField/RequestFieldCompletionContributor.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/request/requestField/RequestFieldCompletionContributor.java @@ -45,7 +45,7 @@ protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull PsiElement position = parameters.getPosition().getOriginalElement(); Project project = position.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return; } @@ -70,7 +70,7 @@ protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull PsiElement position = parameters.getPosition().getOriginalElement(); Project project = position.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return; } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/request/requestField/RequestFieldGotoDeclarationHandler.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/request/requestField/RequestFieldGotoDeclarationHandler.java index 49f4670..a1a078e 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/request/requestField/RequestFieldGotoDeclarationHandler.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/request/requestField/RequestFieldGotoDeclarationHandler.java @@ -28,7 +28,7 @@ public class RequestFieldGotoDeclarationHandler implements GotoDeclarationHandle } Project project = sourceElement.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return null; } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/request/requestField/RequestFieldSuppressor.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/request/requestField/RequestFieldSuppressor.java index fc4a137..161917c 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/request/requestField/RequestFieldSuppressor.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/request/requestField/RequestFieldSuppressor.java @@ -32,10 +32,7 @@ public class RequestFieldSuppressor implements InspectionSuppressor { @Override public boolean isSuppressedFor(@NotNull PsiElement psiElement, @NotNull String s) { Project project = psiElement.getProject(); - if ( - (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) - || !suppressedPhpInspections.contains(s) - ) { + if (PluginUtils.shouldNotCompleteOrNavigate(project) || !suppressedPhpInspections.contains(s)) { return false; } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/request/validation/RuleValidationCompletionContributor.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/request/validation/RuleValidationCompletionContributor.java index 8153532..b4a7dc7 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/request/validation/RuleValidationCompletionContributor.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/request/validation/RuleValidationCompletionContributor.java @@ -30,7 +30,7 @@ protected void addCompletions(@NotNull CompletionParameters completionParameters PsiElement psiElement = completionParameters.getPosition().getOriginalElement().getParent(); Project project = psiElement.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return; } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/request/validation/RuleValidationGotoDeclarationHandler.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/request/validation/RuleValidationGotoDeclarationHandler.java index 0ff1033..2e17950 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/request/validation/RuleValidationGotoDeclarationHandler.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/request/validation/RuleValidationGotoDeclarationHandler.java @@ -42,7 +42,7 @@ public class RuleValidationGotoDeclarationHandler implements GotoDeclarationHand } Project project = sourceElement.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return null; } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/middleware/MiddlewareReferenceContributor.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/middleware/MiddlewareReferenceContributor.java index 063d2df..7259ace 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/middleware/MiddlewareReferenceContributor.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/middleware/MiddlewareReferenceContributor.java @@ -46,7 +46,7 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferen public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) { Project project = psiElement.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return PsiReference.EMPTY_ARRAY; } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/resourceRoute/ResourceRouteReferenceContributor.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/resourceRoute/ResourceRouteReferenceContributor.java index 7f2e98f..64e2ab8 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/resourceRoute/ResourceRouteReferenceContributor.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/resourceRoute/ResourceRouteReferenceContributor.java @@ -45,7 +45,7 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferen public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) { Project project = psiElement.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return PsiReference.EMPTY_ARRAY; } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/routeAction/RouteActionReference.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/routeAction/RouteActionReference.java index 2af0b3b..455abc4 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/routeAction/RouteActionReference.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/routeAction/RouteActionReference.java @@ -5,9 +5,7 @@ import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiReferenceBase; +import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.php.lang.psi.PhpFile; import com.jetbrains.php.lang.psi.elements.Method; @@ -15,15 +13,12 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * This class resolves references controller actions and provides completion */ -public class RouteActionReference extends PsiReferenceBase { +public class RouteActionReference extends PsiReferenceBase implements PsiPolyVariantReference { /** * The current project */ @@ -52,15 +47,45 @@ public RouteActionReference(@NotNull PsiElement element, TextRange rangeInElemen */ @Override public @Nullable PsiElement resolve() { + return null; + } + + @Override + public ResolveResult @NotNull [] multiResolve(boolean b) { String targetAction = StrUtils.removeQuotes(myElement.getText()); + if (!targetAction.contains("@")) { + return new ResolveResult[0]; + } + for (Map.Entry entry : getAllControllersWithActions().entrySet()) { if (entry.getKey().equals(targetAction)) { - return entry.getValue(); + return new ResolveResult[] { new PsiElementResolveResult(entry.getValue()) }; } } - return null; + String[] parts = targetAction.split("@"); + + if (parts.length != 2) { + return new ResolveResult[0]; + } + + String fullController = parts[0].trim(); + String methodName = parts[1].trim(); + int lastIndex = fullController.lastIndexOf('\\'); + String controllerName = (lastIndex != -1) ? fullController.substring(lastIndex + 1) : fullController; + + Collection controllers = PhpIndexUtils.getPhpClassesByName(controllerName, project); + List foundedVariants = new ArrayList<>(); + for (PhpClass controller : controllers) { + Method method = controller.findMethodByName(methodName); + + if (method != null) { + foundedVariants.add(new PsiElementResolveResult(method)); + } + } + + return foundedVariants.toArray(new ResolveResult[0]); } /** diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/routeAction/RouteActionReferenceContributor.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/routeAction/RouteActionReferenceContributor.java index 126a46b..04e88eb 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/routeAction/RouteActionReferenceContributor.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/routeAction/RouteActionReferenceContributor.java @@ -53,7 +53,7 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferen public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) { Project project = psiElement.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return PsiReference.EMPTY_ARRAY; } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/routeName/RouteReference.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/routeName/RouteReference.java index 3eff361..2e67a95 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/routeName/RouteReference.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/routeName/RouteReference.java @@ -10,13 +10,12 @@ import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.util.indexing.FileBasedIndex; +import com.intellij.util.indexing.IdFilter; import com.jetbrains.php.lang.psi.elements.MethodReference; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.util.*; /** * This class resolves references to route names and provides code completion @@ -57,21 +56,24 @@ public RouteReference(@NotNull PsiElement element, TextRange rangeInElement) { public Object @NotNull [] getVariants() { List variants = new ArrayList<>(); - Collection routes = FileBasedIndex.getInstance().getAllKeys(RouteIndex.INDEX_ID, project); - - for (String route : routes) { - String[] split = route.split(" \\| "); - - if (split.length >= 3) { - variants.add( - LookupElementBuilder - .create(split[1]) - .bold() - .withTypeText(split[0], true) - .withIcon(WhispererForLaravelIcon.LARAVEL_ICON) - ); - } - }; + FileBasedIndex.getInstance().processAllKeys( + RouteIndex.INDEX_ID, + route -> { + String[] split = route.split(" \\| "); + if (split.length >= 3) { + variants.add( + LookupElementBuilder + .create(split[1]) + .bold() + .withTypeText(split[0], true) + .withIcon(WhispererForLaravelIcon.LARAVEL_ICON) + ); + } + return true; + }, + GlobalSearchScope.projectScope(project), + IdFilter.getProjectIdFilter(project, false) + ); return variants.toArray(); } @@ -82,14 +84,26 @@ public RouteReference(@NotNull PsiElement element, TextRange rangeInElement) { List results = new ArrayList<>(); FileBasedIndex fileBasedIndex = FileBasedIndex.getInstance(); - Collection keys = fileBasedIndex.getAllKeys(RouteIndex.INDEX_ID, project); + Set matchingKeys = new HashSet<>(); + fileBasedIndex.processAllKeys(RouteIndex.INDEX_ID, key -> { + String[] split = key.split(" \\| "); + if (split.length >= 3 && split[1].equals(routeName)) { + matchingKeys.add(key); + } + return true; + }, + GlobalSearchScope.projectScope(project), + IdFilter.getProjectIdFilter(project, false) + ); - for (String key : keys) { + for (String key : matchingKeys) { String[] split = key.split(" \\| "); if (split.length >= 3 && split[1].equals(routeName)) { Collection files = fileBasedIndex.getContainingFiles( - RouteIndex.INDEX_ID, key, GlobalSearchScope.allScope(project) + RouteIndex.INDEX_ID, + key, + GlobalSearchScope.projectScope(project) ); for (VirtualFile file : files) { diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/routeName/RouteReferenceContributor.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/routeName/RouteReferenceContributor.java index c568404..81ca1f6 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/routeName/RouteReferenceContributor.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/routing/routeName/RouteReferenceContributor.java @@ -56,7 +56,7 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferen public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) { Project project = psiElement.getProject(); - if (!PluginUtils.isLaravelProject(project) && PluginUtils.isLaravelFrameworkNotInstalled(project)) { + if (PluginUtils.shouldNotCompleteOrNavigate(project)) { return PsiReference.EMPTY_ARRAY; } diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/support/codeGeneration/MigrationManager.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/support/codeGeneration/MigrationManager.java index f8acd6d..2d00a3c 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/support/codeGeneration/MigrationManager.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/support/codeGeneration/MigrationManager.java @@ -7,17 +7,15 @@ import at.alirezamoh.whisperer_for_laravel.actions.models.dataTables.Table; import at.alirezamoh.whisperer_for_laravel.indexes.TableIndex; import at.alirezamoh.whisperer_for_laravel.support.codeGeneration.vistors.MigrationVisitor; -import at.alirezamoh.whisperer_for_laravel.support.utils.LaravelPaths; -import at.alirezamoh.whisperer_for_laravel.support.utils.MethodUtils; +import at.alirezamoh.whisperer_for_laravel.support.utils.*; import at.alirezamoh.whisperer_for_laravel.support.providers.ModelProvider; -import at.alirezamoh.whisperer_for_laravel.support.utils.PhpClassUtils; -import at.alirezamoh.whisperer_for_laravel.support.utils.StrUtils; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.util.indexing.FileBasedIndex; +import com.intellij.util.indexing.IdFilter; import com.jetbrains.php.lang.psi.PhpFile; import com.jetbrains.php.lang.psi.elements.PhpClass; import com.jetbrains.php.lang.psi.elements.impl.*; @@ -124,31 +122,35 @@ private void getAllModels(Project project) { * Searches for migration files and extracts tables */ private void searchForMigrations() { - Collection files = new ArrayList<>(); - FileBasedIndex fileBasedIndex = FileBasedIndex.getInstance(); - - Collection tables = fileBasedIndex.getAllKeys(TableIndex.INDEX_ID, project); - - for (String table : tables) { - files.addAll( - fileBasedIndex.getContainingFiles( - TableIndex.INDEX_ID, - table, - GlobalSearchScope.allScope(project) - ) - ); - } - - for (VirtualFile file : files) { - PsiFile psiFile = PsiManager.getInstance(project).findFile(file); - - if (psiFile != null) { - MigrationVisitor migrationVisitor = new MigrationVisitor(); - psiFile.acceptChildren(migrationVisitor); - this.tables.addAll(migrationVisitor.getTables()); + Set keys = new HashSet<>(); + + FileBasedIndex.getInstance().processAllKeys( + TableIndex.INDEX_ID, + key -> { + keys.add(key); + return true; + }, + project + ); + + PsiManager psiManager = PsiManager.getInstance(project); + fileBasedIndex.processFilesContainingAnyKey( + TableIndex.INDEX_ID, + keys, + GlobalSearchScope.allScope(project), + null, + null, + file -> { + PsiFile psiFile = psiManager.findFile(file); + if (psiFile != null) { + MigrationVisitor migrationVisitor = new MigrationVisitor(); + psiFile.acceptChildren(migrationVisitor); + this.tables.addAll(migrationVisitor.getTables()); + } + return true; } - } + ); } /** diff --git a/src/main/java/at/alirezamoh/whisperer_for_laravel/support/utils/PluginUtils.java b/src/main/java/at/alirezamoh/whisperer_for_laravel/support/utils/PluginUtils.java index 1a764fe..48d6890 100644 --- a/src/main/java/at/alirezamoh/whisperer_for_laravel/support/utils/PluginUtils.java +++ b/src/main/java/at/alirezamoh/whisperer_for_laravel/support/utils/PluginUtils.java @@ -144,6 +144,36 @@ public static boolean isDumbMode(Project project) { return StrUtils.removeDoubleSlashes(defaultPath); } + /** + * Determines whether the given package exists in the project by inspecting + * the "composer.json" file + * + * @param project The current project + * @return true or false + */ + public static boolean doesPackageExists(Project project, String packageName) { + PsiFile composerFile = getComposerFile(project); + if (composerFile == null) { + return false; + } + + try { + String fileContent = composerFile.getText(); + + JsonObject jsonObject = JsonParser.parseString(fileContent).getAsJsonObject(); + JsonObject require = jsonObject.getAsJsonObject("require"); + + return require != null && require.has(packageName); + } catch (Exception e) { + LOG.error("Could not extract " + packageName + " from composer file", e); + return false; + } + } + + public static boolean shouldNotCompleteOrNavigate(Project project) { + return !PluginUtils.isLaravelProject(project) || PluginUtils.isLaravelFrameworkNotInstalled(project); + } + /** * Finds the project's composer.json file based on the user-specified or default project path * diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 5017755..751d633 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -17,6 +17,8 @@

  • Laravel packages support: InertiaJs
  • + Documentation
    +

    Contributions:

    Contributions are always welcome! If you encounter any issues, feel free to open an issue on the GitHub Issues page. If you want to contribute to the project, do not hesitate

    ]]> @@ -26,8 +28,12 @@ -
  • Bug fix: Validates Laravel framework installation based on the specified version.
  • -
  • Removes unused libraries
  • +
  • Adds keyboard shortcut for the search menu.
  • +
  • Adds Inertia page creation action.
  • +
  • Adds Inertia page inspection check.
  • +
  • Adds route action 'Go to Declaration' support for older Laravel versions.
  • +
  • Fixes bugs.
  • +
  • Minimum required version set to PhpStorm 2024.1, dropping support for 2023 and below
  • ]]>
    @@ -39,7 +45,7 @@ com.intellij.modules.platform com.jetbrains.php.blade - 1.1.6 + 1.2.0 @@ -80,89 +86,95 @@ + + - + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + - + diff --git a/src/main/resources/templates/inertiaPage.ftl b/src/main/resources/templates/inertiaPage.ftl new file mode 100644 index 0000000..d7ddf53 --- /dev/null +++ b/src/main/resources/templates/inertiaPage.ftl @@ -0,0 +1,41 @@ +<#if vue> +<#if withOptionsApi> + + + + + +<#else> + + + + + + +<#else> +import React from 'react'; + +const ${pageName} = () => { + return ( +
    + {/* Your component markup goes here */} +
    + ); +}; + +export default ${pageName}; + \ No newline at end of file diff --git a/src/main/resources/templates/laravelModels.ftl b/src/main/resources/templates/laravelModels.ftl index b982b4a..5df0a17 100644 --- a/src/main/resources/templates/laravelModels.ftl +++ b/src/main/resources/templates/laravelModels.ftl @@ -50,12 +50,9 @@ namespace ${namespace} { * @method static _BaseEloquentCollection|${model.modelName}[] get(array|string $columns = ['*']) * @method static ${model.modelName} getModel() * @method static ${model.modelName}[] getModels(array|string $columns = ['*']) - * @method static _BaseEloquentCollection|${model.modelName}[] hydrate(array $items) * @method static _BaseEloquentCollection|${model.modelName}[] lazy(int $chunkSize = 1000) * @method static _BaseEloquentCollection|${model.modelName}[] lazyById(int $chunkSize = 1000, null|string $column = null, null|string $alias = null) - * @method static _BaseEloquentCollection|${model.modelName}[] lazyByIdDesc(int $chunkSize = 1000, null|string $column = null, null|string $alias = null) * @method static ${model.modelName} make(array $attributes = []) - * @method static ${model.modelName} newModelInstance(array $attributes = []) * @method static _BaseEloquentCollection|LengthAwarePaginator|${model.modelName}[] paginate(\Closure|int|null $perPage = null, array|string $columns = ['*'], string $pageName = 'page', int|null $page = null, \Closure|int|null $total = null) * @method static _BaseEloquentCollection|Paginator|${model.modelName}[] simplePaginate(int|null $perPage = null, array|string $columns = ['*'], string $pageName = 'page', int|null $page = null) * @method static ${model.modelName} sole(array|string $columns = ['*'])