diff --git a/CHANGELOG.md b/CHANGELOG.md index 518ec33..9a7cbe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,13 @@ ## [Unreleased] -## [1.0.0] - 2025-07-16 +## [1.0.1] - 2025-07-17 + - Support for adding new environment variables from the Panel + - Support for deleting environment variables from the Panel + - Support for renaming environment variables from the Panel + - Improved error handling and safety checks +## [1.0.0] - 2025-07-16 - First release [Unreleased]: https://github.com/ringlesoft/visual-env/compare/v1.0.0...HEAD diff --git a/src/main/java/com/ringlesoft/visualenv/model/EnvVariable.java b/src/main/java/com/ringlesoft/visualenv/model/EnvVariable.java index e16b497..ab70aa9 100644 --- a/src/main/java/com/ringlesoft/visualenv/model/EnvVariable.java +++ b/src/main/java/com/ringlesoft/visualenv/model/EnvVariable.java @@ -31,7 +31,7 @@ public String getName() { } public String getValue() { - return isSecret ? "********" : value; + return isSecret ? "*".repeat(value.length()) : value; } public String getRawValue() { diff --git a/src/main/java/com/ringlesoft/visualenv/model/EnvVariableDefinition.java b/src/main/java/com/ringlesoft/visualenv/model/EnvVariableDefinition.java index 1937637..8c0786c 100644 --- a/src/main/java/com/ringlesoft/visualenv/model/EnvVariableDefinition.java +++ b/src/main/java/com/ringlesoft/visualenv/model/EnvVariableDefinition.java @@ -1,6 +1,5 @@ package com.ringlesoft.visualenv.model; -import java.util.Arrays; import java.util.Collections; import java.util.List; diff --git a/src/main/java/com/ringlesoft/visualenv/profile/LaravelProfile.java b/src/main/java/com/ringlesoft/visualenv/profile/LaravelProfile.java index f0ccbe8..8fb76a8 100644 --- a/src/main/java/com/ringlesoft/visualenv/profile/LaravelProfile.java +++ b/src/main/java/com/ringlesoft/visualenv/profile/LaravelProfile.java @@ -1,7 +1,6 @@ package com.ringlesoft.visualenv.profile; import com.ringlesoft.visualenv.model.CliActionDefinition; -import com.ringlesoft.visualenv.model.CliParameterDefinition; import com.ringlesoft.visualenv.model.EnvFileDefinition; import com.ringlesoft.visualenv.model.EnvVariableDefinition; @@ -24,7 +23,7 @@ public class LaravelProfile implements EnvProfile { public static final String GROUP_MAIL = "mail"; public static final String GROUP_PUSHER = "pusher"; public static final String GROUP_AWS = "aws"; - public static final String GROUP_REDIS = "redis"; + public static final String GROUP_DEBUG = "redis"; public static final String GROUP_VITE_PUSHER = "vite_pusher"; private static final Map REGISTRY = new HashMap<>(); @@ -374,6 +373,15 @@ public class LaravelProfile implements EnvProfile { false ); + register( + "DEBUGBAR_ENABLED", + "Enable or Disable Laravel Debug-bar", + null, + EnvVariableDefinition.VariableType.BOOLEAN, + GROUP_DEBUG, + false + ); + EnvVariableDefinition appKey = REGISTRY.get("APP_KEY"); if (appKey != null) { @@ -467,19 +475,19 @@ public List getAvailableCliActions() { "Generate a new application key and store it in the .env file" ).addEnvironmentVariable("ENV_FILE", "{selectedEnvFile}"), - new CliActionDefinition( - "artisan_env_encrypt", - "Encrypt Environment File", - "php artisan env:encrypt", - "Encrypts an environment file variable using the Laravel framework" - ).addParameter( - new CliParameterDefinition( - "name", - "Variable Name", - "Name of the environment variable to retrieve", - true - ) - ), +// new CliActionDefinition( +// "artisan_env_encrypt", +// "Encrypt Environment File", +// "php artisan env:encrypt", +// "Encrypts an environment file variable using the Laravel framework" +// ).addParameter( +// new CliParameterDefinition( +// "name", +// "Variable Name", +// "Name of the environment variable to retrieve", +// true +// ) +// ), }; return Arrays.asList(definitions); } diff --git a/src/main/java/com/ringlesoft/visualenv/services/EnvFileService.java b/src/main/java/com/ringlesoft/visualenv/services/EnvFileService.java index 36fc0ad..a6891bd 100644 --- a/src/main/java/com/ringlesoft/visualenv/services/EnvFileService.java +++ b/src/main/java/com/ringlesoft/visualenv/services/EnvFileService.java @@ -118,9 +118,6 @@ public boolean updateEnvVariable(String name, String value) { } try { - if (value.contains(" ")) { - value = "\"" + value + "\""; - } // Use EnvFileManager to update the variable EnvFileManager.setEnvVariable(project, activeEnvFile, name, value); // Update cache @@ -206,11 +203,11 @@ public boolean createEnvFromTemplate(VirtualFile templateFile) { EnvVariableDefinition definition = variableRegistry.getVariableDefinition(key); if (definition != null && definition.isSecret()) { // Generate a random string for secret values - value = generateRandomString(32); + value = generateRandomString(); } else if (value.isEmpty() || value.equals("null") || (key.toLowerCase().contains("key") && !key.toLowerCase().contains("keyboard"))) { // Heuristic: if it has "key" in the name but no value, randomize it - value = generateRandomString(32); + value = generateRandomString(); } newContent.append(key).append('=').append(value).append('\n'); @@ -276,15 +273,14 @@ public VirtualFile findTemplateFile() { /** * Generate a random string for use as a secret key * - * @param length Length of the string * @return Random string */ - private String generateRandomString(int length) { + private String generateRandomString() { String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()"; StringBuilder result = new StringBuilder(); Random random = new Random(); - for (int i = 0; i < length; i++) { + for (int i = 0; i < 32; i++) { result.append(characters.charAt(random.nextInt(characters.length()))); } @@ -358,8 +354,7 @@ public String executeArtisanCommand(String command) { } CommandRunner commandRunner = new CommandRunner(project); - String output = commandRunner.runCommandWithOutput(command); - return output; + return commandRunner.runCommandWithOutput(command); } catch (Exception e) { LOG.error("Error executing Artisan command", e); return "Error: " + e.getMessage(); @@ -601,4 +596,43 @@ private void refreshVariables() { public String getLastUpdatedVariable() { return lastUpdatedVariable; } + + /** + * Create a new environment variable to the currently active file + * @param key Name of the variable + * @param value Value of the variable + */ + public boolean addVariable(String key, String value) { + try { + key = key.trim() + .replaceAll("\\s+", "_") // Replace spaces with underscores + .replaceAll("[^a-zA-Z0-9_]", "_") // Remove non-alphanumeric characters + .toUpperCase(); + EnvFileManager.setEnvVariable(project, activeEnvFile, key, value); + return true; + } catch (Exception e) { + return false; + } + } + + public boolean deleteEnvVariable(String variableName) { + try { + EnvFileManager.removeEnvVariable(project, activeEnvFile, variableName); + return true; + } catch (Exception e) { + return false; + } + } + + public boolean renameVariable(String variableName, String newName) { + try { + newName = newName.trim() + .replaceAll("\\s+", "_") // Replace spaces with underscores + .replaceAll("[^a-zA-Z0-9_]", "_") // Remove non-alphanumeric characters + .toUpperCase(); + return EnvFileManager.renameEnvVariable(project, activeEnvFile, variableName, newName); + } catch (Exception e) { + return false; + } + } } diff --git a/src/main/java/com/ringlesoft/visualenv/services/ProjectService.java b/src/main/java/com/ringlesoft/visualenv/services/ProjectService.java index 761dfbc..68bb86b 100644 --- a/src/main/java/com/ringlesoft/visualenv/services/ProjectService.java +++ b/src/main/java/com/ringlesoft/visualenv/services/ProjectService.java @@ -104,10 +104,6 @@ private void scanAndProcessEnvFiles(@NotNull Project project, EnvProfile profile foundFiles.add(envFile); } } - - if (foundFiles.isEmpty()) { - // TODO show - } } public CommandRunner getCommandRunner() { diff --git a/src/main/java/com/ringlesoft/visualenv/toolWindow/CliActionsTab.java b/src/main/java/com/ringlesoft/visualenv/toolWindow/CliActionsTab.java index 9525863..e595c85 100644 --- a/src/main/java/com/ringlesoft/visualenv/toolWindow/CliActionsTab.java +++ b/src/main/java/com/ringlesoft/visualenv/toolWindow/CliActionsTab.java @@ -20,7 +20,7 @@ public class CliActionsTab extends JPanel { private final EnvFileService envService; private final EnvProfile profile; private JTextArea resultArea; // Added field for displaying results - + /** * Create a new Artisan tab * @@ -147,8 +147,7 @@ private void addProfileCommands(JPanel panel) { */ private void executeCliAction(CliActionDefinition action) { String command = action.getCommand(); - String result = ""; - + if (action.isRequiresUserInput()) { // Get parameters List parameters = action.getParameters(); @@ -173,7 +172,7 @@ private void executeCliAction(CliActionDefinition action) { } // Execute the command - result = envService.executeArtisanCommand(command); + String result = envService.executeArtisanCommand(command); displayCommandResult(result, action.getName() + " Result"); } diff --git a/src/main/java/com/ringlesoft/visualenv/toolWindow/EnvEditorTab.java b/src/main/java/com/ringlesoft/visualenv/toolWindow/EnvEditorTab.java index 79a5efc..6914ab8 100644 --- a/src/main/java/com/ringlesoft/visualenv/toolWindow/EnvEditorTab.java +++ b/src/main/java/com/ringlesoft/visualenv/toolWindow/EnvEditorTab.java @@ -41,7 +41,7 @@ public class EnvEditorTab extends JPanel implements AutoCloseable { private final Map groupPanels = new HashMap<>(); private VirtualFile selectedEnvFile; private final Map fileBasenameToPath = new HashMap<>(); - private FileSaveListener fileSaveListener; + private final FileSaveListener fileSaveListener; /** * Create a new Environment editor tab @@ -367,7 +367,7 @@ public void reloadCurrentEnvFile() { } @Override - public void close() throws Exception { + public void close() { fileSaveListener.dispose(); } diff --git a/src/main/java/com/ringlesoft/visualenv/toolWindow/EnvGroupPanel.java b/src/main/java/com/ringlesoft/visualenv/toolWindow/EnvGroupPanel.java index 3ce7996..cd07e8a 100644 --- a/src/main/java/com/ringlesoft/visualenv/toolWindow/EnvGroupPanel.java +++ b/src/main/java/com/ringlesoft/visualenv/toolWindow/EnvGroupPanel.java @@ -1,6 +1,8 @@ package com.ringlesoft.visualenv.toolWindow; +import com.intellij.icons.AllIcons; import com.intellij.openapi.ui.ComboBox; +import com.intellij.util.ui.JBUI; import com.ringlesoft.visualenv.model.EnvVariable; import com.ringlesoft.visualenv.model.EnvVariableDefinition; import com.ringlesoft.visualenv.services.EnvFileService; @@ -12,7 +14,10 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import java.awt.*; +import java.awt.datatransfer.StringSelection; import java.awt.event.ItemEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -106,14 +111,63 @@ private JPanel createControlForVariable(EnvVariable variable) { JPanel panel = new JPanel(new BorderLayout(10, 0)); panel.setAlignmentX(Component.LEFT_ALIGNMENT); panel.setBorder(VisualEnvTheme.VARIABLE_PANEL_BORDER); - + // Create variable name label JLabel nameLabel = new JLabel(variable.getName() + ":"); nameLabel.setFont(nameLabel.getFont().deriveFont(Font.PLAIN, nameLabel.getFont().getSize() - 1)); nameLabel.setBorder(VisualEnvTheme.VARIABLE_NAME_BORDER); nameLabel.setToolTipText(getDescriptionForVariable(variable)); + + // Add right-click context menu + JPopupMenu contextMenu = new JPopupMenu(); + + JMenuItem copyItem = new JMenuItem("Copy Variable Name"); + copyItem.setIcon(AllIcons.Actions.Copy); + copyItem.setHorizontalAlignment(SwingConstants.LEFT); + copyItem.setPreferredSize(new Dimension(180, 30)); + copyItem.addActionListener(e -> { + Toolkit.getDefaultToolkit().getSystemClipboard().setContents( + new StringSelection(variable.getName()), null); + statusUpdater.accept("Variable name copied to clipboard"); + }); + contextMenu.add(copyItem); + + JMenuItem renameItem = new JMenuItem("Rename Variable"); + renameItem.setIcon(AllIcons.Actions.Edit); + renameItem.setHorizontalAlignment(SwingConstants.LEFT); + renameItem.setPreferredSize(new Dimension(180, 30)); + renameItem.addActionListener(e -> showRenameDialog(variable)); + contextMenu.add(renameItem); + + JMenuItem deleteItem = new JMenuItem("Delete Variable"); + deleteItem.setIcon(AllIcons.Actions.GC); + deleteItem.setHorizontalAlignment(SwingConstants.LEFT); + deleteItem.setPreferredSize(new Dimension(180, 30)); + deleteItem.addActionListener(e -> { + if(envFileService.deleteEnvVariable(variable.getName())){ + // Remove the variable from the list + } else { + // failed + } + }); + + // Hover Effect + addHoverEffect(copyItem); + addHoverEffect(renameItem); + addHoverEffect(deleteItem); + + contextMenu.add(deleteItem); + + nameLabel.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if (SwingUtilities.isRightMouseButton(e)) { + contextMenu.show(nameLabel, e.getX(), e.getY()); + } + } + }); panel.add(nameLabel, BorderLayout.WEST); - + // Create variable value component based on type Component valueComponent = createControlByType(variable); panel.add(valueComponent, BorderLayout.CENTER); @@ -269,7 +323,7 @@ private String getDescriptionForVariable(EnvVariable variable) { private void updateVariable(String name, String value) { boolean success = envFileService.updateEnvVariable(name, value); if (success) { - statusUpdater.accept("Updated " + name + " to " + (isSecretVariable(name) ? "*****" : value)); + statusUpdater.accept("Updated " + name + " to " + (isSecretVariable(name) ? "*".repeat(name.length()) : value)); } else { statusUpdater.accept("Failed to update " + name); } @@ -424,4 +478,98 @@ private void updatePanelVisibility(boolean visible) { variablesPanel.setVisible(visible && expanded); setVisible(visible && expanded); } + + private void showRenameDialog(EnvVariable variable) { + // Create a modal dialog for adding a new variable + JDialog renameDialog = new JDialog(); + renameDialog.setTitle("Rename " + variable.getName()); + renameDialog.setModal(true); + renameDialog.setLayout(new BorderLayout()); + + // Main form panel with proper spacing + JPanel formPanel = new JPanel(new GridBagLayout()); + formPanel.setBorder(JBUI.Borders.empty(20)); + GridBagConstraints gbc = new GridBagConstraints(); + + // Variable Name section + gbc.gridx = 0; gbc.gridy = 0; + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = JBUI.insetsBottom(5); + JLabel keyLabel = new JLabel("New Name:"); + formPanel.add(keyLabel, gbc); + + gbc.gridy = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + gbc.insets = JBUI.insetsBottom(15); + JTextField keyField = new JTextField(20); + keyField.setText(variable.getName()); + formPanel.add(keyField, gbc); + + // Message pane for notifications + gbc.gridy = 4; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.insets = JBUI.emptyInsets(); + JLabel messagePane = new JLabel(); + messagePane.setText(""); + messagePane.setForeground(UIManager.getColor("Label.disabledForeground")); + messagePane.setFont(messagePane.getFont().deriveFont(Font.ITALIC, messagePane.getFont().getSize() - 1f)); + formPanel.add(messagePane, gbc); + + // Button panel + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + buttonPanel.setBorder(JBUI.Borders.empty(10, 15, 15, 15)); + + JButton cancelButton = new JButton("Cancel"); + JButton saveButton = new JButton("Save"); + + cancelButton.addActionListener(event -> renameDialog.dispose()); + saveButton.addActionListener(event -> { + String key = keyField.getText(); + messagePane.setText(""); + if (key.isEmpty()) { + return; + } + if (envFileService.renameVariable(variable.getName(), key)) { + renameDialog.dispose(); + } else { + messagePane.setText("Failed to rename variable"); + } + }); + + buttonPanel.add(cancelButton); + buttonPanel.add(saveButton); + renameDialog.add(formPanel, BorderLayout.CENTER); + renameDialog.add(buttonPanel, BorderLayout.SOUTH); + + renameDialog.pack(); + renameDialog.setSize(350, renameDialog.getHeight()); // Slightly wider for better proportions + renameDialog.setLocationRelativeTo(null); // Center on screen + + // Focus the key field + keyField.requestFocus(); + keyField.selectAll(); + renameDialog.setVisible(true); + } + + + private void addHoverEffect(JMenuItem item) { + Color originalBg = item.getBackground(); + Color hoverBg = VisualEnvTheme.BACKGROUND_HIGHLIGHT; + + item.addMouseListener(new MouseAdapter() { + @Override + public void mouseEntered(MouseEvent e) { + item.setBackground(hoverBg); + item.setOpaque(true); + } + + @Override + public void mouseExited(MouseEvent e) { + item.setBackground(originalBg); + item.setOpaque(false); + } + }); + } + } diff --git a/src/main/java/com/ringlesoft/visualenv/toolWindow/VisualEnvToolWindowFactory.java b/src/main/java/com/ringlesoft/visualenv/toolWindow/VisualEnvToolWindowFactory.java index 7ebbf49..5579f2c 100644 --- a/src/main/java/com/ringlesoft/visualenv/toolWindow/VisualEnvToolWindowFactory.java +++ b/src/main/java/com/ringlesoft/visualenv/toolWindow/VisualEnvToolWindowFactory.java @@ -32,6 +32,7 @@ public class VisualEnvToolWindowFactory implements ToolWindowFactory, AutoClosea private JTabbedPane tabbedPane; private JPanel mainPanel; private JPanel controlPanel; + private JPanel bottomPanel; @Override public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { @@ -40,28 +41,36 @@ public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindo this.projectService = project.getService(ProjectService.class); - mainPanel = new JPanel(new BorderLayout()); + mainPanel.setMinimumSize(new Dimension(500, 500)); controlPanel = createControlPanel(); - + contentPanel = new JPanel(new BorderLayout()); tabbedPane = new JBTabbedPane(); - + // Create Environment Variables tab JPanel envPanel = new EnvEditorTab(project, envService, projectService); tabbedPane.addTab("Environment Variables", envPanel); - + // Add Artisan tab if supported if (envService.getActiveProfile().supportsArtisanCommands()) { JPanel artisanPanel = createCliActionsPanel(); tabbedPane.addTab("CLI Commands", artisanPanel); } - + contentPanel.add(tabbedPane, BorderLayout.CENTER); - + mainPanel.add(controlPanel, BorderLayout.NORTH); mainPanel.add(contentPanel, BorderLayout.CENTER); - + + + // Bottom actions + bottomPanel = new JPanel(new BorderLayout()); + bottomPanel.setBorder(JBUI.Borders.empty(5)); + mainPanel.add(bottomPanel, BorderLayout.SOUTH); + + addAddVariableButton(); + ContentFactory contentFactory = ContentFactory.getInstance(); Content content = contentFactory.createContent(mainPanel, "", false); toolWindow.getContentManager().addContent(content); @@ -75,9 +84,9 @@ private JPanel createControlPanel() { // set minimum width to 500 panel.setMinimumSize(new Dimension(500, 0)); panel.setBorder(JBUI.Borders.empty(5)); - + JPanel topPanel = new JPanel(new GridLayout(1, 1, 0, 5)); - + // First row - project type and profile selector JPanel projectTypePanel = new JPanel(new BorderLayout()); @@ -87,7 +96,7 @@ private JPanel createControlPanel() { projectTypeLabel.setForeground(VisualEnvTheme.TEXT_SECONDARY); projectTypeLabel.setBorder(JBUI.Borders.emptyRight(10)); projectTypePanel.add(projectTypeLabel, BorderLayout.WEST); - + topPanel.add(projectTypePanel); panel.add(topPanel, BorderLayout.NORTH); @@ -98,7 +107,7 @@ private JPanel createControlPanel() { * Create the CLI commands panel for the active profile */ private JPanel createCliActionsPanel() { - return new CliActionsTab( envService, envService.getActiveProfile()); + return new CliActionsTab(envService, envService.getActiveProfile()); } /** @@ -108,36 +117,28 @@ private void updateUI() { // Update project type label String projectType = projectService.getProjectType(); projectTypeLabel.setText("Project type: " + projectType); - + // Update button visibility based on profile Container buttonPanel = controlPanel; - + // Find action buttons in the control panel (assuming they're in a box layout or similar) for (Component component : buttonPanel.getComponents()) { - if (component instanceof JButton) { - JButton button = (JButton) component; + if (component instanceof JButton button) { if (button.getText().equals("Create from Example")) { // Only show if profile supports template files button.setVisible(envService.getActiveProfile().supportsTemplateFiles()); } } } - + // Update tabs while (tabbedPane.getTabCount() > 0) { tabbedPane.remove(0); } - + // Create Environment Variables tab JPanel envPanel = new EnvEditorTab(project, envService, projectService); tabbedPane.addTab("Environment Variables", envPanel); - - // Add CLI Commands tab if supported - // This tab is currently Disabled -// if (envService.getActiveProfile().supportsArtisanCommands()) { -// JPanel artisanPanel = createCliActionsPanel(); -// tabbedPane.addTab("CLI Commands", artisanPanel); -// } // Refresh UI mainPanel.revalidate(); @@ -152,38 +153,101 @@ public void addAddVariableButton() { JButton addButton = new JButton("+ Add Variable"); addButton.setAlignmentX(Component.LEFT_ALIGNMENT); addButton.addActionListener(e -> { - // Create a new panel with text fields for key and value - JPanel newVarPanel = new JPanel(new BorderLayout(10, 0)); - newVarPanel.setAlignmentX(Component.LEFT_ALIGNMENT); - newVarPanel.setBorder(JBUI.Borders.emptyBottom(5)); + // Create a modal dialog for adding a new variable + JDialog dialog = new JDialog(); + dialog.setTitle("Add New Variable to " + envService.getActiveEnvFile().getName()); + dialog.setModal(true); + dialog.setLayout(new BorderLayout()); + + // Main form panel with proper spacing + JPanel formPanel = new JPanel(new GridBagLayout()); + formPanel.setBorder(JBUI.Borders.empty(20)); + GridBagConstraints gbc = new GridBagConstraints(); + + // Variable Name section + gbc.gridx = 0; gbc.gridy = 0; + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = JBUI.insetsBottom(5); + JLabel keyLabel = new JLabel("Variable Name:"); + formPanel.add(keyLabel, gbc); - JTextField keyField = new JTextField("NEW_VARIABLE"); - keyField.setBorder(JBUI.Borders.emptyRight(10)); - JTextField valueField = new JTextField(""); + gbc.gridy = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + gbc.insets = JBUI.insetsBottom(15); + JTextField keyField = new JTextField(20); + formPanel.add(keyField, gbc); - newVarPanel.add(keyField, BorderLayout.WEST); - newVarPanel.add(valueField, BorderLayout.CENTER); + // Value section + gbc.gridy = 2; + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0; + gbc.insets = JBUI.insetsBottom(5); + JLabel valueLabel = new JLabel("Value:"); + formPanel.add(valueLabel, gbc); - // Insert it before the add button - contentPanel.add(newVarPanel, contentPanel.getComponentCount() - 1); + gbc.gridy = 3; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + gbc.insets = JBUI.insetsBottom(15); + JTextField valueField = new JTextField(20); + formPanel.add(valueField, gbc); + + // Message pane for notifications + gbc.gridy = 4; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.insets = JBUI.emptyInsets(); + JLabel messagePane = new JLabel(); + messagePane.setText(""); + messagePane.setForeground(UIManager.getColor("Label.disabledForeground")); + messagePane.setFont(messagePane.getFont().deriveFont(Font.ITALIC, messagePane.getFont().getSize() - 1f)); + formPanel.add(messagePane, gbc); + + // Button panel + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + buttonPanel.setBorder(JBUI.Borders.empty(10, 15, 15, 15)); + + JButton cancelButton = new JButton("Cancel"); + JButton saveButton = new JButton("Add"); + + cancelButton.addActionListener(event -> dialog.dispose()); + saveButton.addActionListener(event -> { + String key = keyField.getText(); + String value = valueField.getText(); + messagePane.setText(""); + if (key.isEmpty() || value.isEmpty()) { + return; + } + if (envService.addVariable(key, value)) { + dialog.dispose(); + } else { + messagePane.setText("Failed to add variable"); + } + }); + + buttonPanel.add(cancelButton); + buttonPanel.add(saveButton); + + dialog.add(formPanel, BorderLayout.CENTER); + dialog.add(buttonPanel, BorderLayout.SOUTH); + + dialog.pack(); + dialog.setSize(350, dialog.getHeight()); // Slightly wider for better proportions + dialog.setLocationRelativeTo(null); // Center on screen // Focus the key field keyField.requestFocus(); keyField.selectAll(); - // Refresh UI - contentPanel.revalidate(); - contentPanel.repaint(); + dialog.setVisible(true); }); - - contentPanel.add(addButton); + bottomPanel.add(addButton); } - private JPanel getContentPanel() { return contentPanel; } - + @Override public boolean shouldBeAvailable(@NotNull Project project) { // Always make the tool window available diff --git a/src/main/java/com/ringlesoft/visualenv/utils/CommandRunner.java b/src/main/java/com/ringlesoft/visualenv/utils/CommandRunner.java index ea60191..6cd7168 100644 --- a/src/main/java/com/ringlesoft/visualenv/utils/CommandRunner.java +++ b/src/main/java/com/ringlesoft/visualenv/utils/CommandRunner.java @@ -34,8 +34,6 @@ public void runCommand(String command, ProcessListener processListener) { String[] args = new String[parts.length - 2]; System.arraycopy(parts, 2, args, 0, args.length); runCommandWithOutput(parts[0], parts[1], args, (processListener != null) ? processListener : outputHandler()); - } else { - } } @@ -156,7 +154,7 @@ public void processTerminated(@NotNull ProcessEvent event) { Notifications.Bus.notify(new Notification( "Visual Env Notification Group", "Error", - "Failed to execute command: " + output.toString(), + "Failed to execute command: " + output, NotificationType.ERROR ), project); } diff --git a/src/main/java/com/ringlesoft/visualenv/utils/EnvFileManager.java b/src/main/java/com/ringlesoft/visualenv/utils/EnvFileManager.java index 651e12b..9fecfc1 100644 --- a/src/main/java/com/ringlesoft/visualenv/utils/EnvFileManager.java +++ b/src/main/java/com/ringlesoft/visualenv/utils/EnvFileManager.java @@ -75,6 +75,7 @@ public static void removeEnvVariable(Project project, VirtualFile envFile, Strin int end = matcher.end(); document.deleteString(start, end); } + FileDocumentManager.getInstance().saveDocument(document); }); } @@ -458,6 +459,7 @@ public static List validateEnvFile(VirtualFile envFile) { */ public static boolean renameEnvVariable(Project project, VirtualFile envFile, String oldKey, String newKey) { + // Convert to uppercase if (oldKey.equals(newKey)) return true; // No change needed // Get the current value @@ -803,6 +805,16 @@ public static boolean restoreBackup(Project project, VirtualFile directory, Stri * @param value Env variable value */ private static void setEnvVariableInternal(@NotNull Document document, String key, String value) { + + value = value.replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("\r", "\\r"); + // Quote if contains spaces, special chars, or starts with quote + if (value.matches(".*[\\s#'`${}()].*") || value.startsWith("\"")) { + value = "\"" + value + "\""; + } + try { String documentText = document.getText(); String newLine = key + "=" + value; @@ -815,6 +827,13 @@ private static void setEnvVariableInternal(@NotNull Document document, String ke int end = matcher.end(); document.replaceString(start, end, newLine); } else { + key = key.trim() + .replaceAll("\\s+", "_") // spaces to underscores + .replaceAll("[^a-zA-Z0-9_]", "_") // special chars to underscores + .replaceAll("_{2,}", "_") // multiple underscores to single + .replaceAll("^[0-9_]+", "") // remove leading numbers/underscores + .toUpperCase(); + newLine = key + "=" + value; if (!documentText.isEmpty() && !documentText.endsWith("\n")) { newLine = "\n" + newLine; } diff --git a/src/main/java/com/ringlesoft/visualenv/utils/ProjectDetector.java b/src/main/java/com/ringlesoft/visualenv/utils/ProjectDetector.java index 60ef4a3..426aca5 100644 --- a/src/main/java/com/ringlesoft/visualenv/utils/ProjectDetector.java +++ b/src/main/java/com/ringlesoft/visualenv/utils/ProjectDetector.java @@ -17,7 +17,7 @@ public class ProjectDetector { public static boolean isLaravelProject(Project project) { if (project == null) return false; - VirtualFile baseDir = project.getBaseDir(); + VirtualFile baseDir = project.getProjectFile(); if (baseDir == null) return false; // Check for artisan file (Laravel's command-line tool) @@ -45,7 +45,7 @@ public static boolean isLaravelProject(Project project) { public static boolean isNodeJSProject(Project project) { if (project == null) return false; - VirtualFile baseDir = project.getBaseDir(); + VirtualFile baseDir = project.getProjectFile(); if (baseDir == null) return false; // Check for package.json @@ -72,7 +72,7 @@ public static boolean isNodeJSProject(Project project) { public static boolean isDjangoProject(Project project) { if (project == null) return false; - VirtualFile baseDir = project.getBaseDir(); + VirtualFile baseDir = project.getProjectFile(); if (baseDir == null) return false; // Check for manage.py diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index d29e34a..0115c81 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -2,27 +2,18 @@ com.ringlesoft.visualenv Visual Env - 1.0.0 + 1.0.1 RingleSoft Manage and interact with environment variables in your projects with a user-friendly interface.

