Skip to content

Commit d436b3a

Browse files
committed
feat: add SkipReport for helix, intellij, vscode, zed
1 parent 3b5997b commit d436b3a

6 files changed

Lines changed: 131 additions & 25 deletions

File tree

internal/plugins/helix/export.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/pelletier/go-toml/v2"
1111
"github.com/xinnjie/onekeymap-cli/internal/diff"
12+
"github.com/xinnjie/onekeymap-cli/internal/export"
1213
"github.com/xinnjie/onekeymap-cli/internal/keymap"
1314
"github.com/xinnjie/onekeymap-cli/internal/mappings"
1415
"github.com/xinnjie/onekeymap-cli/pkg/pluginapi"
@@ -49,7 +50,8 @@ func (e *helixExporter) Export(
4950
}
5051

5152
// Generate managed keybindings from current setting
52-
managedKeys := e.generateManagedKeybindings(ctx, setting)
53+
marker := export.NewMarker(setting)
54+
managedKeys := e.generateManagedKeybindings(ctx, setting, marker)
5355

5456
// Merge managed and unmanaged keybindings
5557
finalKeys := e.mergeKeybindings(ctx, managedKeys, unmanagedKeys)
@@ -75,6 +77,7 @@ func (e *helixExporter) Export(
7577
return &pluginapi.PluginExportReport{
7678
BaseEditorConfig: existingKeys,
7779
ExportEditorConfig: finalKeys,
80+
SkipReport: marker.Report(),
7881
}, nil
7982
}
8083

@@ -172,12 +175,21 @@ func (e *helixExporter) isManagedKeybinding(command string, mode Mode) bool {
172175
}
173176

