diff --git a/QuickShell.Core.Tests/RunGlobalQueryTests.cs b/QuickShell.Core.Tests/RunGlobalQueryTests.cs index 7ae7e340..a282d9c2 100644 --- a/QuickShell.Core.Tests/RunGlobalQueryTests.cs +++ b/QuickShell.Core.Tests/RunGlobalQueryTests.cs @@ -1,15 +1,9 @@ using QuickShell.Services; - - - + namespace QuickShell.Core.Tests; - - - + public sealed class RunGlobalQueryTests - { - [Theory] [InlineData("quickshell")] @@ -21,85 +15,56 @@ public sealed class RunGlobalQueryTests [InlineData("qs")] public void TryActivate_PluginName_EntersBrowseMode(string query) - { - Assert.True(RunGlobalQuery.TryActivate(query, rawQuery: null, out var remaining)); Assert.Equal(string.Empty, remaining); - } - - - + [Fact] public void TryActivate_PluginNameWithFilter_StripsPrefix() - { - Assert.True(RunGlobalQuery.TryActivate("quick shell api", rawQuery: null, out var remaining)); Assert.Equal("api", remaining); - } - - - + [Fact] public void TryActivate_RawQueryFallback_ActivatesWhenSearchEmpty() - { - Assert.True(RunGlobalQuery.TryActivate(string.Empty, "Quick Shell", out var remaining)); Assert.Equal(string.Empty, remaining); - } - - - + [Fact] public void TryActivate_UnrelatedQuery_DoesNotActivate() - { - Assert.False(RunGlobalQuery.TryActivate("my api project", rawQuery: null, out var remaining)); Assert.Equal("my api project", remaining); - } - - - + [Fact] public void ShouldSuppressEmptyGlobalQuery_BlocksBareGlobalSearch() - { - var context = new QueryActivationContext(HasActionKeyword: false, Search: string.Empty); Assert.True(RunGlobalQuery.ShouldSuppressEmptyGlobalQuery(context)); - } - - - + [Fact] public void ShouldSuppressEmptyGlobalQuery_AllowsActionKeywordBrowse() - { - var context = new QueryActivationContext(HasActionKeyword: true, Search: string.Empty); Assert.False(RunGlobalQuery.ShouldSuppressEmptyGlobalQuery(context)); - } - } diff --git a/QuickShell.Core.Tests/TestQuickShellServicesFactory.cs b/QuickShell.Core.Tests/TestQuickShellServicesFactory.cs index d9dcf9e0..63fe4299 100644 --- a/QuickShell.Core.Tests/TestQuickShellServicesFactory.cs +++ b/QuickShell.Core.Tests/TestQuickShellServicesFactory.cs @@ -187,5 +187,4 @@ private static (AppDataPaths appDataPaths, TerminalLaunchGlyphs glyphs, Terminal var prewarm = new TerminalCatalogPrewarm(bundle.Catalog); return (appDataPaths, glyphs, listIcons, prewarm); } - } diff --git a/QuickShell.Core.Tests/WorkspaceTrustCommandTests.cs b/QuickShell.Core.Tests/WorkspaceTrustCommandTests.cs index 9d388ab3..d79442ce 100644 --- a/QuickShell.Core.Tests/WorkspaceTrustCommandTests.cs +++ b/QuickShell.Core.Tests/WorkspaceTrustCommandTests.cs @@ -105,8 +105,7 @@ private static (QuickShellServices Services, QuickShellLifetime Lifetime, Shortc lifetime); return (services, lifetime, drafts); } - - + private static TerminalShortcut CreateWorkspace() => new() { diff --git a/QuickShell.Core/Models/TerminalShortcut.cs b/QuickShell.Core/Models/TerminalShortcut.cs index 3539314e..2573a99d 100644 --- a/QuickShell.Core/Models/TerminalShortcut.cs +++ b/QuickShell.Core/Models/TerminalShortcut.cs @@ -1,84 +1,47 @@ namespace QuickShell.Models; - - - + internal sealed class TerminalShortcut - { - public string Id { get; set; } = string.Empty; - - - + public string Name { get; set; } = string.Empty; - - - + public string? Abbreviation { get; set; } - - - + public string Directory { get; set; } = string.Empty; - - - + public string? Command { get; set; } - - - + // "default" means use extension-level default terminal setting. public string Terminal { get; set; } = "default"; - - - + // Optional Windows Terminal profile name for terminal=wt/default->wt. public string? WtProfile { get; set; } - - - + public bool RunAsAdmin { get; set; } - - - + public bool IsPinned { get; set; } - - - + // Lower number means higher in the favorites section. public int? PinOrder { get; set; } - - - + public DateTime? LastUsedUtc { get; set; } - - - + /// One or more terminal launches for this workspace. When empty on disk, synthesized from legacy fields on load. - public List Launches { get; set; } = []; - - - + /// Optional dev server URL opened in the browser when the workspace runs. public string? DevServerUrl { get; set; } - - - + /// When true, opens whenever the full workspace runs. - public bool OpenDevServerOnLaunch { get; set; } - - - + /// Optional repository URL opened from the workspace action menu. - public string? RepoUrl { get; set; } - - - + /// /// One or more GUI companion apps. When empty on disk, synthesized from the legacy /// scalar companion fields on load (see CompanionAppNormalization). @@ -103,7 +66,6 @@ internal sealed class TerminalShortcut /// for the workspace directory. Mirrored from the first entry. /// public string? CompanionAppArguments { get; set; } - } diff --git a/QuickShell.Core/Models/WorkspaceSecurityMetadata.cs b/QuickShell.Core/Models/WorkspaceSecurityMetadata.cs index 4aa06635..7f6dc3de 100644 --- a/QuickShell.Core/Models/WorkspaceSecurityMetadata.cs +++ b/QuickShell.Core/Models/WorkspaceSecurityMetadata.cs @@ -10,7 +10,6 @@ internal sealed record WorkspaceSecurityMetadata public bool IsTrusted { get; init; } = true; public long Revision { get; init; } = 1; - } internal sealed record StoredWorkspace( diff --git a/QuickShell.Core/Services/ShortcutLaunchFormJson.cs b/QuickShell.Core/Services/ShortcutLaunchFormJson.cs index e1596f14..f2077425 100644 --- a/QuickShell.Core/Services/ShortcutLaunchFormJson.cs +++ b/QuickShell.Core/Services/ShortcutLaunchFormJson.cs @@ -1,54 +1,33 @@ using QuickShell.Core.Services; namespace QuickShell.Services; - - - + internal static partial class ShortcutLaunchFormJson - { - private const string CommandColumnWidth = "2"; private const string ProfileColumnWidth = "2"; private const string AdminColumnWidth = "auto"; - - - + public sealed class LaunchRowDraft - { - public string Label { get; set; } = string.Empty; - - - + public string Command { get; set; } = string.Empty; - - - + public string LaunchTarget { get; set; } = "default"; - - - + public bool RunAsAdmin { get; set; } - - - + public bool IsEnabled { get; set; } = true; - } - - - + public static string BuildCommandRowsJson( IReadOnlyList rows, string terminalChoices, LaunchEditorText text) - { - var blocks = new List(); var tipAdmin = Escape(WorkspaceFormTooltips.RunAsAdmin); @@ -122,11 +101,8 @@ public static string BuildCommandRowsJson( blocks.Add("""{ "type": "Container", "spacing": "Medium", "items": [] }"""); return string.Join(',', blocks); - } - - - + public static string BuildCommandsSectionHeaderJson(LaunchEditorText text) => $$""" { @@ -158,25 +134,16 @@ public static string BuildCommandsSectionJson(string commandRows, string suggest """; public static string BuildLaunchRowsJson(IReadOnlyList launches, string terminalChoices) - { - if (launches.Count == 0) - { - return string.Empty; - } - - - + var blocks = new List(); for (var i = 0; i < launches.Count; i++) - { - var launch = launches[i]; var escapedLabel = Escape(launch.Label); @@ -188,9 +155,7 @@ public static string BuildLaunchRowsJson(IReadOnlyList launches, var adminValue = launch.RunAsAdmin ? "true" : "false"; var enabledValue = launch.IsEnabled ? "true" : "false"; - - - + var labelInput = $$""" { @@ -206,13 +171,9 @@ public static string BuildLaunchRowsJson(IReadOnlyList launches, } """; - - - + var commandInput = BuildCommandInputWithClear(i, literalValue: escapedCommand, removeTooltip: FormActionGlyphs.ClearCommandTooltip); - - - + var adminInput = $$""" { @@ -232,9 +193,7 @@ public static string BuildLaunchRowsJson(IReadOnlyList launches, } """; - - - + var enabledInput = $$""" { @@ -254,9 +213,7 @@ public static string BuildLaunchRowsJson(IReadOnlyList launches, } """; - - - + blocks.Add($$""" { @@ -518,25 +475,16 @@ public static string BuildLaunchRowsJson(IReadOnlyList launches, } """); - } - - - + return string.Join(',', blocks); - } - - - + public static string WrapLaunchRowsForTest(string launchRows) => $$"""{ "type": "AdaptiveCard", "version": "1.6", "body": [{{launchRows}}] }"""; - - - + private static string Escape(string? value) => (value ?? string.Empty).Replace("\\", "\\\\").Replace("\"", "\\\""); - } diff --git a/QuickShell.Core/Services/TerminalCatalog.cs b/QuickShell.Core/Services/TerminalCatalog.cs index a68aaabb..c460e7a1 100644 --- a/QuickShell.Core/Services/TerminalCatalog.cs +++ b/QuickShell.Core/Services/TerminalCatalog.cs @@ -63,8 +63,7 @@ public TerminalCatalog(IWtProfilesService profiles) { _profiles = profiles ?? throw new ArgumentNullException(nameof(profiles)); } - - + string ITerminalCatalog.EncodeLaunchTargetId(TerminalShortcut shortcut) => EncodeLaunchTargetId(shortcut); @@ -87,8 +86,7 @@ WorkspaceEntry ITerminalCatalog.ResolveLaunchEntry( IReadOnlyList orderedLaunches, int index) => ResolveLaunchEntry(entry, orderedLaunches, index); - - + public IReadOnlyList GetLaunchTargets(bool includeDefaultChoice = false) { var snapshot = EnsureCached(); diff --git a/QuickShell.Core/Services/TerminalHostIds.cs b/QuickShell.Core/Services/TerminalHostIds.cs index 1859a3d2..31e3f391 100644 --- a/QuickShell.Core/Services/TerminalHostIds.cs +++ b/QuickShell.Core/Services/TerminalHostIds.cs @@ -1,70 +1,72 @@ -namespace QuickShell.Services; - -internal static class TerminalHostIds -{ - public const string LetWindowsChoose = "system"; - - public const string WindowsTerminal = "wt"; - - public const string WindowsConsoleHost = "conhost"; - - public const string IntelligentTerminal = "it"; - - public const string DefaultProfile = "__default__"; - - public static string ResolveEffectiveApplication(string terminalApplicationId) - { - if (terminalApplicationId.Equals(LetWindowsChoose, StringComparison.OrdinalIgnoreCase)) - { - return WindowsDefaultTerminalReader.ReadApplicationId(); - } - - return terminalApplicationId; - } - - public static bool UsesWindowsTerminalProfiles(string terminalApplicationId) => - !ResolveEffectiveApplication(terminalApplicationId) - .Equals(WindowsConsoleHost, StringComparison.OrdinalIgnoreCase); - - public static bool IsWindowsTerminalProfilePrefix(string idPrefix) => - idPrefix.Equals("wt", StringComparison.OrdinalIgnoreCase) - || idPrefix.Equals("wtp", StringComparison.OrdinalIgnoreCase) - || idPrefix.Equals("wtu", StringComparison.OrdinalIgnoreCase); - - public static bool IsSupportedProfilePrefix(string idPrefix) => - IsWindowsTerminalProfilePrefix(idPrefix) - || idPrefix.Equals(IntelligentTerminal, StringComparison.OrdinalIgnoreCase); - - public static string HostExecutable(string terminalApplicationId) - { - var effective = ResolveEffectiveApplication(terminalApplicationId); - return effective.Equals(IntelligentTerminal, StringComparison.OrdinalIgnoreCase) - ? "wtai.exe" - : "wt.exe"; - } - - public static string ProfileIdPrefix(string terminalApplicationId) - { - var effective = ResolveEffectiveApplication(terminalApplicationId); - return effective.Equals(IntelligentTerminal, StringComparison.OrdinalIgnoreCase) - ? IntelligentTerminal - : WindowsTerminal; - } - - public static string SourceLabel(string terminalApplicationId) - { - if (terminalApplicationId.Equals(LetWindowsChoose, StringComparison.OrdinalIgnoreCase)) - { - return "Let Windows choose"; - } - - if (terminalApplicationId.Equals(WindowsConsoleHost, StringComparison.OrdinalIgnoreCase)) - { - return "Windows Console Host"; - } - - return ResolveEffectiveApplication(terminalApplicationId).Equals(IntelligentTerminal, StringComparison.OrdinalIgnoreCase) - ? "Intelligent Terminal" - : "Windows Terminal"; - } -} +namespace QuickShell.Services; + +internal static class TerminalHostIds +{ + public const string LetWindowsChoose = "system"; + + public const string WindowsTerminal = "wt"; + + public const string WindowsConsoleHost = "conhost"; + + public const string IntelligentTerminal = "it"; + + public const string DefaultProfile = "__default__"; + + public static string ResolveEffectiveApplication(string terminalApplicationId) + { + if (terminalApplicationId.Equals(LetWindowsChoose, StringComparison.OrdinalIgnoreCase)) + { + return WindowsDefaultTerminalReader.ReadApplicationId(); + } + + return terminalApplicationId; + } + + public static bool UsesWindowsTerminalProfiles(string terminalApplicationId) => + !ResolveEffectiveApplication(terminalApplicationId) + .Equals(WindowsConsoleHost, StringComparison.OrdinalIgnoreCase); + + public static bool IsWindowsTerminalProfilePrefix(string idPrefix) => + idPrefix.Equals("wt", StringComparison.OrdinalIgnoreCase) + || idPrefix.Equals("wtp", StringComparison.OrdinalIgnoreCase) + || idPrefix.Equals("wtu", StringComparison.OrdinalIgnoreCase); + + public static bool IsSupportedProfilePrefix(string idPrefix) => + IsWindowsTerminalProfilePrefix(idPrefix) + || idPrefix.Equals(IntelligentTerminal, StringComparison.OrdinalIgnoreCase); + + public static string HostExecutable(string terminalApplicationId) + { + var effective = ResolveEffectiveApplication(terminalApplicationId); + + return effective.Equals(IntelligentTerminal, StringComparison.OrdinalIgnoreCase) + ? "wtai.exe" + : "wt.exe"; + } + + public static string ProfileIdPrefix(string terminalApplicationId) + { + var effective = ResolveEffectiveApplication(terminalApplicationId); + + return effective.Equals(IntelligentTerminal, StringComparison.OrdinalIgnoreCase) + ? IntelligentTerminal + : WindowsTerminal; + } + + public static string SourceLabel(string terminalApplicationId) + { + if (terminalApplicationId.Equals(LetWindowsChoose, StringComparison.OrdinalIgnoreCase)) + { + return "Let Windows choose"; + } + + if (terminalApplicationId.Equals(WindowsConsoleHost, StringComparison.OrdinalIgnoreCase)) + { + return "Windows Console Host"; + } + + return ResolveEffectiveApplication(terminalApplicationId).Equals(IntelligentTerminal, StringComparison.OrdinalIgnoreCase) + ? "Intelligent Terminal" + : "Windows Terminal"; + } +} diff --git a/QuickShell.Core/Services/WorkspaceRepositorySnapshot.cs b/QuickShell.Core/Services/WorkspaceRepositorySnapshot.cs index 40517cd6..4aa13ebc 100644 --- a/QuickShell.Core/Services/WorkspaceRepositorySnapshot.cs +++ b/QuickShell.Core/Services/WorkspaceRepositorySnapshot.cs @@ -99,6 +99,20 @@ public IEnumerable SearchTaskActions(string query, ITermina return []; } + var matches = CollectMatchingTaskActions(tokens, catalog); + if (matches.Count == 0) + { + return []; + } + + matches.Sort(CompareTaskActions); + return matches.ToArray(); + } + + private List CollectMatchingTaskActions( + IReadOnlyList tokens, + ITerminalCatalog catalog) + { List? matches = null; foreach (var shortcut in Shortcuts) { @@ -107,53 +121,58 @@ public IEnumerable SearchTaskActions(string query, ITermina continue; } - bool? requiresRepair = null; - foreach (var launch in shortcut.Launches) + AppendMatchingTaskActions(shortcut, tokens, catalog, ref matches); + } + + return matches ?? []; + } + + private static void AppendMatchingTaskActions( + TerminalShortcut shortcut, + IReadOnlyList tokens, + ITerminalCatalog catalog, + ref List? matches) + { + bool? requiresRepair = null; + foreach (var launch in shortcut.Launches) + { + if (!launch.IsEnabled) + { + continue; + } + + var score = ComputeTaskActionScore(shortcut, launch, tokens, catalog); + if (score <= 0) { - if (!launch.IsEnabled) - { - continue; - } - - var score = ComputeTaskActionScore(shortcut, launch, tokens, catalog); - if (score <= 0) - { - continue; - } - - requiresRepair ??= ShortcutHealth.WouldNeedRepair( - shortcut, - requireDirectoryExists: false); - if (requiresRepair.Value) - { - break; - } + continue; + } - matches ??= []; - matches.Add(CreateTaskAction(shortcut, launch, score)); + requiresRepair ??= ShortcutHealth.WouldNeedRepair( + shortcut, + requireDirectoryExists: false); + if (requiresRepair.Value) + { + break; } - } - if (matches is null) - { - return []; + matches ??= []; + matches.Add(CreateTaskAction(shortcut, launch, score)); } + } - matches.Sort(static (left, right) => + private static int CompareTaskActions(WorkspaceTaskAction left, WorkspaceTaskAction right) + { + var byScore = right.Score.CompareTo(left.Score); + if (byScore != 0) { - var byScore = right.Score.CompareTo(left.Score); - if (byScore != 0) - { - return byScore; - } + return byScore; + } - var byName = string.Compare( - left.Workspace.Name, - right.Workspace.Name, - StringComparison.OrdinalIgnoreCase); - return byName != 0 ? byName : left.Launch.Order.CompareTo(right.Launch.Order); - }); - return matches.ToArray(); + var byName = string.Compare( + left.Workspace.Name, + right.Workspace.Name, + StringComparison.OrdinalIgnoreCase); + return byName != 0 ? byName : left.Launch.Order.CompareTo(right.Launch.Order); } private static bool Matches(TerminalShortcut shortcut, string query, int queryStart, int queryLength) @@ -245,7 +264,6 @@ private static int ComputeTaskActionScore( ScoreToken(profileLabel, token, exact: 250, prefix: 175, contains: 90) + ScoreToken(launch.WtProfile, token, exact: 220, prefix: 160, contains: 80); - if (workspaceScore + launchScore == 0) { return 0; diff --git a/QuickShell.Run/QuickShellRunSettingsWindow.cs b/QuickShell.Run/QuickShellRunSettingsWindow.cs index 4455a447..1a2285e2 100644 --- a/QuickShell.Run/QuickShellRunSettingsWindow.cs +++ b/QuickShell.Run/QuickShellRunSettingsWindow.cs @@ -5,17 +5,11 @@ using System.Windows; using System.Windows.Controls; - - - + namespace QuickShell.Run; - - - + internal sealed class QuickShellRunSettingsWindow : Window - { - private readonly QuickShellSettingsReader _settings; private readonly IShortcutRepository _shortcuts; @@ -37,9 +31,7 @@ internal sealed class QuickShellRunSettingsWindow : Window private readonly TextBlock _statusText; private SettingsSnapshot _baseline; - - - + public QuickShellRunSettingsWindow( QuickShellSettingsReader settings, @@ -49,9 +41,7 @@ public QuickShellRunSettingsWindow( IProjectAnalysisService projectAnalysis, ITerminalCatalog catalog) - { - _settings = settings; _shortcuts = shortcuts; @@ -71,15 +61,11 @@ public QuickShellRunSettingsWindow( WindowStartupLocation = WindowStartupLocation.CenterScreen; ResizeMode = ResizeMode.NoResize; - - - + var root = new StackPanel { Margin = new Thickness(16) }; root.Children.Add(Heading("Terminal defaults")); - - - + root.Children.Add(Label("Terminal application")); _terminalAppBox = Combo(RunTerminalChoices.GetTerminalApplicationChoices(_catalog)); @@ -87,37 +73,27 @@ public QuickShellRunSettingsWindow( _terminalAppBox.SelectionChanged += (_, _) => ReloadProfileChoices(); root.Children.Add(_terminalAppBox); - - - + root.Children.Add(Label("Default profile")); _defaultProfileBox = new ComboBox - { - DisplayMemberPath = "Label", SelectedValuePath = "Id", Margin = new Thickness(0, 0, 0, 8), - }; root.Children.Add(_defaultProfileBox); - - - + root.Children.Add(Heading("Git launch")); _blockDirtyBranchBox = new CheckBox - { - Content = "Block launch when dirty and branch would change", Margin = new Thickness(0, 0, 0, 8), - }; root.Children.Add(_blockDirtyBranchBox); @@ -125,35 +101,25 @@ public QuickShellRunSettingsWindow( root.Children.Add(Help( "When a worktree target branch differs from HEAD, block launch if the working tree has uncommitted changes.")); - - - + root.Children.Add(Heading("Home list")); _showRecentsBox = new CheckBox - { - Content = "Show recent workspaces", Margin = new Thickness(0, 0, 0, 8), - }; root.Children.Add(_showRecentsBox); - - - + root.Children.Add(Heading("Multiple commands")); _singleWindowTabsBox = new CheckBox - { - Content = "Open multiple commands in one Windows Terminal window", Margin = new Thickness(0, 0, 0, 8), - }; root.Children.Add(_singleWindowTabsBox); @@ -161,9 +127,7 @@ public QuickShellRunSettingsWindow( root.Children.Add(Help( "When supported, extra commands open as tabs in the same window. Mixed elevation or Console Host still opens separate windows.")); - - - + root.Children.Add(Heading("Shortcuts")); root.Children.Add(TooltipButton( @@ -189,13 +153,9 @@ public QuickShellRunSettingsWindow( "Replace every shortcut with the contents of a JSON file.", () => ImportShortcuts(replace: true))); - - - + root.Children.Add(new Separator { Margin = new Thickness(0, 8, 0, 8) }); - - - + root.Children.Add(TooltipButton( "Open shortcuts.json", @@ -211,25 +171,18 @@ public QuickShellRunSettingsWindow( "Open the folder that stores shortcuts and settings.", () => RunFileDialogs.OpenFolder(_shortcuts.ConfigDirectory))); - - - + _statusText = new TextBlock - { - TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 12, 0, 12), Foreground = System.Windows.Media.Brushes.Gray, - }; root.Children.Add(_statusText); - - - + var buttons = new StackPanel { Orientation = Orientation.Horizontal, @@ -261,23 +214,16 @@ public QuickShellRunSettingsWindow( buttons.Children.Add(save); buttons.Children.Add(done); root.Children.Add(buttons); - - - + Content = root; LoadCurrentValues(); _baseline = CaptureSnapshot(); - } - - - + private void LoadCurrentValues() - { - _terminalAppBox.SelectedValue = _settings.TerminalApplicationId; ReloadProfileChoices(); @@ -289,15 +235,10 @@ private void LoadCurrentValues() _showRecentsBox.IsChecked = QuickShellRecentSettings.IsEnabled(_settings.ReadRecentWorkspaceCount()); _singleWindowTabsBox.IsChecked = !_settings.ReadSeparateWindowsForMultiLaunch(); - } - - - + private void RestoreBaseline() - { - _settings.SaveTerminalDefaults(_baseline.TerminalApp, _baseline.DefaultProfile); _settings.SaveBlockDirtyBranchSwitch(_baseline.BlockDirtyBranch); @@ -305,11 +246,8 @@ private void RestoreBaseline() _settings.SaveRecentWorkspaceCount(_baseline.RecentCount); _settings.SaveMultiLaunchPresentation(_baseline.SingleWindowTabs); - } - - - + private SettingsSnapshot CaptureSnapshot() => new( @@ -323,13 +261,9 @@ private SettingsSnapshot CaptureSnapshot() => QuickShellRecentSettings.FromEnabled(_showRecentsBox.IsChecked == true), _singleWindowTabsBox.IsChecked == true); - - - + private void ReloadProfileChoices() - { - var app = _terminalAppBox.SelectedValue as string ?? _settings.TerminalApplicationId; var selected = _defaultProfileBox.SelectedValue as string ?? _settings.DefaultProfileId; @@ -337,15 +271,10 @@ private void ReloadProfileChoices() _defaultProfileBox.Items.Clear(); foreach (var choice in RunTerminalChoices.GetDefaultProfileChoices(_catalog, app)) - { - _defaultProfileBox.Items.Add(new { choice.Id, choice.Label }); - } - - - + _defaultProfileBox.SelectedValue = RunTerminalChoices.GetDefaultProfileChoices(_catalog, app) .Any(choice => choice.Id.Equals(selected, StringComparison.OrdinalIgnoreCase)) @@ -353,11 +282,8 @@ private void ReloadProfileChoices() ? selected : TerminalHostIds.DefaultProfile; - } - - - + private bool HasUnsavedChanges() { var current = CaptureSnapshot(); @@ -369,9 +295,7 @@ private bool HasUnsavedChanges() } private bool Save() - { - var app = _terminalAppBox.SelectedValue as string ?? TerminalHostIds.LetWindowsChoose; var profile = _defaultProfileBox.SelectedValue as string ?? TerminalHostIds.DefaultProfile; @@ -389,79 +313,46 @@ private bool Save() _baseline = CaptureSnapshot(); return true; - } - - - + private void ExportShortcuts() - { - if (RunFileDialogs.TryExportShortcuts(_shortcuts, this, out var message)) - { - SetStatus(message); - } - } - - - + private void ImportShortcuts(bool replace) - { - if (RunFileDialogs.TryImportShortcuts(_shortcuts, this, replace, out var message) && !string.IsNullOrWhiteSpace(message)) - { - SetStatus(message); - } - } - - - + private void SetStatus(string message) => _statusText.Text = message; - - - + private static TextBlock Heading(string text) => new() - { - Text = text, FontWeight = FontWeights.SemiBold, Margin = new Thickness(0, 12, 0, 8), - }; - - - + private static TextBlock Label(string text) => new() - { - Text = text, Margin = new Thickness(0, 0, 0, 4), - }; - - - + private static TextBlock Help(string text) => new() - { - Text = text, TextWrapping = TextWrapping.Wrap, @@ -471,51 +362,31 @@ private void ImportShortcuts(bool replace) Foreground = System.Windows.Media.Brushes.Gray, FontSize = 12, - }; - - - + private static ComboBox Combo(IReadOnlyList<(string Id, string Label)> choices) - { - var box = new ComboBox - { - DisplayMemberPath = "Label", SelectedValuePath = "Id", Margin = new Thickness(0, 0, 0, 8), - }; foreach (var choice in choices) - { - box.Items.Add(new { choice.Id, choice.Label }); - } - - - + return box; - } - - - + private static Button TooltipButton(string text, string tooltip, Action action) - { - var button = new Button - { - Content = text, HorizontalAlignment = HorizontalAlignment.Stretch, @@ -525,17 +396,13 @@ private static Button TooltipButton(string text, string tooltip, Action action) MinWidth = 220, ToolTip = tooltip, - }; button.Click += (_, _) => action(); return button; - } - - - + private readonly record struct SettingsSnapshot( string TerminalApp, @@ -547,55 +414,34 @@ private readonly record struct SettingsSnapshot( int RecentCount, bool SingleWindowTabs); - } - - - + internal static class QuickShellRunSettingsDialog - { - public static void Show( QuickShellSettingsReader settings, IShortcutRepository shortcuts, IProjectAnalysisService projectAnalysis, ITerminalCatalog catalog) - { - void ShowWindow() - { - var window = new QuickShellRunSettingsWindow(settings, shortcuts, projectAnalysis, catalog); window.ShowDialog(); - } - - - + var app = Application.Current; if (app?.Dispatcher.CheckAccess() == true) - { - ShowWindow(); - } - else - { - app?.Dispatcher.Invoke(ShowWindow); - } - } - } diff --git a/QuickShell/Services/SectionListItems.cs b/QuickShell/Services/SectionListItems.cs index 2ba19ce2..de367875 100644 --- a/QuickShell/Services/SectionListItems.cs +++ b/QuickShell/Services/SectionListItems.cs @@ -1,40 +1,39 @@ -using Microsoft.CommandPalette.Extensions; -using Microsoft.CommandPalette.Extensions.Toolkit; - -namespace QuickShell.Services; - -/// -/// Emits CmdPal section headers per PowerToys PR #43952: a row -/// (no command, non-empty section/title) renders the visible header; stamping -/// on normal items does not. -/// -internal static class SectionListItems -{ - public static IEnumerable InSection(string sectionTitle, IEnumerable items) - { - var materialized = items.ToList(); - if (materialized.Count == 0) - { - return materialized; - } - - if (string.IsNullOrWhiteSpace(sectionTitle)) - { - return materialized; - } - - return PrependHeader(sectionTitle, materialized); - } - - public static IEnumerable PrependHeader(string sectionTitle, IReadOnlyList items) - { - yield return CreateHeader(sectionTitle); - foreach (var item in items) - { - yield return item; - } - } - - public static Separator CreateHeader(string sectionTitle) => new(sectionTitle); -} - \ No newline at end of file +using Microsoft.CommandPalette.Extensions; +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace QuickShell.Services; + +/// +/// Emits CmdPal section headers per PowerToys PR #43952: a row +/// (no command, non-empty section/title) renders the visible header; stamping +/// on normal items does not. +/// +internal static class SectionListItems +{ + public static IEnumerable InSection(string sectionTitle, IEnumerable items) + { + var materialized = items.ToList(); + if (materialized.Count == 0) + { + return materialized; + } + + if (string.IsNullOrWhiteSpace(sectionTitle)) + { + return materialized; + } + + return PrependHeader(sectionTitle, materialized); + } + + public static IEnumerable PrependHeader(string sectionTitle, IReadOnlyList items) + { + yield return CreateHeader(sectionTitle); + foreach (var item in items) + { + yield return item; + } + } + + public static Separator CreateHeader(string sectionTitle) => new(sectionTitle); +} diff --git a/scripts/LogoAssetGenerator/Program.cs b/scripts/LogoAssetGenerator/Program.cs index 77f24938..7da8e4df 100644 --- a/scripts/LogoAssetGenerator/Program.cs +++ b/scripts/LogoAssetGenerator/Program.cs @@ -239,8 +239,8 @@ static void RenderPosterLogoFromBitmap(string outDir, string fileName, SKBitmap DrawPosterKicker(canvas, renderWidth, kickerY, kickerSize); DrawPosterBranding(canvas, renderWidth, titleY, taglineY, titleSize, taglineSize); - var iconBandTop = textBlockBottom + height * 0.02f * ss; - var iconBandBottom = contentBottom - height * 0.11f * ss; + var iconBandTop = textBlockBottom + (height * 0.02f * ss); + var iconBandBottom = contentBottom - (height * 0.11f * ss); var iconBandHeight = iconBandBottom - iconBandTop; var iconWidthTarget = width * 0.68f * ss; var scaleByWidth = iconWidthTarget / bounds.Width; @@ -260,7 +260,7 @@ static void RenderPosterLogoFromBitmap(string outDir, string fileName, SKBitmap var badgeSize = width * 0.021f * ss; var badgeBlockHeight = height * 0.088f * ss; - var badgeRowY = iconTop + scaledHeight + height * 0.02f * ss + (badgeBlockHeight / 2f); + var badgeRowY = iconTop + scaledHeight + (height * 0.02f * ss) + (badgeBlockHeight / 2f); DrawPosterFeatureBadges(canvas, renderWidth, badgeRowY, badgeSize); DrawPosterPaletteMockup(canvas, renderWidth, renderHeight, contentBottom); @@ -297,8 +297,8 @@ static void RenderPosterLogoFromPicture(string outDir, string fileName, SKPictur DrawPosterBranding(canvas, renderWidth, titleY, taglineY, titleSize, taglineSize); // Size icon to fill the band between headline and badge row. - var iconBandTop = textBlockBottom + height * 0.02f * ss; - var iconBandBottom = contentBottom - height * 0.11f * ss; + var iconBandTop = textBlockBottom + (height * 0.02f * ss); + var iconBandBottom = contentBottom - (height * 0.11f * ss); var iconBandHeight = iconBandBottom - iconBandTop; var iconWidthTarget = width * 0.68f * ss; var scaleByWidth = iconWidthTarget / bounds.Width; @@ -324,7 +324,7 @@ static void RenderPosterLogoFromPicture(string outDir, string fileName, SKPictur var badgeSize = width * 0.021f * ss; var badgeBlockHeight = height * 0.088f * ss; - var badgeRowY = iconTop + scaledHeight + height * 0.02f * ss + (badgeBlockHeight / 2f); + var badgeRowY = iconTop + scaledHeight + (height * 0.02f * ss) + (badgeBlockHeight / 2f); DrawPosterFeatureBadges(canvas, renderWidth, badgeRowY, badgeSize); DrawPosterPaletteMockup(canvas, renderWidth, renderHeight, contentBottom); @@ -472,8 +472,8 @@ static void DrawPosterBadgeRow( static void DrawPosterPaletteMockup(SKCanvas canvas, int width, int height, float contentBottom) { - var mockTop = contentBottom + height * 0.018f; - var mockHeight = height - mockTop - height * 0.035f; + var mockTop = contentBottom + (height * 0.018f); + var mockHeight = height - mockTop - (height * 0.035f); var mockWidth = width * 0.86f; var mockLeft = (width - mockWidth) / 2f; var corner = mockHeight * 0.07f; @@ -483,7 +483,7 @@ static void DrawPosterPaletteMockup(SKCanvas canvas, int width, int height, floa { IsAntialias = true, Shader = SKShader.CreateRadialGradient( - new SKPoint(width * 0.42f, mockTop + mockHeight * 0.55f), + new SKPoint(width * 0.42f, mockTop + (mockHeight * 0.55f)), mockWidth * 0.42f, new[] { new SKColor(0x2F, 0x96, 0xE8, 0x28), SKColors.Transparent }, null, @@ -493,7 +493,7 @@ static void DrawPosterPaletteMockup(SKCanvas canvas, int width, int height, floa { IsAntialias = true, Shader = SKShader.CreateRadialGradient( - new SKPoint(width * 0.62f, mockTop + mockHeight * 0.72f), + new SKPoint(width * 0.62f, mockTop + (mockHeight * 0.72f)), mockWidth * 0.32f, new[] { new SKColor(0xF0, 0xC0, 0x38, 0x1A), SKColors.Transparent }, null, @@ -539,15 +539,15 @@ static void DrawPosterPaletteMockup(SKCanvas canvas, int width, int height, floa TextSize = width * 0.022f, TextAlign = SKTextAlign.Left, }; - var chromeX = mockLeft + mockWidth * 0.06f; + var chromeX = mockLeft + (mockWidth * 0.06f); var chromeMetrics = titlePaint.FontMetrics; - var chromeBaseline = mockTop + titleBarHeight * 0.62f - ((chromeMetrics.Ascent + chromeMetrics.Descent) / 2f); + var chromeBaseline = mockTop + (titleBarHeight * 0.62f) - ((chromeMetrics.Ascent + chromeMetrics.Descent) / 2f); canvas.DrawText("Command Palette", chromeX, chromeBaseline, titlePaint); - canvas.DrawText("Win+Alt+Space", mockLeft + mockWidth * 0.94f - hintPaint.MeasureText("Win+Alt+Space"), chromeBaseline, hintPaint); + canvas.DrawText("Win+Alt+Space", mockLeft + (mockWidth * 0.94f) - hintPaint.MeasureText("Win+Alt+Space"), chromeBaseline, hintPaint); - var searchTop = mockTop + titleBarHeight + mockHeight * 0.04f; + var searchTop = mockTop + titleBarHeight + (mockHeight * 0.04f); var searchHeight = mockHeight * 0.11f; - var searchLeft = mockLeft + mockWidth * 0.06f; + var searchLeft = mockLeft + (mockWidth * 0.06f); var searchWidth = mockWidth * 0.88f; var searchRect = new SKRect(searchLeft, searchTop, searchLeft + searchWidth, searchTop + searchHeight); using var searchFill = new SKPaint @@ -576,13 +576,13 @@ static void DrawPosterPaletteMockup(SKCanvas canvas, int width, int height, floa }; var searchMetrics = searchTextPaint.FontMetrics; var searchBaseline = searchRect.MidY - ((searchMetrics.Ascent + searchMetrics.Descent) / 2f); - canvas.DrawText("api", searchLeft + searchWidth * 0.05f, searchBaseline, searchTextPaint); + canvas.DrawText("api", searchLeft + (searchWidth * 0.05f), searchBaseline, searchTextPaint); - var listTop = searchTop + searchHeight + mockHeight * 0.05f; + var listTop = searchTop + searchHeight + (mockHeight * 0.05f); var rowHeight = mockHeight * 0.19f; DrawPaletteListRow( canvas, - mockLeft + mockWidth * 0.06f, + mockLeft + (mockWidth * 0.06f), listTop, mockWidth * 0.88f, rowHeight, @@ -591,8 +591,8 @@ static void DrawPosterPaletteMockup(SKCanvas canvas, int width, int height, floa selected: true); DrawPaletteListRow( canvas, - mockLeft + mockWidth * 0.06f, - listTop + rowHeight + mockHeight * 0.02f, + mockLeft + (mockWidth * 0.06f), + listTop + rowHeight + (mockHeight * 0.02f), mockWidth * 0.88f, rowHeight, "Frontend", @@ -648,9 +648,9 @@ static void DrawPaletteListRow( var titleMetrics = titlePaint.FontMetrics; var subtitleMetrics = subtitlePaint.FontMetrics; - var textX = left + width * 0.04f; - var titleBaseline = top + height * 0.38f - ((titleMetrics.Ascent + titleMetrics.Descent) / 2f); - var subtitleBaseline = top + height * 0.72f - ((subtitleMetrics.Ascent + subtitleMetrics.Descent) / 2f); + var textX = left + (width * 0.04f); + var titleBaseline = top + (height * 0.38f) - ((titleMetrics.Ascent + titleMetrics.Descent) / 2f); + var subtitleBaseline = top + (height * 0.72f) - ((subtitleMetrics.Ascent + subtitleMetrics.Descent) / 2f); canvas.DrawText(title, textX, titleBaseline, titlePaint); canvas.DrawText(TruncateToWidth(subtitle, subtitlePaint, width * 0.92f), textX, subtitleBaseline, subtitlePaint); }