Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,28 @@ namespace Microsoft.CmdPal.Ext.WindowsTerminal.Commands;

internal sealed partial class LaunchProfileCommand : InvokableCommand
{
private readonly string _id;
private readonly string _appUserModelId;
private readonly string _profile;
private readonly bool _openNewTab;
private readonly bool _openQuake;
private readonly AppSettingsManager _appSettingsManager;

internal LaunchProfileCommand(string id, string profile, string iconPath, bool openNewTab, bool openQuake, AppSettingsManager appSettingsManager)
internal LaunchProfileCommand(string appUserModelId, string profile, string iconPath, bool openNewTab, bool openQuake, AppSettingsManager appSettingsManager)
{
this._id = id;
this._appUserModelId = appUserModelId;
this._profile = profile;
this._openNewTab = openNewTab;
this._openQuake = openQuake;
this._appSettingsManager = appSettingsManager;

this.Name = Resources.launch_profile;
this.Icon = new IconInfo(iconPath);
this.Id = MakeId(appUserModelId, profile);
}

private void Launch(string id, string profile)
internal static string MakeId(string appUserModelId, string profileName) => $"terminal/{appUserModelId}/{profileName}";
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how this would fit with the deep-links... I assume we'd like to end up with sth like: x-cmdpal://commands/providerId/commandId and question is if / should be a reserved char or not? In theory we can treat commandId as catch-all parameter

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jiripolasek what is your recommendation here?


private void Launch(string appUserModelId, string profile)
{
IApplicationActivationManager appManager;

Expand All @@ -52,7 +55,7 @@ private void Launch(string id, string profile)
var queryArguments = TerminalHelper.GetArguments(profile, _openNewTab, _openQuake);
try
{
appManager.ActivateApplication(id, queryArguments, noFlags, out var unusedPid);
appManager.ActivateApplication(appUserModelId, queryArguments, noFlags, out var unusedPid);
}
#pragma warning disable IDE0059, CS0168
catch (Exception ex)
Expand All @@ -67,7 +70,7 @@ private void Launch(string id, string profile)

try
{
_appSettingsManager.Current.AddRecentlyUsedProfile(id, profile);
_appSettingsManager.Current.AddRecentlyUsedProfile(appUserModelId, profile);
_appSettingsManager.Save();
}
catch (Exception ex)
Expand All @@ -82,7 +85,7 @@ public override CommandResult Invoke()
{
try
{
Launch(_id, _profile);
Launch(_appUserModelId, _profile);
}
catch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;

namespace Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
Expand Down Expand Up @@ -92,11 +95,100 @@ public static TerminalProfile ParseProfile(TerminalPackage terminal, JsonElement
var hidden = (hiddenElement.ValueKind == JsonValueKind.False || hiddenElement.ValueKind == JsonValueKind.True) && hiddenElement.GetBoolean();

profileElement.TryGetProperty("guid", out var guidElement);
var guid = guidElement.ValueKind == JsonValueKind.String ? Guid.Parse(guidElement.GetString()) : null as Guid?;
Guid? guid = null;
if (guidElement.ValueKind == JsonValueKind.String)
{
var guidString = guidElement.GetString();
if (!string.IsNullOrWhiteSpace(guidString) && Guid.TryParse(guidString, out var parsedGuid))
{
guid = parsedGuid;
}
}

profileElement.TryGetProperty("icon", out var iconElement);
var icon = iconElement.ValueKind == JsonValueKind.String ? iconElement.GetString() : null;

return new TerminalProfile(terminal, name, guid, hidden, icon);
}

/// <summary>
/// Resolve a profile icon to a usable path. For built-in profiles without
/// an explicit icon, looks up the GUID-based icon in the Terminal package's
/// ProfileIcons folder. Handles ms-appx:/// URIs by mapping them to the
/// package install directory. Passes through file paths and font glyphs
/// as-is. Falls back to the terminal package logo as a last resort.
/// </summary>
public static string ResolveProfileIcon(TerminalProfile profile)
{
var icon = profile.Icon;

if (string.IsNullOrEmpty(icon))
{
// Built-in profiles don't have an icon in settings.json.
// Their icons are stored by GUID in the Terminal package.
if (profile.Identifier.HasValue)
{
var guidIcon = TryResolveGuidIcon(profile.Terminal.InstallPath, profile.Identifier.Value);
if (guidIcon is not null)
{
return guidIcon;
}
}

return profile.Terminal.LogoPath;
}

if (icon.StartsWith("ms-appx:///", StringComparison.OrdinalIgnoreCase))
{
return ResolveAppxPath(profile.Terminal.InstallPath, icon) ?? profile.Terminal.LogoPath;
}

return icon;
}

private static string? TryResolveGuidIcon(string installPath, Guid guid)
{
var profileIconsDir = Path.Combine(installPath, "ProfileIcons");
if (!Directory.Exists(profileIconsDir))
{
return null;
}

var guidStr = guid.ToString("B");
foreach (var scale in new[] { ".scale-200", ".scale-150", ".scale-100" })
{
var path = Path.Combine(profileIconsDir, $"{guidStr}{scale}.png");
if (File.Exists(path))
{
return path;
}
}

return null;
}

private static string? ResolveAppxPath(string installPath, string msAppxUri)
{
var relativePath = msAppxUri.Substring("ms-appx:///".Length).Replace('/', '\\');
var resolved = Path.Combine(installPath, relativePath);
if (File.Exists(resolved))
{
return resolved;
}

var dir = Path.GetDirectoryName(resolved);
var name = Path.GetFileNameWithoutExtension(resolved);
var ext = Path.GetExtension(resolved);

foreach (var scale in new[] { ".scale-200", ".scale-150", ".scale-100" })
{
var scaled = Path.Combine(dir!, $"{name}{scale}{ext}");
if (File.Exists(scaled))
{
return scaled;
}
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public IEnumerable<TerminalPackage> GetTerminals()
var aumid = appListEntries.Single().AppUserModelId;
var version = new Version(p.Id.Version.Major, p.Id.Version.Minor, p.Id.Version.Build, p.Id.Version.Revision);
var settingsPath = Path.Combine(localAppDataPath, "Packages", p.Id.FamilyName, "LocalState", "settings.json");
yield return new TerminalPackage(aumid, version, p.DisplayName, settingsPath, p.Logo.LocalPath);
yield return new TerminalPackage(aumid, version, p.DisplayName, settingsPath, p.Logo.LocalPath, p.InstalledPath);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,13 @@ private List<ListItem> Query()
continue;
}

result.Add(new ListItem(new LaunchProfileCommand(profile.Terminal.AppUserModelId, profile.Name, profile.Terminal.LogoPath, openNewTab, openQuake, _appSettingsManager))
var iconPath = TerminalHelper.ResolveProfileIcon(profile);

result.Add(new ListItem(new LaunchProfileCommand(profile.Terminal.AppUserModelId, profile.Name, iconPath, openNewTab, openQuake, _appSettingsManager))
{
Title = profile.Name,
Subtitle = profile.Terminal.DisplayName,
Icon = new IconInfo(iconPath),
MoreCommands = [
new CommandContextItem(new LaunchProfileAsAdminCommand(profile.Terminal.AppUserModelId, profile.Name, openNewTab, openQuake, _appSettingsManager)),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ public class TerminalPackage

public string LogoPath { get; }

public TerminalPackage(string appUserModelId, Version version, string displayName, string settingsPath, string logoPath)
public string InstallPath { get; }

public TerminalPackage(string appUserModelId, Version version, string displayName, string settingsPath, string logoPath, string installPath)
{
AppUserModelId = appUserModelId;
Version = version;
DisplayName = displayName;
SettingsPath = settingsPath;
LogoPath = logoPath;
InstallPath = installPath;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
#nullable enable

using Microsoft.CmdPal.Ext.WindowsTerminal.Commands;
using Microsoft.CmdPal.Ext.WindowsTerminal.Properties;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
Expand Down Expand Up @@ -31,4 +33,18 @@ public WindowsTerminalCommandsProvider()
}

public override ICommandItem[] TopLevelCommands() => [_terminalCommand];

public override ICommandItem? GetCommandItem(string id)
{
var items = _terminalCommand.Command is Pages.ProfilesListPage page ? page.GetItems() : [];
foreach (var item in items)
{
if (item.Command.Id == id)
{
return item;
}
}

return null;
}
}
Loading