- -

Key Features:

-
    -
  • Environment Variable Management - View, edit, and organize environment variables from .env files
  • -
  • Framework Detection - Automatically detects Laravel projects and adapts to project-specific settings
  • -
  • Type-Aware Interface - Specialized UI controls for different variable types (toggles for booleans, dropdowns for enums)
  • -
  • CLI Command Integration - Run framework-specific commands directly from the IDE
  • -
  • Smart Organization - Group variables by category for better navigation
  • -
  • Template Support - Create new .env files from .env.example templates with one click
  • -
- -

- Framework Support:

-
    -
  • Laravel - Full support with predefined variables and artisan command integration
  • -
  • More frameworks coming soon!
  • -
+

Streamline environment variable management across your projects with an intuitive interface that adapts to your development framework.

+

Core Features:

+
    +
  • Smart .env Management - Edit variables with type-aware controls (toggles, dropdowns, validation)
  • +
  • Framework Integration - Auto-detects Laravel projects with predefined variables and artisan commands
  • +
  • Quick Setup - Generate .env files from templates and organize variables by category
  • +
  • CLI Integration - Execute framework commands (php artisan key:generate) directly from the IDE
  • +
+

Supported Frameworks: Laravel, Node.js, Django, Generic (with more coming soon)

]]>
com.intellij.modules.platform diff --git a/src/main/resources/icons/pluginicon.svg b/src/main/resources/icons/pluginicon.svg index 91dd293..3843309 100644 --- a/src/main/resources/icons/pluginicon.svg +++ b/src/main/resources/icons/pluginicon.svg @@ -1,5 +1,5 @@ + viewBox="0 0 512 512" xml:space="preserve" fill="#000000">