feat(config): add some more config options (#64)

## Add
Make these things configurable
- Expand aliases
- Suggest hidden files (#59)
- Toggle menu and mode 

```
[ui]
hidden-files = true # or false

[core]
expand-alias = true # or false

[keybindings]
toggle-mode = "ctrl+r"
toggle-menu = "ctrl+space"
```

- Auto reload config on changes: now just changes config and save the
file, IRIS will auto detect and reload in silent
- Added all these in config on init and docs

## Fix
I tested on holding backspace and the ghost text keep glitching so I
fixed it, this is just a small fix
This commit is contained in:
VERSE
2026-07-29 20:43:46 +07:00
committed by GitHub
parent 69fb77910d
commit d6cfb9c909
9 changed files with 271 additions and 81 deletions
+59 -19
View File
@@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"sync"
"sync/atomic"
"time"
"github.com/BurntSushi/toml"
@@ -32,18 +33,20 @@ func (d Duration) MarshalText() ([]byte, error) {
}
type CoreConfig struct {
Version int `toml:"version"`
Shell string `toml:"shell"`
Mode string `toml:"mode"`
Debug bool `toml:"debug"`
Version int `toml:"version"`
Shell string `toml:"shell"`
Mode string `toml:"mode"`
Debug bool `toml:"debug"`
ExpandAlias bool `toml:"expand-alias"`
}
type UIConfig struct {
Style string `toml:"style"`
GhostText bool `toml:"ghost-text"`
MaxSuggestions int `toml:"max-suggestions"`
MaxHeight int `toml:"max-height"`
NerdFonts bool `toml:"nerd-fonts"`
Style string `toml:"style"`
GhostText bool `toml:"ghost-text"`
ShowHiddenFiles bool `toml:"hidden-files"`
MaxSuggestions int `toml:"max-suggestions"`
MaxHeight int `toml:"max-height"`
NerdFonts bool `toml:"nerd-fonts"`
}
type GitConfig struct {
@@ -57,6 +60,11 @@ type UpdaterConfig struct {
CheckInterval Duration `toml:"check-interval"`
}
type KeybindingsConfig struct {
ToggleMode string `toml:"toggle-mode"`
ToggleMenu string `toml:"toggle-menu"`
}
type SuggestOnEmptyConfig struct {
Enabled bool `toml:"enabled"`
DebounceMS int `toml:"debounce_ms"`
@@ -101,32 +109,64 @@ func (p *ProviderConfig) GetAPIKey() string {
}
type Config struct {
Core CoreConfig `toml:"core"`
UI UIConfig `toml:"ui"`
Git GitConfig `toml:"git"`
Updater UpdaterConfig `toml:"updater"`
AI AIConfig `toml:"ai"`
Core CoreConfig `toml:"core"`
UI UIConfig `toml:"ui"`
Git GitConfig `toml:"git"`
Updater UpdaterConfig `toml:"updater"`
AI AIConfig `toml:"ai"`
Keybindings KeybindingsConfig `toml:"keybindings"`
}
var (
activeConfig *Config
activeConfig atomic.Pointer[Config]
once sync.Once
)
func Get() *Config {
once.Do(func() {
if activeConfig == nil {
activeConfig = DefaultConfig()
if activeConfig.Load() == nil {
activeConfig.Store(DefaultConfig())
}
})
return activeConfig
return activeConfig.Load()
}
func Init(cfg *Config) {
activeConfig = cfg
activeConfig.Store(cfg)
once.Do(func() {})
}
func AutoDetectConfigChange(onReload func(cfg *Config)) {
path, err := ConfigPath()
if err != nil {
return
}
go func() {
var lastMod time.Time
if info, err := os.Stat(path); err == nil {
lastMod = info.ModTime()
}
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for range ticker.C {
info, err := os.Stat(path)
if err == nil {
if !lastMod.IsZero() && info.ModTime().After(lastMod) {
lastMod = info.ModTime()
if newCfg, err := Load(); err == nil {
Init(newCfg)
if onReload != nil {
onReload(newCfg)
}
}
} else if lastMod.IsZero() {
lastMod = info.ModTime()
}
}
}
}()
}
func Load() (*Config, error) {
cfg := DefaultConfig()
+15 -9
View File
@@ -5,17 +5,19 @@ import "time"
func DefaultConfig() *Config {
return &Config{
Core: CoreConfig{
Version: 1,
Shell: "",
Mode: "last",
Debug: false,
Version: 1,
Shell: "",
Mode: "last",
Debug: false,
ExpandAlias: true,
},
UI: UIConfig{
Style: "modern",
GhostText: true,
MaxSuggestions: 100,
MaxHeight: 15,
NerdFonts: true,
Style: "modern",
GhostText: true,
ShowHiddenFiles: false,
MaxSuggestions: 100,
MaxHeight: 15,
NerdFonts: true,
},
Git: GitConfig{
FilterActiveBranch: true,
@@ -38,6 +40,10 @@ func DefaultConfig() *Config {
MinIntervalMS: 5000,
},
},
Keybindings: KeybindingsConfig{
ToggleMode: "ctrl+r",
ToggleMenu: "shift+tab",
},
}
}
+64
View File
@@ -0,0 +1,64 @@
package config
import (
"strings"
)
// MatchKey matches a sequence of terminal bytes against a configured key string.
// Key strings can be e.g. "ctrl+r", "tab", "shift+tab", "right".
// Returns matched=true if the byte sequence matches the configured key, and consumed=number of bytes to consume.
func MatchKey(input []byte, expected string) (matched bool, consumed int) {
if len(input) == 0 {
return false, 0
}
expected = strings.ToLower(strings.TrimSpace(expected))
if strings.HasPrefix(expected, "ctrl+") && len(expected) == 6 {
char := expected[5]
if char >= 'a' && char <= 'z' {
targetByte := char - 'a' + 1
if input[0] == targetByte {
return true, 1
}
}
}
switch expected {
case "ctrl+space":
if input[0] == 0x00 {
return true, 1
}
case "tab":
if input[0] == 0x09 {
return true, 1
}
case "shift+tab":
// typically \033[Z
if len(input) >= 3 && input[0] == 0x1b && input[1] == '[' && input[2] == 'Z' {
return true, 3
}
case "right":
// typically \033[C or \033OC
if len(input) >= 3 && input[0] == 0x1b && (input[1] == '[' || input[1] == 'O') && input[2] == 'C' {
return true, 3
}
case "enter":
if input[0] == 0x0d || input[0] == 0x0a {
return true, 1
}
}
return false, 0
}
// FormatKeyName takes a config key string like "ctrl+r" and formats it for UI display, e.g. "<Ctrl+R>".
func FormatKeyName(key string) string {
parts := strings.Split(key, "+")
for i, p := range parts {
if len(p) > 0 {
parts[i] = strings.ToUpper(p[:1]) + strings.ToLower(p[1:])
}
}
return "<" + strings.Join(parts, "+") + ">"
}