closes #62 when you open a new Zellij pane, the shell inherits Iris’s environment variables, so it thinks Iris is already running. But the pane doesn't inherit the underlying connection (FD 13), so suggestions silently fail into a dead pipe the old code only checked for `tmux` by process name, ignoring Zellij completely - Fixed: instead of checking for specific app names, Iris now tracks the active terminal device (`IRIS_TTY`) if a new shell detects that it's on a different `TTY` than Iris, it clears the stale environment variables so Iris can start fresh. The `IRIS_PID != PPID` check prevents Iris from trying to re-exec itself when spawning sub-shells. ```sh if [ -n "$IRIS_PID" ] && [ "$IRIS_PID" != "$PPID" ] && [ "${TTY:-$(tty 2>/dev/null)}" != "$IRIS_TTY" ]; then unset IRIS_PID IRIS_IS_CHILD IRIS_FD IRIS_TTY fi ```
303 lines
8.1 KiB
Go
303 lines
8.1 KiB
Go
package root
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/versenilvis/iris/integration/shell"
|
|
"github.com/versenilvis/iris/internal/config"
|
|
)
|
|
|
|
var initCmd = &cobra.Command{
|
|
Use: "init [bash|zsh|fish]",
|
|
Short: "Generate the autostart script for your shell",
|
|
Long: `Add the output of this command to your shell's configuration file to start Iris automatically.
|
|
For example, add this to your ~/.zshrc:
|
|
eval "$(iris init zsh)"`,
|
|
ValidArgs: []string{"bash", "zsh", "fish"},
|
|
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
shell := args[0]
|
|
switch shell {
|
|
case "zsh":
|
|
fmt.Printf(`
|
|
# Iris Autostart Hook
|
|
# a multiplexer pane inherits IRIS_* but runs on its own tty, so those vars
|
|
# point at an iris that is not driving this terminal
|
|
if [ -n "$IRIS_PID" ] && [ "$IRIS_PID" != "$PPID" ] && [ "${TTY:-$(tty 2>/dev/null)}" != "$IRIS_TTY" ]; then
|
|
unset IRIS_PID IRIS_IS_CHILD IRIS_FD IRIS_TTY
|
|
fi
|
|
|
|
if [ -z "$IRIS_PID" ] && [ -z "$IRIS_RESCUE" ]; then
|
|
export IRIS_ACTIVE_SHELL="zsh"
|
|
exec iris
|
|
fi
|
|
|
|
# Iris Autocomplete Hook
|
|
if [ -n "$IRIS_PID" ] && [ -n "$IRIS_FD" ]; then
|
|
_iris_send_lbuffer() {
|
|
print -u $IRIS_FD -N -r -- "$LBUFFER" 2>/dev/null
|
|
}
|
|
|
|
_iris_sync_cwd() {
|
|
print -u $IRIS_FD -N -r -- "IRIS_CWD:$PWD" 2>/dev/null
|
|
}
|
|
|
|
_iris_precmd() {
|
|
local iris_exit_code=$?
|
|
_iris_sync_cwd
|
|
print -u $IRIS_FD -N -r -- "IRIS_CMD_STOP:$iris_exit_code" 2>/dev/null
|
|
}
|
|
|
|
_iris_preexec() {
|
|
print -u $IRIS_FD -N -r -- "IRIS_CMD_START" 2>/dev/null
|
|
}
|
|
|
|
autoload -Uz add-zle-hook-widget
|
|
autoload -Uz add-zsh-hook
|
|
|
|
add-zle-hook-widget line-pre-redraw _iris_send_lbuffer
|
|
add-zsh-hook precmd _iris_precmd
|
|
add-zsh-hook preexec _iris_preexec
|
|
add-zsh-hook chpwd _iris_sync_cwd
|
|
fi
|
|
`)
|
|
case "bash":
|
|
fmt.Printf(`
|
|
# Iris Autostart Hook
|
|
# a multiplexer pane inherits IRIS_* but runs on its own tty, so those vars
|
|
# point at an iris that is not driving this terminal
|
|
if [ -n "$IRIS_PID" ] && [ "$IRIS_PID" != "$PPID" ] && [ "$(tty 2>/dev/null)" != "$IRIS_TTY" ]; then
|
|
unset IRIS_PID IRIS_IS_CHILD IRIS_FD IRIS_TTY
|
|
fi
|
|
|
|
if [ -z "$IRIS_PID" ] && [ -z "$IRIS_RESCUE" ]; then
|
|
export IRIS_ACTIVE_SHELL="bash"
|
|
exec iris
|
|
fi
|
|
|
|
# Iris Autocomplete Hook
|
|
if [ -n "$IRIS_PID" ] && [ -n "$IRIS_FD" ]; then
|
|
_iris_bash_precmd() {
|
|
local iris_exit_code=$?
|
|
printf "IRIS_CWD:%%s\x00" "$PWD" >&$IRIS_FD 2>/dev/null
|
|
printf "IRIS_CMD_STOP:%%s\x00" "$iris_exit_code" >&$IRIS_FD 2>/dev/null
|
|
}
|
|
|
|
if [[ ";$PROMPT_COMMAND;" != *";_iris_bash_precmd;"* ]]; then
|
|
PROMPT_COMMAND="_iris_bash_precmd${PROMPT_COMMAND:+;$PROMPT_COMMAND}"
|
|
fi
|
|
fi
|
|
|
|
`)
|
|
case "fish":
|
|
fmt.Printf(`
|
|
# Iris Autostart Hook
|
|
# a multiplexer pane inherits IRIS_* but runs on its own tty, so those vars
|
|
# point at an iris that is not driving this terminal
|
|
if set -q IRIS_PID
|
|
set -l iris_ppid (ps -o ppid= -p $fish_pid 2>/dev/null | string trim)
|
|
set -l iris_cur_tty (tty 2>/dev/null)
|
|
if test "$IRIS_PID" != "$iris_ppid"; and test "$iris_cur_tty" != "$IRIS_TTY"
|
|
set -e IRIS_PID
|
|
set -e IRIS_IS_CHILD
|
|
set -e IRIS_FD
|
|
set -e IRIS_TTY
|
|
end
|
|
end
|
|
|
|
if status is-interactive; and not set -q IRIS_PID; and not set -q IRIS_RESCUE
|
|
set -gx IRIS_ACTIVE_SHELL "fish"
|
|
exec iris
|
|
end
|
|
|
|
# Iris Autocomplete Hook
|
|
if set -q IRIS_PID; and set -q IRIS_FD
|
|
function _iris_fish_postexec --on-event fish_postexec
|
|
set -l iris_exit_code $status
|
|
printf "IRIS_CWD:%%s\x00" "$PWD" >&$IRIS_FD 2>/dev/null
|
|
printf "IRIS_CMD_STOP:%%s\x00" "$iris_exit_code" >&$IRIS_FD 2>/dev/null
|
|
end
|
|
function _iris_fish_prompt --on-event fish_prompt
|
|
printf "IRIS_CWD:%%s\x00" "$PWD" >&$IRIS_FD 2>/dev/null
|
|
end
|
|
function _iris_fish_preexec --on-event fish_preexec
|
|
printf "IRIS_CMD_START\x00" >&$IRIS_FD 2>/dev/null
|
|
end
|
|
end
|
|
`)
|
|
}
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
rootCmd.AddCommand(initCmd)
|
|
rootCmd.AddCommand(setupCmd)
|
|
}
|
|
|
|
var setupCmd = &cobra.Command{
|
|
Use: "setup [shell]",
|
|
Short: "Automatically setup iris shell integration and install binary",
|
|
Args: cobra.MaximumNArgs(1),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
home, _ := os.UserHomeDir()
|
|
|
|
localBin := filepath.Join(home, ".local", "bin")
|
|
_ = os.MkdirAll(localBin, 0755)
|
|
|
|
exe, _ := os.Executable()
|
|
targetExe := filepath.Join(localBin, "iris")
|
|
|
|
fmt.Printf("Installing iris to %s...\n", targetExe)
|
|
input, err := os.ReadFile(exe)
|
|
if err != nil {
|
|
fmt.Printf("Failed to read current executable: %v\n", err)
|
|
return
|
|
}
|
|
|
|
_ = os.Remove(targetExe)
|
|
err = os.WriteFile(targetExe, input, 0755)
|
|
if err != nil {
|
|
fmt.Printf("Failed to write to %s: %v\n", targetExe, err)
|
|
return
|
|
}
|
|
|
|
var shellName string
|
|
if len(args) > 0 {
|
|
shellName = filepath.Base(args[0])
|
|
} else {
|
|
shellPath := os.Getenv("SHELL")
|
|
shellName = filepath.Base(shellPath)
|
|
}
|
|
var configFile string
|
|
var evalCmd string
|
|
|
|
switch shellName {
|
|
case "zsh":
|
|
configFile = filepath.Join(shell.GetZshConfigDir(), ".zshrc")
|
|
evalCmd = `eval "$(iris init zsh)"`
|
|
case "bash":
|
|
configFile = filepath.Join(home, ".bashrc")
|
|
evalCmd = `eval "$(iris init bash)"`
|
|
case "fish":
|
|
configFile = filepath.Join(shell.GetFishConfigDir(), "config.fish")
|
|
evalCmd = `iris init fish | source`
|
|
default:
|
|
fmt.Printf("Unsupported shell: %s. Please add iris init manually.\n", shellName)
|
|
return
|
|
}
|
|
|
|
content, readErr := os.ReadFile(configFile)
|
|
if strings.Contains(string(content), "iris init") {
|
|
fmt.Printf("Iris is already configured in %s\n", configFile)
|
|
} else {
|
|
var newContent string
|
|
if readErr == nil && len(content) > 0 {
|
|
newContent = "# Iris Autocomplete\n" + evalCmd + "\n\n" + string(content)
|
|
} else {
|
|
newContent = "# Iris Autocomplete\n" + evalCmd + "\n"
|
|
}
|
|
if mkdirErr := os.MkdirAll(filepath.Dir(configFile), 0755); mkdirErr != nil {
|
|
fmt.Printf("Failed to create directory for %s: %v\n", configFile, mkdirErr)
|
|
return
|
|
}
|
|
err = os.WriteFile(configFile, []byte(newContent), 0644)
|
|
if err != nil {
|
|
fmt.Printf("Failed to update %s: %v\n", configFile, err)
|
|
return
|
|
}
|
|
fmt.Printf("✓ Added iris integration to top of %s\n", configFile)
|
|
}
|
|
|
|
// initialize default config file if it does not exist
|
|
if path, err := config.ConfigPath(); err == nil {
|
|
if _, statErr := os.Stat(path); os.IsNotExist(statErr) {
|
|
_ = os.MkdirAll(filepath.Dir(path), 0755)
|
|
defaultContent := `# ~/.config/iris/config.toml
|
|
# iris configuration file
|
|
|
|
[core]
|
|
# schema version
|
|
# do not edit this field manually
|
|
version = 1
|
|
|
|
# override shell: "bash", "zsh", "fish", keep empty for auto detection
|
|
shell = ""
|
|
|
|
# run the selected shell as a login shell
|
|
shell-login = false
|
|
|
|
# startup mode: "last", "spec", "history"
|
|
# "last" = remember last mode used
|
|
mode = "last"
|
|
|
|
# enable debug logging
|
|
debug = false
|
|
|
|
# automatically expand aliases on space
|
|
expand-alias = true
|
|
|
|
# automatically execute command after accepting suggestion
|
|
auto-execute = false
|
|
|
|
[ui]
|
|
# visual style: "modern" (icons, category pills, shortcut footer) or "classic" (minimalist, centered number, no icons)
|
|
style = "modern"
|
|
|
|
# enable Nerd Fonts icons in overlay menu
|
|
nerd-fonts = true
|
|
|
|
# show hidden files with dot prefix
|
|
hidden-files = false
|
|
|
|
# enable inline ghost text
|
|
ghost-text = true
|
|
|
|
# maximum suggestions to display
|
|
max-suggestions = 100
|
|
|
|
# maximum height of the overlay
|
|
max-height = 15
|
|
|
|
# maximum width of the overlay (0 = responsive to terminal)
|
|
max-width = 0
|
|
|
|
[git]
|
|
# hide current branch in checkout/switch list
|
|
filter-active-branch = true
|
|
|
|
# merge remote and local branches with same name
|
|
deduplicate-branches = true
|
|
|
|
[updater]
|
|
# check for updates on startup
|
|
check-on-startup = true
|
|
|
|
# update channel: "stable", "nightly"
|
|
channel = "stable"
|
|
|
|
# interval between update checks, e.g. "24h", "6h", "30m"
|
|
check-interval = "24h"
|
|
|
|
[keybindings]
|
|
toggle-mode = "ctrl+r"
|
|
toggle-menu = "shift+tab"
|
|
select = "tab"
|
|
navigate-up = "up"
|
|
navigate-down = "down"
|
|
navigate-right = "right"
|
|
`
|
|
if errWrite := os.WriteFile(path, []byte(defaultContent), 0644); errWrite == nil {
|
|
fmt.Printf("✓ Initialized default config file at %s\n", path)
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Println("\nSetup complete! Please restart your terminal or run:")
|
|
fmt.Printf(" \033[32msource %s\033[0m\n", configFile)
|
|
},
|
|
}
|