merge: combine zoxide and Atuin context navigation
# Conflicts: # README.md # internal/config/config.go # internal/config/config_test.go # root/config_cmd.go # root/config_cmd_test.go # root/init.go
This commit is contained in:
@@ -83,6 +83,11 @@ type ZoxideConfig struct {
|
||||
ExtendCd bool `toml:"extend-cd"`
|
||||
}
|
||||
|
||||
type HistoryConfig struct {
|
||||
// Scope controls Atuin filtering for non-empty history searches.
|
||||
Scope string `toml:"scope"`
|
||||
}
|
||||
|
||||
type SuggestOnEmptyConfig struct {
|
||||
Enabled bool `toml:"enabled"`
|
||||
DebounceMS int `toml:"debounce_ms"`
|
||||
@@ -130,6 +135,7 @@ type Config struct {
|
||||
Core CoreConfig `toml:"core"`
|
||||
UI UIConfig `toml:"ui"`
|
||||
Git GitConfig `toml:"git"`
|
||||
History HistoryConfig `toml:"history"`
|
||||
Updater UpdaterConfig `toml:"updater"`
|
||||
AI AIConfig `toml:"ai"`
|
||||
Zoxide ZoxideConfig `toml:"zoxide"`
|
||||
@@ -288,6 +294,11 @@ func validate(cfg *Config) error {
|
||||
return fmt.Errorf("core.shell: invalid value %q (want: bash|zsh|fish)", cfg.Core.Shell)
|
||||
}
|
||||
|
||||
validHistoryScopes := map[string]bool{"global": true, "directory": true, "workspace": true, "hybrid": true}
|
||||
if !validHistoryScopes[cfg.History.Scope] {
|
||||
return fmt.Errorf("history.scope: invalid value %q (want: global|directory|workspace|hybrid)", cfg.History.Scope)
|
||||
}
|
||||
|
||||
validChannels := map[string]bool{"stable": true, "nightly": true}
|
||||
if !validChannels[cfg.Updater.Channel] {
|
||||
return fmt.Errorf("updater.channel: invalid value %q (want: stable|nightly)", cfg.Updater.Channel)
|
||||
|
||||
@@ -33,6 +33,9 @@ func TestDefaultConfigAndState(t *testing.T) {
|
||||
if cfg.Zoxide.ExtendCd {
|
||||
t.Errorf("expected zoxide cd extension to be disabled by default")
|
||||
}
|
||||
if cfg.History.Scope != "global" {
|
||||
t.Errorf("expected global history scope, got %q", cfg.History.Scope)
|
||||
}
|
||||
|
||||
// test manual provider registration
|
||||
cfg.AI.Provider = "custom"
|
||||
@@ -210,6 +213,21 @@ func TestValidateAutoUpdateRange(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateHistoryScope(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
for _, scope := range []string{"global", "directory", "workspace", "hybrid"} {
|
||||
cfg.History.Scope = scope
|
||||
if err := validate(cfg); err != nil {
|
||||
t.Errorf("expected history scope %q to be valid: %v", scope, err)
|
||||
}
|
||||
}
|
||||
|
||||
cfg.History.Scope = "session"
|
||||
if err := validate(cfg); err == nil {
|
||||
t.Error("expected unsupported history scope to be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSave(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "iris-config-test")
|
||||
if err != nil {
|
||||
|
||||
@@ -29,6 +29,9 @@ func DefaultConfig() *Config {
|
||||
FilterActiveBranch: true,
|
||||
DeduplicateBranches: true,
|
||||
},
|
||||
History: HistoryConfig{
|
||||
Scope: "global",
|
||||
},
|
||||
Updater: UpdaterConfig{
|
||||
CheckOnStartup: true,
|
||||
Channel: "stable",
|
||||
|
||||
@@ -108,6 +108,21 @@ func detectGitInfo(cwd string) (hasGit bool, branch string) {
|
||||
return hasGit, ""
|
||||
}
|
||||
|
||||
// GitRoot returns the repository root containing cwd, or an empty string.
|
||||
func GitRoot(cwd string) string {
|
||||
dir := filepath.Clean(cwd)
|
||||
for {
|
||||
if _, err := os.Stat(filepath.Join(dir, ".git")); err == nil {
|
||||
return dir
|
||||
}
|
||||
parent := filepath.Dir(dir)
|
||||
if parent == dir {
|
||||
return ""
|
||||
}
|
||||
dir = parent
|
||||
}
|
||||
}
|
||||
|
||||
type cacheEntry struct {
|
||||
key string // cwd + "|" + dirModTime
|
||||
info WorkspaceInfo
|
||||
|
||||
@@ -44,6 +44,24 @@ func TestDetect_EmptyDirectory(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitRoot(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
nested := filepath.Join(repo, "internal", "pkg")
|
||||
if err := os.MkdirAll(filepath.Join(repo, ".git"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(nested, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if got := GitRoot(nested); got != repo {
|
||||
t.Fatalf("expected git root %q, got %q", repo, got)
|
||||
}
|
||||
if got := GitRoot(t.TempDir()); got != "" {
|
||||
t.Fatalf("expected no git root, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetect_NodeProject(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
_ = os.WriteFile(filepath.Join(tmp, "package.json"), []byte("{}"), 0644)
|
||||
@@ -135,7 +153,7 @@ func TestDetectCached_BranchSwitchWithoutDirChange(t *testing.T) {
|
||||
// Simulate branch switch by updating .git/HEAD only
|
||||
// (this does not update the modtime of tmp on typical filesystems since tmp's direct children didn't change)
|
||||
_ = os.WriteFile(headPath, []byte("ref: refs/heads/feature\n"), 0644)
|
||||
|
||||
|
||||
// Force the modtime of HEAD to be distinct to avoid flakiness on low-res file systems
|
||||
infoAfter, _ := os.Stat(headPath)
|
||||
newMod := infoAfter.ModTime().Add(2 * time.Second)
|
||||
|
||||
Reference in New Issue
Block a user