mirror of
https://github.com/ddworken/hishtory.git
synced 2025-02-02 11:39:24 +01:00
* Add support for configuring the TUI color scheme, for #134 * Add tests for getting and setting the custom color scheme, and support full colors where terminals support them * Add comments to document termenv.ANSI setting, and fix tests so they work uniformly
This commit is contained in:
parent
49fd540014
commit
8b7e54eab4
@ -1761,6 +1761,15 @@ func testTui_color(t *testing.T) {
|
|||||||
out = captureTerminalOutputComplex(t, TmuxCaptureConfig{tester: tester, complexCommands: []TmuxCommand{{Keys: "hishtory SPACE tquery ENTER"}, {Keys: "ech"}}, includeEscapeSequences: true})
|
out = captureTerminalOutputComplex(t, TmuxCaptureConfig{tester: tester, complexCommands: []TmuxCommand{{Keys: "hishtory SPACE tquery ENTER"}, {Keys: "ech"}}, includeEscapeSequences: true})
|
||||||
out = stripTuiCommandPrefix(t, out)
|
out = stripTuiCommandPrefix(t, out)
|
||||||
testutils.CompareGoldens(t, out, "TestTui-ColoredOutputWithSearch-BetaMode")
|
testutils.CompareGoldens(t, out, "TestTui-ColoredOutputWithSearch-BetaMode")
|
||||||
|
|
||||||
|
// And one more time with customized colors
|
||||||
|
testutils.CompareGoldens(t, tester.RunInteractiveShell(t, ` hishtory config-get color-scheme`), "TestTui-DefaultColorScheme")
|
||||||
|
tester.RunInteractiveShell(t, ` hishtory config-set color-scheme selected-text #45f542`)
|
||||||
|
tester.RunInteractiveShell(t, ` hishtory config-set color-scheme selected-background #4842f5`)
|
||||||
|
tester.RunInteractiveShell(t, ` hishtory config-set color-scheme border-color #f54272`)
|
||||||
|
out = captureTerminalOutputComplex(t, TmuxCaptureConfig{tester: tester, complexCommands: []TmuxCommand{{Keys: "hishtory SPACE tquery ENTER"}, {Keys: "ech"}}, includeEscapeSequences: true})
|
||||||
|
out = stripTuiCommandPrefix(t, out)
|
||||||
|
testutils.CompareGoldens(t, out, "TestTui-CustomColorScheme")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTui_delete(t *testing.T) {
|
func testTui_delete(t *testing.T) {
|
||||||
|
@ -2,6 +2,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ddworken/hishtory/client/hctx"
|
"github.com/ddworken/hishtory/client/hctx"
|
||||||
@ -15,6 +16,7 @@ var configAddCmd = &cobra.Command{
|
|||||||
GroupID: GROUP_ID_CONFIG,
|
GroupID: GROUP_ID_CONFIG,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
lib.CheckFatalError(cmd.Help())
|
lib.CheckFatalError(cmd.Help())
|
||||||
|
os.Exit(1)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/ddworken/hishtory/client/hctx"
|
"github.com/ddworken/hishtory/client/hctx"
|
||||||
"github.com/ddworken/hishtory/client/lib"
|
"github.com/ddworken/hishtory/client/lib"
|
||||||
@ -14,6 +15,7 @@ var configDeleteCmd = &cobra.Command{
|
|||||||
GroupID: GROUP_ID_CONFIG,
|
GroupID: GROUP_ID_CONFIG,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
lib.CheckFatalError(cmd.Help())
|
lib.CheckFatalError(cmd.Help())
|
||||||
|
os.Exit(1)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ddworken/hishtory/client/hctx"
|
"github.com/ddworken/hishtory/client/hctx"
|
||||||
@ -17,6 +18,7 @@ var configGetCmd = &cobra.Command{
|
|||||||
GroupID: GROUP_ID_CONFIG,
|
GroupID: GROUP_ID_CONFIG,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
lib.CheckFatalError(cmd.Help())
|
lib.CheckFatalError(cmd.Help())
|
||||||
|
os.Exit(1)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +123,18 @@ var getCustomColumnsCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var getColorScheme = &cobra.Command{
|
||||||
|
Use: "color-scheme",
|
||||||
|
Short: "Get the currently configured color scheme for selected text in the TUI",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
ctx := hctx.MakeContext()
|
||||||
|
config := hctx.GetConf(ctx)
|
||||||
|
fmt.Println("selected-text: " + config.ColorScheme.SelectedText)
|
||||||
|
fmt.Println("selected-background: " + config.ColorScheme.SelectedBackground)
|
||||||
|
fmt.Println("border-color: " + config.ColorScheme.BorderColor)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(configGetCmd)
|
rootCmd.AddCommand(configGetCmd)
|
||||||
configGetCmd.AddCommand(getEnableControlRCmd)
|
configGetCmd.AddCommand(getEnableControlRCmd)
|
||||||
@ -132,4 +146,5 @@ func init() {
|
|||||||
configGetCmd.AddCommand(getHighlightMatchesCmd)
|
configGetCmd.AddCommand(getHighlightMatchesCmd)
|
||||||
configGetCmd.AddCommand(getEnableAiCompletion)
|
configGetCmd.AddCommand(getEnableAiCompletion)
|
||||||
configGetCmd.AddCommand(getPresavingCmd)
|
configGetCmd.AddCommand(getPresavingCmd)
|
||||||
|
configGetCmd.AddCommand(getColorScheme)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/ddworken/hishtory/client/hctx"
|
"github.com/ddworken/hishtory/client/hctx"
|
||||||
"github.com/ddworken/hishtory/client/lib"
|
"github.com/ddworken/hishtory/client/lib"
|
||||||
@ -15,6 +17,7 @@ var configSetCmd = &cobra.Command{
|
|||||||
GroupID: GROUP_ID_CONFIG,
|
GroupID: GROUP_ID_CONFIG,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
lib.CheckFatalError(cmd.Help())
|
lib.CheckFatalError(cmd.Help())
|
||||||
|
os.Exit(1)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,6 +149,61 @@ var setTimestampFormatCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var setColorSchemeCmd = &cobra.Command{
|
||||||
|
Use: "color-scheme",
|
||||||
|
Short: "Set a custom color scheme",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
lib.CheckFatalError(cmd.Help())
|
||||||
|
os.Exit(1)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var setColorSchemeSelectedText = &cobra.Command{
|
||||||
|
Use: "selected-text",
|
||||||
|
Short: "Set the color of the selected text to the given hexadecimal color",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
lib.CheckFatalError(validateColor(args[0]))
|
||||||
|
ctx := hctx.MakeContext()
|
||||||
|
config := hctx.GetConf(ctx)
|
||||||
|
config.ColorScheme.SelectedText = args[0]
|
||||||
|
lib.CheckFatalError(hctx.SetConfig(config))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var setColorSchemeSelectedBackground = &cobra.Command{
|
||||||
|
Use: "selected-background",
|
||||||
|
Short: "Set the background color of the selected row to the given hexadecimal color",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
lib.CheckFatalError(validateColor(args[0]))
|
||||||
|
ctx := hctx.MakeContext()
|
||||||
|
config := hctx.GetConf(ctx)
|
||||||
|
config.ColorScheme.SelectedBackground = args[0]
|
||||||
|
lib.CheckFatalError(hctx.SetConfig(config))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var setColorSchemeBorderColor = &cobra.Command{
|
||||||
|
Use: "border-color",
|
||||||
|
Short: "Set the color of the table borders",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
lib.CheckFatalError(validateColor(args[0]))
|
||||||
|
ctx := hctx.MakeContext()
|
||||||
|
config := hctx.GetConf(ctx)
|
||||||
|
config.ColorScheme.BorderColor = args[0]
|
||||||
|
lib.CheckFatalError(hctx.SetConfig(config))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateColor(color string) error {
|
||||||
|
if !strings.HasPrefix(color, "#") || len(color) != 7 {
|
||||||
|
return fmt.Errorf("color %q is invalid, it should be a hexadecimal color like #663399", color)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(configSetCmd)
|
rootCmd.AddCommand(configSetCmd)
|
||||||
configSetCmd.AddCommand(setEnableControlRCmd)
|
configSetCmd.AddCommand(setEnableControlRCmd)
|
||||||
@ -156,4 +214,8 @@ func init() {
|
|||||||
configSetCmd.AddCommand(setHighlightMatchesCmd)
|
configSetCmd.AddCommand(setHighlightMatchesCmd)
|
||||||
configSetCmd.AddCommand(setEnableAiCompletionCmd)
|
configSetCmd.AddCommand(setEnableAiCompletionCmd)
|
||||||
configSetCmd.AddCommand(setPresavingCmd)
|
configSetCmd.AddCommand(setPresavingCmd)
|
||||||
|
configSetCmd.AddCommand(setColorSchemeCmd)
|
||||||
|
setColorSchemeCmd.AddCommand(setColorSchemeSelectedText)
|
||||||
|
setColorSchemeCmd.AddCommand(setColorSchemeSelectedBackground)
|
||||||
|
setColorSchemeCmd.AddCommand(setColorSchemeBorderColor)
|
||||||
}
|
}
|
||||||
|
@ -201,6 +201,14 @@ type ClientConfig struct {
|
|||||||
AiCompletion bool `json:"ai_completion"`
|
AiCompletion bool `json:"ai_completion"`
|
||||||
// Whether to enable presaving
|
// Whether to enable presaving
|
||||||
EnablePresaving bool `json:"enable_presaving"`
|
EnablePresaving bool `json:"enable_presaving"`
|
||||||
|
// The current color scheme for the TUI
|
||||||
|
ColorScheme ColorScheme `json:"color_scheme"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ColorScheme struct {
|
||||||
|
SelectedText string
|
||||||
|
SelectedBackground string
|
||||||
|
BorderColor string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomColumnDefinition struct {
|
type CustomColumnDefinition struct {
|
||||||
@ -229,6 +237,14 @@ func GetConfigContents() ([]byte, error) {
|
|||||||
return dat, nil
|
return dat, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetDefaultColorScheme() ColorScheme {
|
||||||
|
return ColorScheme{
|
||||||
|
SelectedBackground: "#3300ff",
|
||||||
|
SelectedText: "#ffff99",
|
||||||
|
BorderColor: "#585858",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func GetConfig() (ClientConfig, error) {
|
func GetConfig() (ClientConfig, error) {
|
||||||
data, err := GetConfigContents()
|
data, err := GetConfigContents()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -245,6 +261,15 @@ func GetConfig() (ClientConfig, error) {
|
|||||||
if config.TimestampFormat == "" {
|
if config.TimestampFormat == "" {
|
||||||
config.TimestampFormat = "Jan 2 2006 15:04:05 MST"
|
config.TimestampFormat = "Jan 2 2006 15:04:05 MST"
|
||||||
}
|
}
|
||||||
|
if config.ColorScheme.SelectedBackground == "" {
|
||||||
|
config.ColorScheme.SelectedBackground = GetDefaultColorScheme().SelectedBackground
|
||||||
|
}
|
||||||
|
if config.ColorScheme.SelectedText == "" {
|
||||||
|
config.ColorScheme.SelectedText = GetDefaultColorScheme().SelectedText
|
||||||
|
}
|
||||||
|
if config.ColorScheme.BorderColor == "" {
|
||||||
|
config.ColorScheme.BorderColor = GetDefaultColorScheme().BorderColor
|
||||||
|
}
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
27
client/lib/goldens/TestTui-CustomColorScheme
Normal file
27
client/lib/goldens/TestTui-CustomColorScheme
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Search Query: > ech[7m [0m[39m[49m
|
||||||
|
|
||||||
|
[91m┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│[39m Hostname CWD Timestamp Runtime Exit Code Command [91m│
|
||||||
|
│────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
|
│[92m[104m localhost [39m [92m/tmp/ [39m [92mOct 17 2022 21:43:21 PDT [39m [92m3s [39m [92m2 [39m [1m[92mech[0m[92m[104mo 'aaaaaa bbbb' [39m [91m[49m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
│[39m [91m│
|
||||||
|
└────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
[90mhiSHtory: Search your shell history[39m [90m • ctrl+h[39m [90mhelp
|
3
client/lib/goldens/TestTui-DefaultColorScheme
Normal file
3
client/lib/goldens/TestTui-DefaultColorScheme
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
selected-text: #ffff99
|
||||||
|
selected-background: #3300ff
|
||||||
|
border-color: #585858
|
@ -41,10 +41,6 @@ var LAST_DISPATCHED_QUERY_ID = 0
|
|||||||
var LAST_DISPATCHED_QUERY_TIMESTAMP time.Time
|
var LAST_DISPATCHED_QUERY_TIMESTAMP time.Time
|
||||||
var LAST_PROCESSED_QUERY_ID = -1
|
var LAST_PROCESSED_QUERY_ID = -1
|
||||||
|
|
||||||
var baseStyle = lipgloss.NewStyle().
|
|
||||||
BorderStyle(lipgloss.NormalBorder()).
|
|
||||||
BorderForeground(lipgloss.Color("240"))
|
|
||||||
|
|
||||||
type keyMap struct {
|
type keyMap struct {
|
||||||
Up key.Binding
|
Up key.Binding
|
||||||
Down key.Binding
|
Down key.Binding
|
||||||
@ -456,11 +452,18 @@ func isCompactHeightMode() bool {
|
|||||||
return height < 25
|
return height < 25
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getBaseStyle(config hctx.ClientConfig) lipgloss.Style {
|
||||||
|
return lipgloss.NewStyle().
|
||||||
|
BorderStyle(lipgloss.NormalBorder()).
|
||||||
|
BorderForeground(lipgloss.Color(config.ColorScheme.BorderColor))
|
||||||
|
}
|
||||||
|
|
||||||
func renderNullableTable(m model, helpText string) string {
|
func renderNullableTable(m model, helpText string) string {
|
||||||
if m.table == nil {
|
if m.table == nil {
|
||||||
return strings.Repeat("\n", TABLE_HEIGHT+3)
|
return strings.Repeat("\n", TABLE_HEIGHT+3)
|
||||||
}
|
}
|
||||||
helpTextLen := strings.Count(helpText, "\n")
|
helpTextLen := strings.Count(helpText, "\n")
|
||||||
|
baseStyle := getBaseStyle(*hctx.GetConf(m.ctx))
|
||||||
if isCompactHeightMode() && helpTextLen > 1 {
|
if isCompactHeightMode() && helpTextLen > 1 {
|
||||||
// If the help text is expanded, and this is a small window, then we truncate the table so that the help text displays on top of it
|
// If the help text is expanded, and this is a small window, then we truncate the table so that the help text displays on top of it
|
||||||
lines := strings.Split(baseStyle.Render(m.table.View()), "\n")
|
lines := strings.Split(baseStyle.Render(m.table.View()), "\n")
|
||||||
@ -692,12 +695,12 @@ func makeTable(ctx context.Context, rows []table.Row) (table.Model, error) {
|
|||||||
s := table.DefaultStyles()
|
s := table.DefaultStyles()
|
||||||
s.Header = s.Header.
|
s.Header = s.Header.
|
||||||
BorderStyle(lipgloss.NormalBorder()).
|
BorderStyle(lipgloss.NormalBorder()).
|
||||||
BorderForeground(lipgloss.Color("240")).
|
BorderForeground(lipgloss.Color(config.ColorScheme.BorderColor)).
|
||||||
BorderBottom(true).
|
BorderBottom(true).
|
||||||
Bold(false)
|
Bold(false)
|
||||||
s.Selected = s.Selected.
|
s.Selected = s.Selected.
|
||||||
Foreground(lipgloss.Color("229")).
|
Foreground(lipgloss.Color(config.ColorScheme.SelectedText)).
|
||||||
Background(lipgloss.Color("57")).
|
Background(lipgloss.Color(config.ColorScheme.SelectedBackground)).
|
||||||
Bold(false)
|
Bold(false)
|
||||||
if config.HighlightMatches {
|
if config.HighlightMatches {
|
||||||
MATCH_NOTHING_REGEXP := regexp.MustCompile("a^")
|
MATCH_NOTHING_REGEXP := regexp.MustCompile("a^")
|
||||||
@ -795,7 +798,18 @@ func deleteHistoryEntry(ctx context.Context, entry data.HistoryEntry) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TuiQuery(ctx context.Context, initialQuery string) error {
|
func TuiQuery(ctx context.Context, initialQuery string) error {
|
||||||
lipgloss.SetColorProfile(termenv.ANSI)
|
if hctx.GetConf(ctx).ColorScheme == hctx.GetDefaultColorScheme() {
|
||||||
|
// Set termenv.ANSI for the default color scheme, so that we preserve
|
||||||
|
// the true default color scheme of hishtory which was initially
|
||||||
|
// configured with termenv.ANSI (even though we want to support
|
||||||
|
// full colors) for custom color schemes.
|
||||||
|
lipgloss.SetColorProfile(termenv.ANSI)
|
||||||
|
} else if os.Getenv("HISHTORY_TEST") != "" {
|
||||||
|
// We also set termenv.ANSI for tests so as to ensure that all our
|
||||||
|
// test environments behave the same (by default, github actions
|
||||||
|
// ubuntu and macos have different termenv support).
|
||||||
|
lipgloss.SetColorProfile(termenv.ANSI)
|
||||||
|
}
|
||||||
p := tea.NewProgram(initialModel(ctx, initialQuery), tea.WithOutput(os.Stderr))
|
p := tea.NewProgram(initialModel(ctx, initialQuery), tea.WithOutput(os.Stderr))
|
||||||
// Async: Get the initial set of rows
|
// Async: Get the initial set of rows
|
||||||
go func() {
|
go func() {
|
||||||
|
Loading…
Reference in New Issue
Block a user