11package cmd
22
33import (
4+ "bytes"
45 "errors"
56 "fmt"
67 "io"
78 "log/slog"
89 "os"
910 "path/filepath"
1011 "strings"
12+ "text/template"
1113
1214 tea "github.com/charmbracelet/bubbletea"
1315 "github.com/spf13/cobra"
@@ -145,25 +147,11 @@ func executeImportInteractive(
145147 return err
146148 }
147149
148- if result != nil && len (result .SkipReport .SkipActions ) > 0 {
149- vm := views .NewImportSkipReportViewModel (result .SkipReport )
150- p := tea .NewProgram (vm )
151- if _ , err := p .Run (); err != nil {
152- logger .Error ("could not start program" , "error" , err )
153- }
150+ if result != nil {
151+ printImportSummary (cmd , result )
154152 }
155153
156- // Show validation report if there are issues
157- if result != nil && result .Report != nil &&
158- (len (result .Report .Issues ) > 0 || len (result .Report .Warnings ) > 0 ) {
159- logger .Info ("Validation found issues. Displaying report..." )
160- if err := runValidationReportPreview (result .Report ); err != nil {
161- logger .Warn ("Failed to display validation report" , "error" , err )
162- }
163- }
164-
165- // Show changes preview and get user confirmation
166- if result != nil && result .Changes != nil {
154+ if result .Changes .HasChanges () {
167155 confirmed , err := runImportChangesPreview (result .Changes )
168156 if err != nil {
169157 logger .Warn ("failed to render changes preview" , "error" , err )
@@ -172,6 +160,8 @@ func executeImportInteractive(
172160 logger .Info ("User cancelled applying changes; no file will be written" )
173161 return nil
174162 }
163+ } else {
164+ cmd .Println ("No changes to import - file will not be updated" )
175165 }
176166
177167 return saveImportResult (f .output , result , logger )
@@ -217,6 +207,10 @@ func executeImportNonInteractive(
217207 return err
218208 }
219209
210+ if result != nil {
211+ printImportSummary (cmd , result )
212+ }
213+
220214 return saveImportResult (f .output , result , logger )
221215}
222216
@@ -278,6 +272,163 @@ func saveImportResult(outputPath string, result *importerapi.ImportResult, logge
278272 return nil
279273}
280274
275+ type importSkippedView struct {
276+ Action string
277+ KeybindingCount int
278+ Reason string
279+ }
280+
281+ type importSummaryView struct {
282+ TotalImported int
283+ Skipped []importSkippedView
284+ HasValidation bool
285+ ValidationSource string
286+ MappingsProcessed int
287+ MappingsSucceeded int
288+ Issues []string
289+ Warnings []string
290+ }
291+
292+ // nolint: gochecknoglobals
293+ var importSummaryTemplate = template .Must (template .New ("importSummary" ).Parse (`
294+ Import Summary:
295+ ✓ {{ .TotalImported }} actions imported into onekeymap
296+ {{- $skippedCount := len .Skipped }}
297+ {{- if eq $skippedCount 0 }}
298+ ✗ 0 editor actions skipped
299+ {{- else }}
300+ ✗ {{ $skippedCount }} editor actions skipped:
301+ {{- range .Skipped }}
302+ - {{ .Action }}{{ if gt .KeybindingCount 0 }} ({{ .KeybindingCount }} keybindings){{ end }}{{ if .Reason }}: {{ .Reason }}{{ end }}
303+ {{- end }}
304+ {{- end }}
305+ {{- if .HasValidation }}
306+
307+ Validation Summary:
308+ Source: {{ .ValidationSource }} | Mappings Processed: {{ .MappingsProcessed }} | Succeeded: {{ .MappingsSucceeded }}
309+ Issues: {{ len .Issues }}, Warnings: {{ len .Warnings }}
310+ {{- if gt (len .Issues) 0 }}
311+ Issues ({{ len .Issues }}):
312+ {{- range .Issues }}
313+ - {{ . }}
314+ {{- end }}
315+ {{- end }}
316+ {{- if gt (len .Warnings) 0 }}
317+ Warnings ({{ len .Warnings }}):
318+ {{- range .Warnings }}
319+ - {{ . }}
320+ {{- end }}
321+ {{- end }}
322+ {{- end }}
323+ ` ))
324+
325+ func printImportSummary (cmd * cobra.Command , result * importerapi.ImportResult ) {
326+ view := importSummaryView {
327+ TotalImported : len (result .Setting .Actions ),
328+ }
329+
330+ for _ , sk := range result .SkipReport .SkipActions {
331+ item := importSkippedView {
332+ Action : sk .EditorSpecificAction ,
333+ KeybindingCount : len (sk .Keybindings ),
334+ }
335+ if sk .Error != nil {
336+ item .Reason = sk .Error .Error ()
337+ }
338+ view .Skipped = append (view .Skipped , item )
339+ }
340+
341+ if result .Report != nil {
342+ rep := result .Report
343+ view .HasValidation = true
344+ view .ValidationSource = rep .SourceEditor
345+ view .MappingsProcessed = rep .Summary .MappingsProcessed
346+ view .MappingsSucceeded = rep .Summary .MappingsSucceeded
347+ for _ , issue := range rep .Issues {
348+ view .Issues = append (view .Issues , renderValidationIssueInline (issue ))
349+ }
350+ for _ , warning := range rep .Warnings {
351+ view .Warnings = append (view .Warnings , renderValidationIssueInline (warning ))
352+ }
353+ }
354+
355+ var buf bytes.Buffer
356+ if err := importSummaryTemplate .Execute (& buf , view ); err != nil {
357+ // Fallback to minimal output if template rendering fails
358+ cmd .Println ()
359+ cmd .Println ("Import Summary:" )
360+ cmd .Printf (" ✓ %d actions imported into onekeymap\n " , view .TotalImported )
361+ return
362+ }
363+
364+ cmd .Println ()
365+ cmd .Print (buf .String ())
366+ }
367+
368+ // renderValidationIssueInline renders a single validation issue in a compact textual form,
369+ // mirroring the semantics of views.renderIssue but without TUI styling.
370+ func renderValidationIssueInline (issue validateapi.ValidationIssue ) string {
371+ switch issue .Type {
372+ case validateapi .IssueTypeKeybindConflict :
373+ if c , ok := issue .Details .(validateapi.KeybindConflict ); ok {
374+ var actionLines []string
375+ for _ , action := range c .Actions {
376+ if action .Context != "" {
377+ actionLines = append (actionLines , fmt .Sprintf ("%s (%s)" , action .Context , action .ActionID ))
378+ } else {
379+ actionLines = append (actionLines , action .ActionID )
380+ }
381+ }
382+ return fmt .Sprintf (
383+ "Keybind Conflict: %s is mapped to multiple actions:\n - %s" ,
384+ c .Keybinding ,
385+ strings .Join (actionLines , "\n - " ),
386+ )
387+ }
388+ case validateapi .IssueTypeDanglingAction :
389+ if d , ok := issue .Details .(validateapi.DanglingAction ); ok {
390+ suggestion := ""
391+ if d .Suggestion != "" {
392+ suggestion = fmt .Sprintf (" (%s)" , d .Suggestion )
393+ }
394+ return fmt .Sprintf (
395+ "Dangling Action: %s does not exist in target %s.%s" ,
396+ d .Action ,
397+ d .TargetEditor ,
398+ suggestion ,
399+ )
400+ }
401+ case validateapi .IssueTypeUnsupportedAction :
402+ if u , ok := issue .Details .(validateapi.UnsupportedAction ); ok {
403+ return fmt .Sprintf (
404+ "Unsupported Action: %s (on key %s) is not supported for target %s." ,
405+ u .Action ,
406+ u .Keybinding ,
407+ u .TargetEditor ,
408+ )
409+ }
410+ case validateapi .IssueTypeDuplicateMapping :
411+ if d , ok := issue .Details .(validateapi.DuplicateMapping ); ok {
412+ return fmt .Sprintf (
413+ "Duplicate Mapping: Action %s with key %s is defined multiple times." ,
414+ d .Action ,
415+ d .Keybinding ,
416+ )
417+ }
418+ case validateapi .IssueTypePotentialShadowing :
419+ if p , ok := issue .Details .(validateapi.PotentialShadowing ); ok {
420+ return fmt .Sprintf (
421+ "Potential Shadowing: Key %s (for action %s). %s" ,
422+ p .Keybinding ,
423+ p .Action ,
424+ p .CriticalShortcutDescription ,
425+ )
426+ }
427+ }
428+
429+ return "Unknown issue type."
430+ }
431+
281432func handleInteractiveImportFlags (
282433 cmd * cobra.Command ,
283434 f * importFlags ,
@@ -415,13 +566,3 @@ func runImportForm(
415566 }
416567 return nil
417568}
418-
419- // run the validation report TUI ---.
420- func runValidationReportPreview (report * validateapi.ValidationReport ) error {
421- m := views .NewValidationReportModel (report )
422- p := tea .NewProgram (m )
423- if _ , err := p .Run (); err != nil {
424- return err
425- }
426- return nil
427- }
0 commit comments