Repository navigation
Expand file tree
/
Copy pathKeybindingViewBuilder.cs
More file actions
125 lines (111 loc) · 4.87 KB
/
Copy pathKeybindingViewBuilder.cs
File metadata and controls
125 lines (111 loc) · 4.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
namespace OneCode.Core.Keybindings;
/// <summary>
/// 生效绑定的来源分类。
/// </summary>
public enum KeybindingSource
{
/// <summary>与默认绑定一致。</summary>
Default,
/// <summary>用户覆盖默认或新增的绑定。</summary>
Custom,
/// <summary>用户显式解绑(action 为 null)。</summary>
Unbound,
}
/// <summary>
/// 可显示的生效绑定视图条目。<see cref="Description"/> 为动作的功能说明
/// (来自 <see cref="KeybindingDefaults.AllActionDescriptions"/>);
/// 用户自定义 command: 绑定与显式解绑无说明(null)。
/// </summary>
public sealed record KeybindingView(
string Context,
string KeyDisplay,
string? Action,
KeybindingSource Source,
string? Description = null);
/// <summary>
/// 将合并后的绑定条目(默认在前、用户在后,后匹配生效)转换为显示视图:
/// 按 (Context, 规范化按键) 去重取最后一条,并与默认绑定对比标记来源。
/// 供 TUI overlay 与命令文本输出共用。
/// </summary>
public static class KeybindingViewBuilder
{
public static IReadOnlyList<KeybindingView> Build(IReadOnlyList<KeybindingEntry> mergedBindings)
{
// 同一 (context, key) 保留最后一条——用户绑定追加在默认之后,后匹配的生效。
var effective = new Dictionary<(string Context, string Key), KeybindingEntry>();
foreach (var entry in mergedBindings)
{
var key = NormalizeEntryKey(entry);
effective[(entry.Context, key)] = entry;
}
// 默认绑定索引:(context, key) → action
var defaults = new Dictionary<(string Context, string Key), string?>();
foreach (var entry in KeybindingDefaults.GetDefaultParsedBindings())
{
defaults[(entry.Context, NormalizeEntryKey(entry))] = entry.Action;
}
var views = new List<KeybindingView>(effective.Count);
foreach (var ((context, _), entry) in effective)
{
var source = Classify(entry, defaults);
views.Add(new KeybindingView(
context,
ToTitleCase(KeybindingParser.ChordToDisplayString(entry.Chord)),
entry.Action,
source,
entry.Action is null
? null
: KeybindingDefaults.AllActionDescriptions.GetValueOrDefault(entry.Action)));
}
// 按 KeybindingDefaults.AllContexts 的顺序分组,组内按按键排序,保证稳定输出。
var contextOrder = new Dictionary<string, int>(
KeybindingDefaults.AllContexts.Select((c, i) => new KeyValuePair<string, int>(c, i)),
StringComparer.Ordinal);
return views
.OrderBy(v => contextOrder.TryGetValue(v.Context, out var order) ? order : int.MaxValue)
.ThenBy(v => v.Context, StringComparer.Ordinal)
.ThenBy(v => v.KeyDisplay, StringComparer.Ordinal)
.ToList();
}
/// <summary>
/// 查询某个动作当前生效的快捷键显示名称(例如 <c>"Ctrl+O"</c>);若该动作被用户重映射,
/// 返回用户配置的按键文本;若未找到有效绑定则返回 <paramref name="fallback"/>。
/// </summary>
public static string GetEffectiveKeyDisplay(
IReadOnlyList<KeybindingEntry> mergedBindings,
string action,
string fallback)
{
if (mergedBindings.Count == 0)
return fallback;
var views = Build(mergedBindings);
var match = views.LastOrDefault(v =>
v.Source != KeybindingSource.Unbound
&& string.Equals(v.Action, action, StringComparison.Ordinal));
return match?.KeyDisplay ?? fallback;
}
private static string NormalizeEntryKey(KeybindingEntry entry) =>
KeybindingParser.NormalizeKeyForComparison(KeybindingParser.ChordToString(entry.Chord));
private static KeybindingSource Classify(
KeybindingEntry entry,
IReadOnlyDictionary<(string Context, string Key), string?> defaults)
{
if (entry.Action is null)
return KeybindingSource.Unbound;
return defaults.TryGetValue((entry.Context, NormalizeEntryKey(entry)), out var defaultAction)
&& defaultAction == entry.Action
? KeybindingSource.Default
: KeybindingSource.Custom;
}
/// <summary>
/// "ctrl+shift+d" → "Ctrl+Shift+D";和弦保留空格分隔("Ctrl+X Ctrl+K")。
/// </summary>
internal static string ToTitleCase(string keyDisplay)
{
return string.Join(' ', keyDisplay.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.Select(part => string.Join('+', part.Split('+')
.Select(p => p.Length <= 1
? p.ToUpperInvariant()
: char.ToUpperInvariant(p[0]) + p[1..].ToLowerInvariant()))));
}
}