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]