174177
// generateManagedKeybindings generates Helix keybindings from KeymapSetting.
175-
func (e *helixExporter) generateManagedKeybindings(ctx context.Context, setting *keymapv1.Keymap) helixKeys {
178+
func (e *helixExporter) generateManagedKeybindings(
179+
ctx context.Context,
180+
setting *keymapv1.Keymap,
181+
marker *export.Marker,
182+
) helixKeys {
176183
keysByMode := helixKeys{}
177184

178185
for _, km := range setting.GetActions() {
179186
mapping := e.mappingConfig.Get(km.GetName())
180187
if mapping == nil || len(mapping.Helix) == 0 {
188+
for _, b := range km.GetBindings() {
189+
if b != nil && b.GetKeyChords() != nil {
190+
marker.MarkSkippedForReason(km.GetName(), b.GetKeyChords(), pluginapi.ErrActionNotSupported)
191+
}
192+
}
181193
continue
182194
}
183195

@@ -196,11 +208,18 @@ func (e *helixExporter) generateManagedKeybindings(ctx context.Context, setting
196208
"action",
197209
km.GetName(),
198210
)
211+
marker.MarkSkippedForReason(
212+
km.GetName(),
213+
b.GetKeyChords(),
214+
&pluginapi.NotSupportedError{Note: err.Error()},
215+
)
199216
} else {
200217
e.logger.WarnContext(ctx, "Skipping keybinding with un-formattable key", "action", km.GetName(), "error", err)
218+
marker.MarkSkippedForReason(km.GetName(), b.GetKeyChords(), &pluginapi.NotSupportedError{Note: err.Error()})
201219
}
202220
continue
203221
}
222+
marker.MarkExported(km.GetName(), b.GetKeyChords())
204223

205224
for _, hconf := range mapping.Helix {
206225
if hconf.Command == "" {

internal/plugins/intellij/export.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"sort"
1010

1111
"github.com/xinnjie/onekeymap-cli/internal/diff"
12+
"github.com/xinnjie/onekeymap-cli/internal/export"
1213
"github.com/xinnjie/onekeymap-cli/internal/keymap"
1314
"github.com/xinnjie/onekeymap-cli/internal/mappings"
1415
"github.com/xinnjie/onekeymap-cli/pkg/pluginapi"
@@ -54,7 +55,8 @@ func (e *intellijExporter) Export(
5455
}
5556

5657
// Generate managed actions from current setting
57-
managedActions := e.generateManagedActions(setting)
58+
marker := export.NewMarker(setting)
59+
managedActions := e.generateManagedActions(setting, marker)
5860

5961
// Merge managed and unmanaged actions
6062
finalActions := e.mergeActions(managedActions, unmanagedActions)
@@ -81,6 +83,7 @@ func (e *intellijExporter) Export(
8183
return &pluginapi.PluginExportReport{
8284
BaseEditorConfig: existingDoc,
8385
ExportEditorConfig: doc,
86+
SkipReport: marker.Report(),
8487
}, nil
8588
}
8689

@@ -109,7 +112,7 @@ func (e *intellijExporter) isManagedAction(actionID string) bool {
109112
}
110113

111114
// generateManagedActions generates IntelliJ actions from KeymapSetting.
112-
func (e *intellijExporter) generateManagedActions(setting *keymapv1.Keymap) []ActionXML {
115+
func (e *intellijExporter) generateManagedActions(setting *keymapv1.Keymap, marker *export.Marker) []ActionXML {
113116
// Group keybindings by IntelliJ action ID while preserving order of first appearance.
114117
actionsMap := make(map[string]*ActionXML)
115118
var actionOrder []string
@@ -121,6 +124,11 @@ func (e *intellijExporter) generateManagedActions(setting *keymapv1.Keymap) []Ac
121124
mapping := e.mappingConfig.Get(km.GetName())
122125
if mapping == nil || mapping.IntelliJ.Action == "" {
123126
e.logger.Info("no mapping found for action", "action", km.GetName())
127+
for _, b := range km.GetBindings() {
128+
if b != nil && b.GetKeyChords() != nil {
129+
marker.MarkSkippedForReason(km.GetName(), b.GetKeyChords(), pluginapi.ErrActionNotSupported)
130+
}
131+
}
124132
continue
125133
}
126134
actionID := mapping.IntelliJ.Action
@@ -132,8 +140,14 @@ func (e *intellijExporter) generateManagedActions(setting *keymapv1.Keymap) []Ac
132140
shortcutXML, err := FormatKeybinding(keymap.NewKeyBinding(b))
133141
if err != nil {
134142
e.logger.Warn("failed to format keybinding", "action", km.GetName(), "error", err)
143+
marker.MarkSkippedForReason(
144+
km.GetName(),
145+
b.GetKeyChords(),
146+
&pluginapi.NotSupportedError{Note: err.Error()},
147+
)
135148
continue
136149
}
150+
marker.MarkExported(km.GetName(), b.GetKeyChords())
137151

138152
if _, exists := actionsMap[actionID]; !exists {
139153
actionsMap[actionID] = &ActionXML{ID: actionID}

internal/plugins/vscode/export.go

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/tailscale/hujson"
1212
"github.com/xinnjie/onekeymap-cli/internal/diff"
13+
"github.com/xinnjie/onekeymap-cli/internal/export"
1314
"github.com/xinnjie/onekeymap-cli/internal/keymap"
1415
"github.com/xinnjie/onekeymap-cli/internal/mappings"
1516
"github.com/xinnjie/onekeymap-cli/pkg/pluginapi"
@@ -83,29 +84,18 @@ func (e *vscodeExporter) Export(
8384
opts pluginapi.PluginExportOption,
8485
) (*pluginapi.PluginExportReport, error) {
8586
// Decode existing config for non-destructive merge
86-
var existingKeybindings []vscodeKeybinding
87-
if opts.ExistingConfig != nil {
88-
// Read all content first to apply hujson.Standardize
89-
rawData, err := io.ReadAll(opts.ExistingConfig)
90-
if err != nil {
91-
return nil, fmt.Errorf("failed to read existing config: %w", err)
92-
}
93-
// Strip comments and trailing commas using hujson
94-
standardizedData, err := hujson.Standardize(rawData)
95-
if err != nil {
96-
return nil, fmt.Errorf("failed to standardize JSON: %w", err)
97-
}
98-
if err := json.Unmarshal(standardizedData, &existingKeybindings); err != nil {
99-
return nil, fmt.Errorf("failed to decode existing config: %w", err)
100-
}
87+
existingKeybindings, err := e.parseExistingConfig(opts.ExistingConfig)
88+
if err != nil {
89+
return nil, err
10190
}
10291

10392
var unmanagedKeybindings []vscodeKeybinding
10493
if len(existingKeybindings) > 0 {
10594
unmanagedKeybindings = e.identifyUnmanagedKeybindings(existingKeybindings)
10695
}
10796

108-
managedKeybindings := e.generateManagedKeybindings(setting)
97+
marker := export.NewMarker(setting)
98+
managedKeybindings := e.generateManagedKeybindings(setting, marker)
10999

110100
finalKeybindings := e.mergeKeybindings(managedKeybindings, unmanagedKeybindings)
111101

@@ -124,9 +114,36 @@ func (e *vscodeExporter) Export(
124114
return &pluginapi.PluginExportReport{
125115
BaseEditorConfig: existingKeybindings,
126116
ExportEditorConfig: finalKeybindings,
117+
SkipReport: marker.Report(),
127118
}, nil
128119
}
129120

121+
func (e *vscodeExporter) parseExistingConfig(reader io.Reader) ([]vscodeKeybinding, error) {
122+
var existingKeybindings []vscodeKeybinding
123+
if reader == nil {
124+
return existingKeybindings, nil
125+
}
126+
127+
// Read all content first to apply hujson.Standardize
128+
rawData, err := io.ReadAll(reader)
129+
if err != nil {
130+
return nil, fmt.Errorf("failed to read existing config: %w", err)
131+
}
132+
133+
if len(rawData) > 0 {
134+
// Strip comments and trailing commas using hujson
135+
standardizedData, err := hujson.Standardize(rawData)
136+
if err != nil {
137+
return nil, fmt.Errorf("failed to standardize JSON: %w", err)
138+
}
139+
if err := json.Unmarshal(standardizedData, &existingKeybindings); err != nil {
140+
return nil, fmt.Errorf("failed to decode existing config: %w", err)
141+
}
142+
}
143+
144+
return existingKeybindings, nil
145+
}
146+
130147
// identifyUnmanagedKeybindings performs reverse lookup to identify keybindings
131148
// that are not managed by onekeymap.
132149
func (e *vscodeExporter) identifyUnmanagedKeybindings(existingKeybindings []vscodeKeybinding) []vscodeKeybinding {
@@ -160,17 +177,30 @@ func (e *vscodeExporter) findMappingByVSCodeKeybinding(kb vscodeKeybinding) *map
160177
}
161178

162179
// generateManagedKeybindings generates VSCode keybindings from KeymapSetting.
163-
func (e *vscodeExporter) generateManagedKeybindings(setting *keymapv1.Keymap) []vscodeKeybinding {
180+
func (e *vscodeExporter) generateManagedKeybindings(
181+
setting *keymapv1.Keymap,
182+
marker *export.Marker,
183+
) []vscodeKeybinding {
164184
var vscodeKeybindings []vscodeKeybinding
165185

166186
for _, km := range setting.GetActions() {
167187
mapping := e.mappingConfig.Get(km.GetName())
168188
if mapping == nil {
189+
for _, b := range km.GetBindings() {
190+
if b != nil && b.GetKeyChords() != nil {
191+
marker.MarkSkippedForReason(km.GetName(), b.GetKeyChords(), pluginapi.ErrActionNotSupported)
192+
}
193+
}
169194
continue
170195
}
171196

172197
vscodeConfigs := mapping.VSCode
173198
if len(vscodeConfigs) == 0 {
199+
for _, b := range km.GetBindings() {
200+
if b != nil && b.GetKeyChords() != nil {
201+
marker.MarkSkippedForReason(km.GetName(), b.GetKeyChords(), pluginapi.ErrActionNotSupported)
202+
}
203+
}
174204
continue
175205
}
176206

@@ -182,8 +212,14 @@ func (e *vscodeExporter) generateManagedKeybindings(setting *keymapv1.Keymap) []
182212
keys, err := FormatKeybinding(binding)
183213
if err != nil {
184214
e.logger.Warn("Skipping keybinding with un-formattable key", "action", km.GetName(), "error", err)
215+
marker.MarkSkippedForReason(
216+
km.GetName(),
217+
b.GetKeyChords(),
218+
&pluginapi.NotSupportedError{Note: err.Error()},
219+
)
185220
continue
186221
}
222+
marker.MarkExported(km.GetName(), b.GetKeyChords())
187223
for _, vscodeConfig := range vscodeConfigs {
188224
if vscodeConfig.Command == "" {
189225
continue

internal/plugins/vscode/export_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,22 @@ func TestExporter_Export(t *testing.T) {
218218
}
219219
]`,
220220
},
221+
{
222+
name: "handles empty existing config file",
223+
keymapSetting: &keymapv1.Keymap{
224+
Actions: []*keymapv1.Action{
225+
keymap.NewActioinBinding("actions.edit.copy", "meta+c"),
226+
},
227+
},
228+
existingConfig: ``, // Represents an empty file
229+
expectedJSON: `[
230+
{
231+
"key": "cmd+c",
232+
"command": "editor.action.clipboardCopyAction",
233+
"when": "editorTextFocus && condition > 0"
234+
}
235+
]`,
236+
},
221237
// Base order preservation tests
222238
{
223239
name: "preserves order by base config using command as key",

internal/plugins/zed/export.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/tailscale/hujson"
1212
"github.com/xinnjie/onekeymap-cli/internal/diff"
13+
"github.com/xinnjie/onekeymap-cli/internal/export"
1314
"github.com/xinnjie/onekeymap-cli/internal/keymap"
1415
"github.com/xinnjie/onekeymap-cli/internal/mappings"
1516
"github.com/xinnjie/onekeymap-cli/pkg/pluginapi"
@@ -45,7 +46,8 @@ func (p *zedExporter) Export(
4546
}
4647

4748
// Generate managed keybindings from current setting
48-
managedKeymaps := p.generateManagedKeybindings(setting)
49+
marker := export.NewMarker(setting)
50+
managedKeymaps := p.generateManagedKeybindings(setting, marker)
4951

5052
// Merge managed and existing keybindings
5153
finalKeymaps := p.mergeKeybindings(managedKeymaps, existingConfig)
@@ -71,6 +73,7 @@ func (p *zedExporter) Export(
7173
return &pluginapi.PluginExportReport{
7274
BaseEditorConfig: existingConfig,
7375
ExportEditorConfig: finalKeymaps,
76+
SkipReport: marker.Report(),
7477
}, nil
7578
}
7679

@@ -142,13 +145,18 @@ func orderByBaseContext(final zedKeymapConfig, base zedKeymapConfig) zedKeymapCo
142145
}
143146

144147
// generateManagedKeybindings creates keybindings from the current setting.
145-
func (p *zedExporter) generateManagedKeybindings(setting *keymapv1.Keymap) zedKeymapConfig {
148+
func (p *zedExporter) generateManagedKeybindings(setting *keymapv1.Keymap, marker *export.Marker) zedKeymapConfig {
146149
keymapsByContext := make(map[string]map[string]zedActionValue)
147150

148151
for _, km := range setting.GetActions() {
149152
actionConfig, err := p.actionIDToZed(km.GetName())
150153
if err != nil {
151154
p.logger.Info("no mapping found for action", "action", km.GetName())
155+
for _, b := range km.GetBindings() {
156+
if b != nil && b.GetKeyChords() != nil {
157+
marker.MarkSkippedForReason(km.GetName(), b.GetKeyChords(), pluginapi.ErrActionNotSupported)
158+
}
159+
}
152160
continue
153161
}
154162

@@ -159,8 +167,14 @@ func (p *zedExporter) generateManagedKeybindings(setting *keymapv1.Keymap) zedKe
159167
keys, err := FormatZedKeybind(keymap.NewKeyBinding(b))
160168
if err != nil {
161169
p.logger.Warn("failed to format key binding", "error", err)
170+
marker.MarkSkippedForReason(
171+
km.GetName(),
172+
b.GetKeyChords(),
173+
&pluginapi.NotSupportedError{Note: err.Error()},
174+
)
162175
continue
163176
}
177+
marker.MarkExported(km.GetName(), b.GetKeyChords())
164178

165179
// For each Zed mapping config, create a binding under its context
166180
for _, zconf := range *actionConfig {

internal/views/skip_report_view.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type SkipReportViewModel struct {
2424
title string
2525
table table.Model
2626
style lipgloss.Style
27+
help string
2728
}
2829

2930
func NewSkipReportViewModel(skipActions []pluginapi.SkipAction) SkipReportViewModel {
@@ -66,7 +67,12 @@ func NewSkipReportViewModel(skipActions []pluginapi.SkipAction) SkipReportViewMo
6667

6768
t.SetStyles(s)
6869

69-
return SkipReportViewModel{title: "Skipped Actions for Export", table: t, style: baseStyle}
70+
return SkipReportViewModel{
71+
title: "Skipped Actions for Export",
72+
table: t,
73+
style: baseStyle,
74+
help: "Press q or ctrl+c to quit",
75+
}
7076
}
7177

7278
func (m SkipReportViewModel) Init() tea.Cmd {
@@ -87,5 +93,6 @@ func (m SkipReportViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
8793

8894
func (m SkipReportViewModel) View() string {
8995
return lipgloss.NewStyle().Bold(true).PaddingBottom(1).Render(m.title) + "\n" +
90-
m.style.Render(m.table.View()) + "\n"
96+
m.style.Render(m.table.View()) + "\n" +
97+
helpStyle.Render(m.help)
9198
}

0 commit comments

Comments
 (0)