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 @@