From 3de9e535ece50ff9b486ca0dd6572eb0d90a883f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 24 Jan 2026 07:51:22 +0000
Subject: [PATCH] Address code review feedback: convert absolute to relative
paths for portability
Co-authored-by: Jack251970 <53996452+Jack251970@users.noreply.github.com>
---
.../Environments/AbstractPluginEnvironment.cs | 8 +-
Flow.Launcher.Infrastructure/Constant.cs | 41 +++++++++
Flow.Launcher.Test/PathResolutionTest.cs | 84 +++++++++++++++++++
.../SettingsPaneGeneralViewModel.cs | 6 +-
4 files changed, 134 insertions(+), 5 deletions(-)
diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs
index c37030812..3a9732aa3 100644
--- a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs
@@ -42,7 +42,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
}
///
- /// Resolves the configured executable path to an absolute path.
+ /// Resolves the configured plugin settings file path to an absolute path.
/// Supports both absolute paths and relative paths (relative to ProgramDirectory).
///
private string ResolvedPluginsSettingsFilePath => Constant.ResolveAbsolutePath(PluginsSettingsFilePath);
@@ -74,7 +74,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
if (!string.IsNullOrEmpty(selectedFile))
{
- PluginsSettingsFilePath = selectedFile;
+ // Convert to relative path if within ProgramDirectory for portability
+ PluginsSettingsFilePath = Constant.ConvertToRelativePathIfPossible(selectedFile);
}
// Nothing selected because user pressed cancel from the file dialog window
else
@@ -98,7 +99,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
if (!string.IsNullOrEmpty(selectedFile))
{
- PluginsSettingsFilePath = selectedFile;
+ // Convert to relative path if within ProgramDirectory for portability
+ PluginsSettingsFilePath = Constant.ConvertToRelativePathIfPossible(selectedFile);
}
else
{
diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs
index 57175930f..fd1538646 100644
--- a/Flow.Launcher.Infrastructure/Constant.cs
+++ b/Flow.Launcher.Infrastructure/Constant.cs
@@ -76,5 +76,46 @@ namespace Flow.Launcher.Infrastructure
// Resolve relative to ProgramDirectory
return Path.GetFullPath(Path.Combine(ProgramDirectory, path));
}
+
+ ///
+ /// Converts an absolute path to a relative path if it's within ProgramDirectory.
+ /// This enables portability by storing paths relative to the program directory when possible.
+ ///
+ /// The absolute path to convert
+ /// A relative path if the path is within ProgramDirectory, otherwise the original absolute path
+ public static string ConvertToRelativePathIfPossible(string absolutePath)
+ {
+ if (string.IsNullOrEmpty(absolutePath))
+ return absolutePath;
+
+ if (!Path.IsPathRooted(absolutePath))
+ return absolutePath;
+
+ try
+ {
+ // Get the full absolute paths for comparison
+ var fullAbsolutePath = Path.GetFullPath(absolutePath);
+ var fullProgramDir = Path.GetFullPath(ProgramDirectory);
+
+ // Check if the absolute path is within ProgramDirectory
+ if (fullAbsolutePath.StartsWith(fullProgramDir, StringComparison.OrdinalIgnoreCase))
+ {
+ // Convert to relative path
+ var relativePath = Path.GetRelativePath(fullProgramDir, fullAbsolutePath);
+
+ // Prefix with .\ for clarity
+ if (!relativePath.StartsWith("."))
+ relativePath = ".\\" + relativePath;
+
+ return relativePath;
+ }
+ }
+ catch
+ {
+ // If conversion fails, return the original path
+ }
+
+ return absolutePath;
+ }
}
}
diff --git a/Flow.Launcher.Test/PathResolutionTest.cs b/Flow.Launcher.Test/PathResolutionTest.cs
index 4e03a2d6f..3888b41a4 100644
--- a/Flow.Launcher.Test/PathResolutionTest.cs
+++ b/Flow.Launcher.Test/PathResolutionTest.cs
@@ -100,5 +100,89 @@ namespace Flow.Launcher.Test
// Assert
Assert.Equal(uncPath, result);
}
+
+ [Fact]
+ public void ConvertToRelativePathIfPossible_WithPathInProgramDirectory_ReturnsRelativePath()
+ {
+ // Arrange
+ var absolutePath = Path.Combine(Constant.ProgramDirectory, "runtimes", "python", "pythonw.exe");
+
+ // Act
+ var result = Constant.ConvertToRelativePathIfPossible(absolutePath);
+
+ // Assert
+ Assert.True(result.StartsWith(".\\"), "Result should start with .\\");
+ Assert.Contains("runtimes", result);
+ Assert.Contains("python", result);
+ }
+
+ [Fact]
+ public void ConvertToRelativePathIfPossible_WithPathOutsideProgramDirectory_ReturnsAbsolutePath()
+ {
+ // Arrange
+ var absolutePath = @"C:\Python\python.exe";
+
+ // Act
+ var result = Constant.ConvertToRelativePathIfPossible(absolutePath);
+
+ // Assert
+ Assert.Equal(absolutePath, result);
+ }
+
+ [Fact]
+ public void ConvertToRelativePathIfPossible_WithRelativePath_ReturnsOriginalPath()
+ {
+ // Arrange
+ var relativePath = @".\runtimes\python\pythonw.exe";
+
+ // Act
+ var result = Constant.ConvertToRelativePathIfPossible(relativePath);
+
+ // Assert
+ Assert.Equal(relativePath, result);
+ }
+
+ [Fact]
+ public void ConvertToRelativePathIfPossible_WithNullPath_ReturnsNull()
+ {
+ // Arrange
+ string nullPath = null;
+
+ // Act
+ var result = Constant.ConvertToRelativePathIfPossible(nullPath);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void ConvertToRelativePathIfPossible_WithEmptyPath_ReturnsEmpty()
+ {
+ // Arrange
+ var emptyPath = string.Empty;
+
+ // Act
+ var result = Constant.ConvertToRelativePathIfPossible(emptyPath);
+
+ // Assert
+ Assert.Equal(string.Empty, result);
+ }
+
+ [Fact]
+ public void RoundTripTest_RelativePathResolutionAndConversion()
+ {
+ // Arrange
+ var originalRelative = @".\runtimes\python\pythonw.exe";
+
+ // Act - Resolve to absolute
+ var absolute = Constant.ResolveAbsolutePath(originalRelative);
+ // Convert back to relative
+ var backToRelative = Constant.ConvertToRelativePathIfPossible(absolute);
+
+ // Assert
+ Assert.True(Path.IsPathRooted(absolute), "Resolved path should be absolute");
+ Assert.True(backToRelative.StartsWith(".\\"), "Converted path should be relative");
+ Assert.Contains("runtimes\\python\\pythonw.exe", backToRelative);
+ }
}
}
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
index aa78849ba..eb00ae3ea 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
@@ -377,7 +377,8 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
);
if (!string.IsNullOrEmpty(selectedFile))
- Settings.PluginSettings.PythonExecutablePath = selectedFile;
+ // Convert to relative path if within ProgramDirectory for portability
+ Settings.PluginSettings.PythonExecutablePath = Constant.ConvertToRelativePathIfPossible(selectedFile);
}
[RelayCommand]
@@ -389,7 +390,8 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
);
if (!string.IsNullOrEmpty(selectedFile))
- Settings.PluginSettings.NodeExecutablePath = selectedFile;
+ // Convert to relative path if within ProgramDirectory for portability
+ Settings.PluginSettings.NodeExecutablePath = Constant.ConvertToRelativePathIfPossible(selectedFile);
}
[RelayCommand]