fix(palette): theme selected descriptions
- commit
- 90f4a2ee4ba3e5da2446a331bbb6a6154cf7c370
- author
- brice · · 2026-07-18 14:58 UTC
- parent
- f40c90d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +126 -31
M internal/usecase/daemon/palette.go +1 -1
| @@ -501,6 +501,6 @@ func composePaletteClientFrame(model *palette.Model, base renderer.Frame, cfg do | |||
| 501 | 501 | styleSet := resolveStyles(styles) | |
| 502 | 502 | modal := paletteModalFor(domain.Size{Cols: base.Width, Rows: base.Height}, cfg) | |
| 503 | 503 | return composeModalClientFrame(base, modal, styleSet, func(size domain.Size) renderer.Frame { | |
| 504 | - | return model.Render(size, palette.RenderOptions{Styles: palette.RenderStyles{Base: styleSet.PickerBase, Row: styleSet.SurfaceInactive, Selection: styleSet.PickerSelection, Description: styleSet.PickerDescription}, Guidance: guidance}) | |
| 504 | + | return model.Render(size, palette.RenderOptions{Styles: palette.RenderStyles{Base: styleSet.PickerBase, Row: styleSet.SurfaceInactive, Selection: styleSet.PickerSelection, Description: styleSet.PickerDescription, SelectionDescription: styleSet.PickerSelectionMuted}, Guidance: guidance}) | |
| 505 | 505 | }) | |
| 506 | 506 | } | |
| @@ -501,6 +501,6 @@ func composePaletteClientFrame(model *palette.Model, base renderer.Frame, cfg do | |||||
| 501 | styleSet := resolveStyles(styles) | 501 | styleSet := resolveStyles(styles) | ||
| 502 | modal := paletteModalFor(domain.Size{Cols: base.Width, Rows: base.Height}, cfg) | 502 | modal := paletteModalFor(domain.Size{Cols: base.Width, Rows: base.Height}, cfg) | ||
| 503 | return composeModalClientFrame(base, modal, styleSet, func(size domain.Size) renderer.Frame { | 503 | return composeModalClientFrame(base, modal, styleSet, func(size domain.Size) renderer.Frame { | ||
| 504 | - | return model.Render(size, palette.RenderOptions{Styles: palette.RenderStyles{Base: styleSet.PickerBase, Row: styleSet.SurfaceInactive, Selection: styleSet.PickerSelection, Description: styleSet.PickerDescription}, Guidance: guidance}) | 504 | + | return model.Render(size, palette.RenderOptions{Styles: palette.RenderStyles{Base: styleSet.PickerBase, Row: styleSet.SurfaceInactive, Selection: styleSet.PickerSelection, Description: styleSet.PickerDescription, SelectionDescription: styleSet.PickerSelectionMuted}, Guidance: guidance}) |
| 505 | }) | 505 | }) | ||
| 506 | } | 506 | } | ||
M internal/usecase/daemon/palette_test.go +21 -6
| @@ -73,14 +73,17 @@ func TestCaptureOverlayLayersPreservesPaletteDescriptionSurfaceAcrossFallbacks(t | |||
| 73 | 73 | ||
| 74 | 74 | captureOverlayLayers(&state, snap, domain.PaletteConfig{}) | |
| 75 | 75 | ||
| 76 | - | description := state.overlays.palette.inner.At(4, 1).Style | |
| 77 | - | require.Equal(t, state.styles.SurfaceInactive.Background, description.Background) | |
| 78 | - | require.Equal(t, state.styles.SurfaceInactive.HasBackgroundRGB, description.HasBackgroundRGB) | |
| 79 | - | require.Equal(t, state.styles.SurfaceInactive.BackgroundRGB, description.BackgroundRGB) | |
| 76 | + | inactive := state.overlays.palette.inner.At(4, 1).Style | |
| 77 | + | require.Equal(t, state.styles.SurfaceInactive.Background, inactive.Background) | |
| 78 | + | require.Equal(t, state.styles.SurfaceInactive.HasBackgroundRGB, inactive.HasBackgroundRGB) | |
| 79 | + | require.Equal(t, state.styles.SurfaceInactive.BackgroundRGB, inactive.BackgroundRGB) | |
| 80 | 80 | if tt.indexed { | |
| 81 | - | require.Equal(t, 2, description.Foreground) | |
| 82 | - | require.False(t, description.HasBackgroundRGB) | |
| 81 | + | require.Equal(t, 2, inactive.Foreground) | |
| 82 | + | require.False(t, inactive.HasBackgroundRGB) | |
| 83 | 83 | } | |
| 84 | + | ||
| 85 | + | selected := state.overlays.palette.inner.At(4, 2).Style | |
| 86 | + | require.True(t, selected.Equal(state.styles.PickerSelectionMuted)) | |
| 84 | 87 | }) | |
| 85 | 88 | } | |
| 86 | 89 | } | |
| @@ -721,6 +724,18 @@ func TestComposePaletteClientFrameUsesConfiguredPosition(t *testing.T) { | |||
| 721 | 724 | } | |
| 722 | 725 | } | |
| 723 | 726 | ||
| 727 | + | func TestComposePaletteClientFrameUsesSelectedDescriptionTheme(t *testing.T) { | |
| 728 | + | model := palette.New(palette.CommandResults([]command.Command{{Code: "ONE", Desc: "first description"}})) | |
| 729 | + | size := domain.Size{Cols: 120, Rows: 40} | |
| 730 | + | base := renderer.NewFrame(size.Cols, size.Rows) | |
| 731 | + | styles := themeui.Resolve(themeui.BuiltinDark, domain.ThemeAccent{Mode: domain.ThemeAccentAuto}).Styles | |
| 732 | + | ||
| 733 | + | frame, _ := composePaletteClientFrame(model, base, domain.PaletteConfig{}, "", styles) | |
| 734 | + | inner := paletteModalFor(size, domain.PaletteConfig{}).Inner(size) | |
| 735 | + | ||
| 736 | + | require.True(t, frame.At(inner.X+4, inner.Y+1).Style.Equal(styles.PickerSelectionMuted)) | |
| 737 | + | } | |
| 738 | + | ||
| 724 | 739 | func TestPaletteUTF8PendingCompletesFilter(t *testing.T) { | |
| 725 | 740 | d, sess, ac, sends, releases := newManualTabSession(t, 1) | |
| 726 | 741 | defer releases[0]() | |
| @@ -73,14 +73,17 @@ func TestCaptureOverlayLayersPreservesPaletteDescriptionSurfaceAcrossFallbacks(t | |||||
| 73 | 73 | ||||
| 74 | captureOverlayLayers(&state, snap, domain.PaletteConfig{}) | 74 | captureOverlayLayers(&state, snap, domain.PaletteConfig{}) | ||
| 75 | 75 | ||||
| 76 | - | description := state.overlays.palette.inner.At(4, 1).Style | 76 | + | inactive := state.overlays.palette.inner.At(4, 1).Style |
| 77 | - | require.Equal(t, state.styles.SurfaceInactive.Background, description.Background) | 77 | + | require.Equal(t, state.styles.SurfaceInactive.Background, inactive.Background) |
| 78 | - | require.Equal(t, state.styles.SurfaceInactive.HasBackgroundRGB, description.HasBackgroundRGB) | 78 | + | require.Equal(t, state.styles.SurfaceInactive.HasBackgroundRGB, inactive.HasBackgroundRGB) |
| 79 | - | require.Equal(t, state.styles.SurfaceInactive.BackgroundRGB, description.BackgroundRGB) | 79 | + | require.Equal(t, state.styles.SurfaceInactive.BackgroundRGB, inactive.BackgroundRGB) |
| 80 | if tt.indexed { | 80 | if tt.indexed { | ||
| 81 | - | require.Equal(t, 2, description.Foreground) | 81 | + | require.Equal(t, 2, inactive.Foreground) |
| 82 | - | require.False(t, description.HasBackgroundRGB) | 82 | + | require.False(t, inactive.HasBackgroundRGB) |
| 83 | } | 83 | } | ||
| 84 | + | ||||
| 85 | + | selected := state.overlays.palette.inner.At(4, 2).Style | |||
| 86 | + | require.True(t, selected.Equal(state.styles.PickerSelectionMuted)) | |||
| 84 | }) | 87 | }) | ||
| 85 | } | 88 | } | ||
| 86 | } | 89 | } | ||
| @@ -721,6 +724,18 @@ func TestComposePaletteClientFrameUsesConfiguredPosition(t *testing.T) { | |||||
| 721 | } | 724 | } | ||
| 722 | } | 725 | } | ||
| 723 | 726 | ||||
| 727 | + | func TestComposePaletteClientFrameUsesSelectedDescriptionTheme(t *testing.T) { | |||
| 728 | + | model := palette.New(palette.CommandResults([]command.Command{{Code: "ONE", Desc: "first description"}})) | |||
| 729 | + | size := domain.Size{Cols: 120, Rows: 40} | |||
| 730 | + | base := renderer.NewFrame(size.Cols, size.Rows) | |||
| 731 | + | styles := themeui.Resolve(themeui.BuiltinDark, domain.ThemeAccent{Mode: domain.ThemeAccentAuto}).Styles | |||
| 732 | + | ||||
| 733 | + | frame, _ := composePaletteClientFrame(model, base, domain.PaletteConfig{}, "", styles) | |||
| 734 | + | inner := paletteModalFor(size, domain.PaletteConfig{}).Inner(size) | |||
| 735 | + | ||||
| 736 | + | require.True(t, frame.At(inner.X+4, inner.Y+1).Style.Equal(styles.PickerSelectionMuted)) | |||
| 737 | + | } | |||
| 738 | + | ||||
| 724 | func TestPaletteUTF8PendingCompletesFilter(t *testing.T) { | 739 | func TestPaletteUTF8PendingCompletesFilter(t *testing.T) { | ||
| 725 | d, sess, ac, sends, releases := newManualTabSession(t, 1) | 740 | d, sess, ac, sends, releases := newManualTabSession(t, 1) | ||
| 726 | defer releases[0]() | 741 | defer releases[0]() | ||
M internal/usecase/daemon/render_pipeline.go +1 -1
| @@ -226,7 +226,7 @@ func captureOverlayLayers(state *capturedRenderState, snap *overlayRenderSnapsho | |||
| 226 | 226 | guidance = snap.paletteHints.Feedback | |
| 227 | 227 | } | |
| 228 | 228 | o.paletteGuidance = snap.paletteFeedback | |
| 229 | - | o.palette.inner = snap.paletteModel.Render(rectSize(o.palette.modal.Inner(size)), palette.RenderOptions{Styles: palette.RenderStyles{Base: styles.PickerBase, Row: styles.SurfaceInactive, Selection: styles.PickerSelection, Description: styles.PickerDescription}, Guidance: guidance, Feedback: snap.paletteFeedback}) | |
| 229 | + | o.palette.inner = snap.paletteModel.Render(rectSize(o.palette.modal.Inner(size)), palette.RenderOptions{Styles: palette.RenderStyles{Base: styles.PickerBase, Row: styles.SurfaceInactive, Selection: styles.PickerSelection, Description: styles.PickerDescription, SelectionDescription: styles.PickerSelectionMuted}, Guidance: guidance, Feedback: snap.paletteFeedback}) | |
| 230 | 230 | } | |
| 231 | 231 | if snap.promptActive && snap.promptModel != nil { | |
| 232 | 232 | o.prompt.modal = promptModalFor(snap.promptModel.Title()) | |
| @@ -226,7 +226,7 @@ func captureOverlayLayers(state *capturedRenderState, snap *overlayRenderSnapsho | |||||
| 226 | guidance = snap.paletteHints.Feedback | 226 | guidance = snap.paletteHints.Feedback | ||
| 227 | } | 227 | } | ||
| 228 | o.paletteGuidance = snap.paletteFeedback | 228 | o.paletteGuidance = snap.paletteFeedback | ||
| 229 | - | o.palette.inner = snap.paletteModel.Render(rectSize(o.palette.modal.Inner(size)), palette.RenderOptions{Styles: palette.RenderStyles{Base: styles.PickerBase, Row: styles.SurfaceInactive, Selection: styles.PickerSelection, Description: styles.PickerDescription}, Guidance: guidance, Feedback: snap.paletteFeedback}) | 229 | + | o.palette.inner = snap.paletteModel.Render(rectSize(o.palette.modal.Inner(size)), palette.RenderOptions{Styles: palette.RenderStyles{Base: styles.PickerBase, Row: styles.SurfaceInactive, Selection: styles.PickerSelection, Description: styles.PickerDescription, SelectionDescription: styles.PickerSelectionMuted}, Guidance: guidance, Feedback: snap.paletteFeedback}) |
| 230 | } | 230 | } | ||
| 231 | if snap.promptActive && snap.promptModel != nil { | 231 | if snap.promptActive && snap.promptModel != nil { | ||
| 232 | o.prompt.modal = promptModalFor(snap.promptModel.Title()) | 232 | o.prompt.modal = promptModalFor(snap.promptModel.Title()) | ||
M internal/usecase/palette/model.go +19 -8
| @@ -13,10 +13,11 @@ import ( | |||
| 13 | 13 | type RenderStyles struct { | |
| 14 | 14 | // Base fills unused modal interior cells; Row is the ordinary inactive | |
| 15 | 15 | // result surface. They are separate to retain the chrome hierarchy. | |
| 16 | - | Base renderer.Style | |
| 17 | - | Row renderer.Style | |
| 18 | - | Selection renderer.Style | |
| 19 | - | Description renderer.Style | |
| 16 | + | Base renderer.Style | |
| 17 | + | Row renderer.Style | |
| 18 | + | Selection renderer.Style | |
| 19 | + | Description renderer.Style | |
| 20 | + | SelectionDescription renderer.Style | |
| 20 | 21 | } | |
| 21 | 22 | ||
| 22 | 23 | // RenderOptions contains presentation supplied by the daemon for one render. | |
| @@ -57,8 +58,10 @@ func DefaultRenderStyles() RenderStyles { | |||
| 57 | 58 | selection.Inverse = true | |
| 58 | 59 | description := renderer.DefaultStyle() | |
| 59 | 60 | description.Italic = true | |
| 61 | + | selectionDescription := selection | |
| 62 | + | selectionDescription.Italic = true | |
| 60 | 63 | base := renderer.DefaultStyle() | |
| 61 | - | return RenderStyles{Base: base, Row: base, Selection: selection, Description: description} | |
| 64 | + | return RenderStyles{Base: base, Row: base, Selection: selection, Description: description, SelectionDescription: selectionDescription} | |
| 62 | 65 | } | |
| 63 | 66 | ||
| 64 | 67 | func (m *Model) Insert(r rune) { | |
| @@ -248,6 +251,10 @@ func (m *Model) Render(inner domain.Size, opts RenderOptions) renderer.Frame { | |||
| 248 | 251 | return frame | |
| 249 | 252 | } | |
| 250 | 253 | base, row, selection, desc := styles.Base, styles.Row, styles.Selection, styles.Description | |
| 254 | + | selectionDesc := styles.SelectionDescription | |
| 255 | + | if selectionDesc == (renderer.Style{}) { | |
| 256 | + | selectionDesc = mergePaletteDescStyle(selection, desc) | |
| 257 | + | } | |
| 251 | 258 | ui.FillRect(frame, domain.Rect{Width: frame.Width, Height: frame.Height}, renderer.Cell{Rune: ' ', Style: base}) | |
| 252 | 259 | if m == nil { | |
| 253 | 260 | ui.DrawInputLine(frame, 0, "> ", "", base, selection) | |
| @@ -283,7 +290,7 @@ func (m *Model) Render(inner domain.Size, opts RenderOptions) renderer.Frame { | |||
| 283 | 290 | } | |
| 284 | 291 | ui.FillRect(frame, domain.Rect{Y: y + start, Width: frame.Width, Height: 1}, renderer.Cell{Rune: ' ', Style: style}) | |
| 285 | 292 | if cmd, ok := match.Result.Command(); ok { | |
| 286 | - | m.renderCommand(frame, y+start, style, selection, desc, codeWidth, cmd, match.Positions, activeCmd, activeOK, opts.Guidance, opts.Feedback, idx == m.selected) | |
| 293 | + | m.renderCommand(frame, y+start, style, selection, desc, selectionDesc, codeWidth, cmd, match.Positions, activeCmd, activeOK, opts.Guidance, opts.Feedback, idx == m.selected) | |
| 287 | 294 | continue | |
| 288 | 295 | } | |
| 289 | 296 | m.renderSession(frame, y+start, style, selection, match, opts.Feedback, idx == m.selected) | |
| @@ -291,7 +298,7 @@ func (m *Model) Render(inner domain.Size, opts RenderOptions) renderer.Frame { | |||
| 291 | 298 | return frame | |
| 292 | 299 | } | |
| 293 | 300 | ||
| 294 | - | func (m *Model) renderCommand(frame renderer.Frame, y int, style, selection, desc renderer.Style, codeWidth int, cmd command.Command, positions []int, activeCmd command.Command, activeOK bool, guidance, feedback string, selected bool) { | |
| 301 | + | func (m *Model) renderCommand(frame renderer.Frame, y int, style, selection, desc, selectionDesc renderer.Style, codeWidth int, cmd command.Command, positions []int, activeCmd command.Command, activeOK bool, guidance, feedback string, selected bool) { | |
| 295 | 302 | x, nextHighlight := 0, 0 | |
| 296 | 303 | for _, r := range cmd.Code { | |
| 297 | 304 | cellStyle := style | |
| @@ -316,7 +323,11 @@ func (m *Model) renderCommand(frame renderer.Frame, y int, style, selection, des | |||
| 316 | 323 | } else if guidance != "" && activeOK && cmd.ContextHint != command.ContextHintNone && activeCmd.Code == cmd.Code { | |
| 317 | 324 | description = guidance | |
| 318 | 325 | } | |
| 319 | - | ui.DrawText(frame, x, y, frame.Width, description, mergePaletteDescStyle(style, desc)) | |
| 326 | + | descriptionStyle := mergePaletteDescStyle(style, desc) | |
| 327 | + | if selected { | |
| 328 | + | descriptionStyle = selectionDesc | |
| 329 | + | } | |
| 330 | + | ui.DrawText(frame, x, y, frame.Width, description, descriptionStyle) | |
| 320 | 331 | } | |
| 321 | 332 | ||
| 322 | 333 | func (m *Model) renderSession(frame renderer.Frame, y int, style, selection renderer.Style, match Match, feedback string, selected bool) { | |
| @@ -13,10 +13,11 @@ import ( | |||||
| 13 | type RenderStyles struct { | 13 | type RenderStyles struct { | ||
| 14 | // Base fills unused modal interior cells; Row is the ordinary inactive | 14 | // Base fills unused modal interior cells; Row is the ordinary inactive | ||
| 15 | // result surface. They are separate to retain the chrome hierarchy. | 15 | // result surface. They are separate to retain the chrome hierarchy. | ||
| 16 | - | Base renderer.Style | 16 | + | Base renderer.Style |
| 17 | - | Row renderer.Style | 17 | + | Row renderer.Style |
| 18 | - | Selection renderer.Style | 18 | + | Selection renderer.Style |
| 19 | - | Description renderer.Style | 19 | + | Description renderer.Style |
| 20 | + | SelectionDescription renderer.Style | |||
| 20 | } | 21 | } | ||
| 21 | 22 | ||||
| 22 | // RenderOptions contains presentation supplied by the daemon for one render. | 23 | // RenderOptions contains presentation supplied by the daemon for one render. | ||
| @@ -57,8 +58,10 @@ func DefaultRenderStyles() RenderStyles { | |||||
| 57 | selection.Inverse = true | 58 | selection.Inverse = true | ||
| 58 | description := renderer.DefaultStyle() | 59 | description := renderer.DefaultStyle() | ||
| 59 | description.Italic = true | 60 | description.Italic = true | ||
| 61 | + | selectionDescription := selection | |||
| 62 | + | selectionDescription.Italic = true | |||
| 60 | base := renderer.DefaultStyle() | 63 | base := renderer.DefaultStyle() | ||
| 61 | - | return RenderStyles{Base: base, Row: base, Selection: selection, Description: description} | 64 | + | return RenderStyles{Base: base, Row: base, Selection: selection, Description: description, SelectionDescription: selectionDescription} |
| 62 | } | 65 | } | ||
| 63 | 66 | ||||
| 64 | func (m *Model) Insert(r rune) { | 67 | func (m *Model) Insert(r rune) { | ||
| @@ -248,6 +251,10 @@ func (m *Model) Render(inner domain.Size, opts RenderOptions) renderer.Frame { | |||||
| 248 | return frame | 251 | return frame | ||
| 249 | } | 252 | } | ||
| 250 | base, row, selection, desc := styles.Base, styles.Row, styles.Selection, styles.Description | 253 | base, row, selection, desc := styles.Base, styles.Row, styles.Selection, styles.Description | ||
| 254 | + | selectionDesc := styles.SelectionDescription | |||
| 255 | + | if selectionDesc == (renderer.Style{}) { | |||
| 256 | + | selectionDesc = mergePaletteDescStyle(selection, desc) | |||
| 257 | + | } | |||
| 251 | ui.FillRect(frame, domain.Rect{Width: frame.Width, Height: frame.Height}, renderer.Cell{Rune: ' ', Style: base}) | 258 | ui.FillRect(frame, domain.Rect{Width: frame.Width, Height: frame.Height}, renderer.Cell{Rune: ' ', Style: base}) | ||
| 252 | if m == nil { | 259 | if m == nil { | ||
| 253 | ui.DrawInputLine(frame, 0, "> ", "", base, selection) | 260 | ui.DrawInputLine(frame, 0, "> ", "", base, selection) | ||
| @@ -283,7 +290,7 @@ func (m *Model) Render(inner domain.Size, opts RenderOptions) renderer.Frame { | |||||
| 283 | } | 290 | } | ||
| 284 | ui.FillRect(frame, domain.Rect{Y: y + start, Width: frame.Width, Height: 1}, renderer.Cell{Rune: ' ', Style: style}) | 291 | ui.FillRect(frame, domain.Rect{Y: y + start, Width: frame.Width, Height: 1}, renderer.Cell{Rune: ' ', Style: style}) | ||
| 285 | if cmd, ok := match.Result.Command(); ok { | 292 | if cmd, ok := match.Result.Command(); ok { | ||
| 286 | - | m.renderCommand(frame, y+start, style, selection, desc, codeWidth, cmd, match.Positions, activeCmd, activeOK, opts.Guidance, opts.Feedback, idx == m.selected) | 293 | + | m.renderCommand(frame, y+start, style, selection, desc, selectionDesc, codeWidth, cmd, match.Positions, activeCmd, activeOK, opts.Guidance, opts.Feedback, idx == m.selected) |
| 287 | continue | 294 | continue | ||
| 288 | } | 295 | } | ||
| 289 | m.renderSession(frame, y+start, style, selection, match, opts.Feedback, idx == m.selected) | 296 | m.renderSession(frame, y+start, style, selection, match, opts.Feedback, idx == m.selected) | ||
| @@ -291,7 +298,7 @@ func (m *Model) Render(inner domain.Size, opts RenderOptions) renderer.Frame { | |||||
| 291 | return frame | 298 | return frame | ||
| 292 | } | 299 | } | ||
| 293 | 300 | ||||
| 294 | - | func (m *Model) renderCommand(frame renderer.Frame, y int, style, selection, desc renderer.Style, codeWidth int, cmd command.Command, positions []int, activeCmd command.Command, activeOK bool, guidance, feedback string, selected bool) { | 301 | + | func (m *Model) renderCommand(frame renderer.Frame, y int, style, selection, desc, selectionDesc renderer.Style, codeWidth int, cmd command.Command, positions []int, activeCmd command.Command, activeOK bool, guidance, feedback string, selected bool) { |
| 295 | x, nextHighlight := 0, 0 | 302 | x, nextHighlight := 0, 0 | ||
| 296 | for _, r := range cmd.Code { | 303 | for _, r := range cmd.Code { | ||
| 297 | cellStyle := style | 304 | cellStyle := style | ||
| @@ -316,7 +323,11 @@ func (m *Model) renderCommand(frame renderer.Frame, y int, style, selection, des | |||||
| 316 | } else if guidance != "" && activeOK && cmd.ContextHint != command.ContextHintNone && activeCmd.Code == cmd.Code { | 323 | } else if guidance != "" && activeOK && cmd.ContextHint != command.ContextHintNone && activeCmd.Code == cmd.Code { | ||
| 317 | description = guidance | 324 | description = guidance | ||
| 318 | } | 325 | } | ||
| 319 | - | ui.DrawText(frame, x, y, frame.Width, description, mergePaletteDescStyle(style, desc)) | 326 | + | descriptionStyle := mergePaletteDescStyle(style, desc) |
| 327 | + | if selected { | |||
| 328 | + | descriptionStyle = selectionDesc | |||
| 329 | + | } | |||
| 330 | + | ui.DrawText(frame, x, y, frame.Width, description, descriptionStyle) | |||
| 320 | } | 331 | } | ||
| 321 | 332 | ||||
| 322 | func (m *Model) renderSession(frame renderer.Frame, y int, style, selection renderer.Style, match Match, feedback string, selected bool) { | 333 | func (m *Model) renderSession(frame renderer.Frame, y int, style, selection renderer.Style, match Match, feedback string, selected bool) { | ||
M internal/usecase/palette/model_test.go +84 -15
| @@ -203,10 +203,11 @@ func frameRow(frame renderer.Frame, y int) string { | |||
| 203 | 203 | ||
| 204 | 204 | func TestRenderUsesConfiguredStyles(t *testing.T) { | |
| 205 | 205 | tests := []struct { | |
| 206 | - | name string | |
| 207 | - | styles func() RenderStyles | |
| 208 | - | x int | |
| 209 | - | assert func(t *testing.T, style renderer.Style) | |
| 206 | + | name string | |
| 207 | + | styles func() RenderStyles | |
| 208 | + | x, y int | |
| 209 | + | unfiltered bool | |
| 210 | + | assert func(t *testing.T, style renderer.Style) | |
| 210 | 211 | }{ | |
| 211 | 212 | { | |
| 212 | 213 | name: "selection style for fuzzy highlights", | |
| @@ -219,6 +220,7 @@ func TestRenderUsesConfiguredStyles(t *testing.T) { | |||
| 219 | 220 | return styles | |
| 220 | 221 | }, | |
| 221 | 222 | x: 0, | |
| 223 | + | y: 1, | |
| 222 | 224 | assert: func(t *testing.T, style renderer.Style) { | |
| 223 | 225 | require.True(t, style.Bold) | |
| 224 | 226 | require.False(t, style.Inverse) | |
| @@ -237,7 +239,9 @@ func TestRenderUsesConfiguredStyles(t *testing.T) { | |||
| 237 | 239 | styles.Description = muted | |
| 238 | 240 | return styles | |
| 239 | 241 | }, | |
| 240 | - | x: 4, | |
| 242 | + | x: 4, | |
| 243 | + | y: 2, | |
| 244 | + | unfiltered: true, | |
| 241 | 245 | assert: func(t *testing.T, style renderer.Style) { | |
| 242 | 246 | require.True(t, style.Italic) | |
| 243 | 247 | require.True(t, style.HasForegroundRGB) | |
| @@ -250,9 +254,11 @@ func TestRenderUsesConfiguredStyles(t *testing.T) { | |||
| 250 | 254 | styles: func() RenderStyles { | |
| 251 | 255 | styles := DefaultRenderStyles() | |
| 252 | 256 | styles.Description.Italic = false | |
| 257 | + | styles.SelectionDescription.Italic = false | |
| 253 | 258 | return styles | |
| 254 | 259 | }, | |
| 255 | 260 | x: 4, | |
| 261 | + | y: 1, | |
| 256 | 262 | assert: func(t *testing.T, style renderer.Style) { | |
| 257 | 263 | require.False(t, style.Italic) | |
| 258 | 264 | }, | |
| @@ -261,16 +267,75 @@ func TestRenderUsesConfiguredStyles(t *testing.T) { | |||
| 261 | 267 | ||
| 262 | 268 | for _, tt := range tests { | |
| 263 | 269 | t.Run(tt.name, func(t *testing.T) { | |
| 264 | - | m := New(CommandResults([]command.Command{cmd("CPY", "Copy", "Enter copy mode")})) | |
| 265 | - | m.Insert('c') | |
| 266 | - | m.Insert('y') | |
| 270 | + | m := New(CommandResults([]command.Command{ | |
| 271 | + | cmd("CPY", "Copy", "Enter copy mode"), | |
| 272 | + | cmd("CNT", "New", "Create tab"), | |
| 273 | + | })) | |
| 274 | + | if !tt.unfiltered { | |
| 275 | + | m.Insert('c') | |
| 276 | + | m.Insert('y') | |
| 277 | + | } | |
| 267 | 278 | frame := m.Render(domain.Size{Cols: 28, Rows: 3}, RenderOptions{Styles: tt.styles()}) | |
| 268 | 279 | ||
| 269 | - | tt.assert(t, frame.At(tt.x, 1).Style) | |
| 280 | + | tt.assert(t, frame.At(tt.x, tt.y).Style) | |
| 270 | 281 | }) | |
| 271 | 282 | } | |
| 272 | 283 | } | |
| 273 | 284 | ||
| 285 | + | func TestPaletteDescriptionUsesSelectedRowForeground(t *testing.T) { | |
| 286 | + | selection := renderer.DefaultStyle() | |
| 287 | + | selection.HasForegroundRGB = true | |
| 288 | + | selection.ForegroundRGB = renderer.RGB{R: 240, G: 241, B: 242} | |
| 289 | + | selection.HasBackgroundRGB = true | |
| 290 | + | selection.BackgroundRGB = renderer.RGB{R: 20, G: 21, B: 22} | |
| 291 | + | description := renderer.DefaultStyle() | |
| 292 | + | description.Italic = true | |
| 293 | + | description.HasForegroundRGB = true | |
| 294 | + | description.ForegroundRGB = renderer.RGB{R: 100, G: 101, B: 102} | |
| 295 | + | selectionDescription := selection | |
| 296 | + | selectionDescription.Italic = true | |
| 297 | + | selectionDescription.ForegroundRGB = renderer.RGB{R: 220, G: 221, B: 222} | |
| 298 | + | ||
| 299 | + | model := New(CommandResults([]command.Command{ | |
| 300 | + | cmd("ONE", "One", "first description"), | |
| 301 | + | cmd("TWO", "Two", "second description"), | |
| 302 | + | })) | |
| 303 | + | frame := model.Render(domain.Size{Cols: 32, Rows: 3}, RenderOptions{Styles: RenderStyles{ | |
| 304 | + | Base: renderer.DefaultStyle(), Row: renderer.DefaultStyle(), | |
| 305 | + | Selection: selection, Description: description, SelectionDescription: selectionDescription, | |
| 306 | + | }}) | |
| 307 | + | ||
| 308 | + | selected := frame.At(4, 1).Style | |
| 309 | + | require.True(t, selected.Equal(selectionDescription)) | |
| 310 | + | ||
| 311 | + | inactive := frame.At(4, 2).Style | |
| 312 | + | require.Equal(t, description.ForegroundRGB, inactive.ForegroundRGB) | |
| 313 | + | require.True(t, inactive.Italic) | |
| 314 | + | } | |
| 315 | + | ||
| 316 | + | func TestPaletteDescriptionWithoutSelectionStylePreservesDescriptionForeground(t *testing.T) { | |
| 317 | + | selection := renderer.DefaultStyle() | |
| 318 | + | selection.HasForegroundRGB = true | |
| 319 | + | selection.ForegroundRGB = renderer.RGB{R: 240, G: 241, B: 242} | |
| 320 | + | selection.HasBackgroundRGB = true | |
| 321 | + | selection.BackgroundRGB = renderer.RGB{R: 20, G: 21, B: 22} | |
| 322 | + | description := renderer.DefaultStyle() | |
| 323 | + | description.Italic = true | |
| 324 | + | description.HasForegroundRGB = true | |
| 325 | + | description.ForegroundRGB = renderer.RGB{R: 100, G: 101, B: 102} | |
| 326 | + | ||
| 327 | + | model := New(CommandResults([]command.Command{cmd("ONE", "One", "first description")})) | |
| 328 | + | frame := model.Render(domain.Size{Cols: 32, Rows: 2}, RenderOptions{Styles: RenderStyles{ | |
| 329 | + | Base: renderer.DefaultStyle(), Row: renderer.DefaultStyle(), | |
| 330 | + | Selection: selection, Description: description, | |
| 331 | + | }}) | |
| 332 | + | ||
| 333 | + | selected := frame.At(4, 1).Style | |
| 334 | + | require.Equal(t, description.ForegroundRGB, selected.ForegroundRGB) | |
| 335 | + | require.Equal(t, selection.BackgroundRGB, selected.BackgroundRGB) | |
| 336 | + | require.True(t, selected.Italic) | |
| 337 | + | } | |
| 338 | + | ||
| 274 | 339 | func TestPaletteDescriptionKeepsInactiveRowSurfaceAcrossFallbacks(t *testing.T) { | |
| 275 | 340 | paletteColors := [16]renderer.RGB{} | |
| 276 | 341 | paletteColors[2] = renderer.RGB{R: 10, G: 230, B: 120} | |
| @@ -311,15 +376,19 @@ func TestPaletteDescriptionKeepsInactiveRowSurfaceAcrossFallbacks(t *testing.T) | |||
| 311 | 376 | frame := model.Render(domain.Size{Cols: 32, Rows: 3}, RenderOptions{Styles: RenderStyles{ | |
| 312 | 377 | Base: styles.PickerBase, Row: styles.SurfaceInactive, | |
| 313 | 378 | Selection: styles.PickerSelection, Description: styles.PickerDescription, | |
| 379 | + | SelectionDescription: styles.PickerSelectionMuted, | |
| 314 | 380 | }}) | |
| 315 | - | description := frame.At(4, 1).Style | |
| 316 | - | require.Equal(t, styles.SurfaceInactive.Background, description.Background) | |
| 317 | - | require.Equal(t, styles.SurfaceInactive.HasBackgroundRGB, description.HasBackgroundRGB) | |
| 318 | - | require.Equal(t, styles.SurfaceInactive.BackgroundRGB, description.BackgroundRGB) | |
| 381 | + | inactive := frame.At(4, 1).Style | |
| 382 | + | require.Equal(t, styles.SurfaceInactive.Background, inactive.Background) | |
| 383 | + | require.Equal(t, styles.SurfaceInactive.HasBackgroundRGB, inactive.HasBackgroundRGB) | |
| 384 | + | require.Equal(t, styles.SurfaceInactive.BackgroundRGB, inactive.BackgroundRGB) | |
| 319 | 385 | if tt.name == "indexed only" { | |
| 320 | - | require.Equal(t, 2, description.Foreground) | |
| 321 | - | require.False(t, description.HasBackgroundRGB) | |
| 386 | + | require.Equal(t, 2, inactive.Foreground) | |
| 387 | + | require.False(t, inactive.HasBackgroundRGB) | |
| 322 | 388 | } | |
| 389 | + | ||
| 390 | + | selected := frame.At(4, 2).Style | |
| 391 | + | require.True(t, selected.Equal(styles.PickerSelectionMuted)) | |
| 323 | 392 | }) | |
| 324 | 393 | } | |
| 325 | 394 | } | |
| @@ -203,10 +203,11 @@ func frameRow(frame renderer.Frame, y int) string { | |||||
| 203 | 203 | ||||
| 204 | func TestRenderUsesConfiguredStyles(t *testing.T) { | 204 | func TestRenderUsesConfiguredStyles(t *testing.T) { | ||
| 205 | tests := []struct { | 205 | tests := []struct { | ||
| 206 | - | name string | 206 | + | name string |
| 207 | - | styles func() RenderStyles | 207 | + | styles func() RenderStyles |
| 208 | - | x int | 208 | + | x, y int |
| 209 | - | assert func(t *testing.T, style renderer.Style) | 209 | + | unfiltered bool |
| 210 | + | assert func(t *testing.T, style renderer.Style) | |||
| 210 | }{ | 211 | }{ | ||
| 211 | { | 212 | { | ||
| 212 | name: "selection style for fuzzy highlights", | 213 | name: "selection style for fuzzy highlights", | ||
| @@ -219,6 +220,7 @@ func TestRenderUsesConfiguredStyles(t *testing.T) { | |||||
| 219 | return styles | 220 | return styles | ||
| 220 | }, | 221 | }, | ||
| 221 | x: 0, | 222 | x: 0, | ||
| 223 | + | y: 1, | |||
| 222 | assert: func(t *testing.T, style renderer.Style) { | 224 | assert: func(t *testing.T, style renderer.Style) { | ||
| 223 | require.True(t, style.Bold) | 225 | require.True(t, style.Bold) | ||
| 224 | require.False(t, style.Inverse) | 226 | require.False(t, style.Inverse) | ||
| @@ -237,7 +239,9 @@ func TestRenderUsesConfiguredStyles(t *testing.T) { | |||||
| 237 | styles.Description = muted | 239 | styles.Description = muted | ||
| 238 | return styles | 240 | return styles | ||
| 239 | }, | 241 | }, | ||
| 240 | - | x: 4, | 242 | + | x: 4, |
| 243 | + | y: 2, | |||
| 244 | + | unfiltered: true, | |||
| 241 | assert: func(t *testing.T, style renderer.Style) { | 245 | assert: func(t *testing.T, style renderer.Style) { | ||
| 242 | require.True(t, style.Italic) | 246 | require.True(t, style.Italic) | ||
| 243 | require.True(t, style.HasForegroundRGB) | 247 | require.True(t, style.HasForegroundRGB) | ||
| @@ -250,9 +254,11 @@ func TestRenderUsesConfiguredStyles(t *testing.T) { | |||||
| 250 | styles: func() RenderStyles { | 254 | styles: func() RenderStyles { | ||
| 251 | styles := DefaultRenderStyles() | 255 | styles := DefaultRenderStyles() | ||
| 252 | styles.Description.Italic = false | 256 | styles.Description.Italic = false | ||
| 257 | + | styles.SelectionDescription.Italic = false | |||
| 253 | return styles | 258 | return styles | ||
| 254 | }, | 259 | }, | ||
| 255 | x: 4, | 260 | x: 4, | ||
| 261 | + | y: 1, | |||
| 256 | assert: func(t *testing.T, style renderer.Style) { | 262 | assert: func(t *testing.T, style renderer.Style) { | ||
| 257 | require.False(t, style.Italic) | 263 | require.False(t, style.Italic) | ||
| 258 | }, | 264 | }, | ||
| @@ -261,16 +267,75 @@ func TestRenderUsesConfiguredStyles(t *testing.T) { | |||||
| 261 | 267 | ||||
| 262 | for _, tt := range tests { | 268 | for _, tt := range tests { | ||
| 263 | t.Run(tt.name, func(t *testing.T) { | 269 | t.Run(tt.name, func(t *testing.T) { | ||
| 264 | - | m := New(CommandResults([]command.Command{cmd("CPY", "Copy", "Enter copy mode")})) | 270 | + | m := New(CommandResults([]command.Command{ |
| 265 | - | m.Insert('c') | 271 | + | cmd("CPY", "Copy", "Enter copy mode"), |
| 266 | - | m.Insert('y') | 272 | + | cmd("CNT", "New", "Create tab"), |
| 273 | + | })) | |||
| 274 | + | if !tt.unfiltered { | |||
| 275 | + | m.Insert('c') | |||
| 276 | + | m.Insert('y') | |||
| 277 | + | } | |||
| 267 | frame := m.Render(domain.Size{Cols: 28, Rows: 3}, RenderOptions{Styles: tt.styles()}) | 278 | frame := m.Render(domain.Size{Cols: 28, Rows: 3}, RenderOptions{Styles: tt.styles()}) | ||
| 268 | 279 | ||||
| 269 | - | tt.assert(t, frame.At(tt.x, 1).Style) | 280 | + | tt.assert(t, frame.At(tt.x, tt.y).Style) |
| 270 | }) | 281 | }) | ||
| 271 | } | 282 | } | ||
| 272 | } | 283 | } | ||
| 273 | 284 | ||||
| 285 | + | func TestPaletteDescriptionUsesSelectedRowForeground(t *testing.T) { | |||
| 286 | + | selection := renderer.DefaultStyle() | |||
| 287 | + | selection.HasForegroundRGB = true | |||
| 288 | + | selection.ForegroundRGB = renderer.RGB{R: 240, G: 241, B: 242} | |||
| 289 | + | selection.HasBackgroundRGB = true | |||
| 290 | + | selection.BackgroundRGB = renderer.RGB{R: 20, G: 21, B: 22} | |||
| 291 | + | description := renderer.DefaultStyle() | |||
| 292 | + | description.Italic = true | |||
| 293 | + | description.HasForegroundRGB = true | |||
| 294 | + | description.ForegroundRGB = renderer.RGB{R: 100, G: 101, B: 102} | |||
| 295 | + | selectionDescription := selection | |||
| 296 | + | selectionDescription.Italic = true | |||
| 297 | + | selectionDescription.ForegroundRGB = renderer.RGB{R: 220, G: 221, B: 222} | |||
| 298 | + | ||||
| 299 | + | model := New(CommandResults([]command.Command{ | |||
| 300 | + | cmd("ONE", "One", "first description"), | |||
| 301 | + | cmd("TWO", "Two", "second description"), | |||
| 302 | + | })) | |||
| 303 | + | frame := model.Render(domain.Size{Cols: 32, Rows: 3}, RenderOptions{Styles: RenderStyles{ | |||
| 304 | + | Base: renderer.DefaultStyle(), Row: renderer.DefaultStyle(), | |||
| 305 | + | Selection: selection, Description: description, SelectionDescription: selectionDescription, | |||
| 306 | + | }}) | |||
| 307 | + | ||||
| 308 | + | selected := frame.At(4, 1).Style | |||
| 309 | + | require.True(t, selected.Equal(selectionDescription)) | |||
| 310 | + | ||||
| 311 | + | inactive := frame.At(4, 2).Style | |||
| 312 | + | require.Equal(t, description.ForegroundRGB, inactive.ForegroundRGB) | |||
| 313 | + | require.True(t, inactive.Italic) | |||
| 314 | + | } | |||
| 315 | + | ||||
| 316 | + | func TestPaletteDescriptionWithoutSelectionStylePreservesDescriptionForeground(t *testing.T) { | |||
| 317 | + | selection := renderer.DefaultStyle() | |||
| 318 | + | selection.HasForegroundRGB = true | |||
| 319 | + | selection.ForegroundRGB = renderer.RGB{R: 240, G: 241, B: 242} | |||
| 320 | + | selection.HasBackgroundRGB = true | |||
| 321 | + | selection.BackgroundRGB = renderer.RGB{R: 20, G: 21, B: 22} | |||
| 322 | + | description := renderer.DefaultStyle() | |||
| 323 | + | description.Italic = true | |||
| 324 | + | description.HasForegroundRGB = true | |||
| 325 | + | description.ForegroundRGB = renderer.RGB{R: 100, G: 101, B: 102} | |||
| 326 | + | ||||
| 327 | + | model := New(CommandResults([]command.Command{cmd("ONE", "One", "first description")})) | |||
| 328 | + | frame := model.Render(domain.Size{Cols: 32, Rows: 2}, RenderOptions{Styles: RenderStyles{ | |||
| 329 | + | Base: renderer.DefaultStyle(), Row: renderer.DefaultStyle(), | |||
| 330 | + | Selection: selection, Description: description, | |||
| 331 | + | }}) | |||
| 332 | + | ||||
| 333 | + | selected := frame.At(4, 1).Style | |||
| 334 | + | require.Equal(t, description.ForegroundRGB, selected.ForegroundRGB) | |||
| 335 | + | require.Equal(t, selection.BackgroundRGB, selected.BackgroundRGB) | |||
| 336 | + | require.True(t, selected.Italic) | |||
| 337 | + | } | |||
| 338 | + | ||||
| 274 | func TestPaletteDescriptionKeepsInactiveRowSurfaceAcrossFallbacks(t *testing.T) { | 339 | func TestPaletteDescriptionKeepsInactiveRowSurfaceAcrossFallbacks(t *testing.T) { | ||
| 275 | paletteColors := [16]renderer.RGB{} | 340 | paletteColors := [16]renderer.RGB{} | ||
| 276 | paletteColors[2] = renderer.RGB{R: 10, G: 230, B: 120} | 341 | paletteColors[2] = renderer.RGB{R: 10, G: 230, B: 120} | ||
| @@ -311,15 +376,19 @@ func TestPaletteDescriptionKeepsInactiveRowSurfaceAcrossFallbacks(t *testing.T) | |||||
| 311 | frame := model.Render(domain.Size{Cols: 32, Rows: 3}, RenderOptions{Styles: RenderStyles{ | 376 | frame := model.Render(domain.Size{Cols: 32, Rows: 3}, RenderOptions{Styles: RenderStyles{ | ||
| 312 | Base: styles.PickerBase, Row: styles.SurfaceInactive, | 377 | Base: styles.PickerBase, Row: styles.SurfaceInactive, | ||
| 313 | Selection: styles.PickerSelection, Description: styles.PickerDescription, | 378 | Selection: styles.PickerSelection, Description: styles.PickerDescription, | ||
| 379 | + | SelectionDescription: styles.PickerSelectionMuted, | |||
| 314 | }}) | 380 | }}) | ||
| 315 | - | description := frame.At(4, 1).Style | 381 | + | inactive := frame.At(4, 1).Style |
| 316 | - | require.Equal(t, styles.SurfaceInactive.Background, description.Background) | 382 | + | require.Equal(t, styles.SurfaceInactive.Background, inactive.Background) |
| 317 | - | require.Equal(t, styles.SurfaceInactive.HasBackgroundRGB, description.HasBackgroundRGB) | 383 | + | require.Equal(t, styles.SurfaceInactive.HasBackgroundRGB, inactive.HasBackgroundRGB) |
| 318 | - | require.Equal(t, styles.SurfaceInactive.BackgroundRGB, description.BackgroundRGB) | 384 | + | require.Equal(t, styles.SurfaceInactive.BackgroundRGB, inactive.BackgroundRGB) |
| 319 | if tt.name == "indexed only" { | 385 | if tt.name == "indexed only" { | ||
| 320 | - | require.Equal(t, 2, description.Foreground) | 386 | + | require.Equal(t, 2, inactive.Foreground) |
| 321 | - | require.False(t, description.HasBackgroundRGB) | 387 | + | require.False(t, inactive.HasBackgroundRGB) |
| 322 | } | 388 | } | ||
| 389 | + | ||||
| 390 | + | selected := frame.At(4, 2).Style | |||
| 391 | + | require.True(t, selected.Equal(styles.PickerSelectionMuted)) | |||
| 323 | }) | 392 | }) | ||
| 324 | } | 393 | } | ||
| 325 | } | 394 | } | ||