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:
@@ -107,14 +107,20 @@ version = 1
|
|||||||
shell = "" # "zsh", "bash", "fish", or empty for auto-detection
|
shell = "" # "zsh", "bash", "fish", or empty for auto-detection
|
||||||
mode = "last" # "last", "spec", or "history"
|
mode = "last" # "last", "spec", or "history"
|
||||||
debug = false
|
debug = false
|
||||||
|
expand-alias = true
|
||||||
|
|
||||||
[ui]
|
[ui]
|
||||||
style = "modern" # "modern" or "classic"
|
style = "modern" # "modern" or "classic"
|
||||||
ghost-text = true
|
ghost-text = true
|
||||||
|
hidden-files = false
|
||||||
max-suggestions = 100
|
max-suggestions = 100
|
||||||
max-height = 15
|
max-height = 15
|
||||||
nerd-fonts = true
|
nerd-fonts = true
|
||||||
|
|
||||||
|
[keybindings]
|
||||||
|
toggle-mode = "ctrl+r"
|
||||||
|
toggle-menu = "ctrl+space"
|
||||||
|
|
||||||
[git]
|
[git]
|
||||||
filter-active-branch = true
|
filter-active-branch = true
|
||||||
deduplicate-branches = true
|
deduplicate-branches = true
|
||||||
|
|||||||
+19
-1
@@ -434,6 +434,24 @@ func (o *Overlay) GetGhostText(buffer string, cursorAtEnd bool) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Overlay) ClearGhostTextState() {
|
||||||
|
o.mu.Lock()
|
||||||
|
defer o.mu.Unlock()
|
||||||
|
o.LastGhostLen = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Overlay) HideGhostTextSync() string {
|
||||||
|
o.mu.Lock()
|
||||||
|
defer o.mu.Unlock()
|
||||||
|
if o.LastGhostLen > 0 {
|
||||||
|
padLen := o.LastGhostLen + 4
|
||||||
|
res := "\0337" + strings.Repeat(" ", padLen) + "\0338"
|
||||||
|
o.LastGhostLen = 0
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (o *Overlay) RenderGhostText(buffer string, userNavigated bool, cursorAtEnd bool) string {
|
func (o *Overlay) RenderGhostText(buffer string, userNavigated bool, cursorAtEnd bool) string {
|
||||||
o.mu.Lock()
|
o.mu.Lock()
|
||||||
defer o.mu.Unlock()
|
defer o.mu.Unlock()
|
||||||
@@ -757,7 +775,7 @@ func (o *Overlay) draw() string {
|
|||||||
if !isClassic {
|
if !isClassic {
|
||||||
keyStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#a277ff")).Bold(true)
|
keyStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#a277ff")).Bold(true)
|
||||||
tabKey := keyStyle.Render("<Tab>")
|
tabKey := keyStyle.Render("<Tab>")
|
||||||
ctrlRKey := keyStyle.Render("<Ctrl+R>")
|
ctrlRKey := keyStyle.Render(config.FormatKeyName(config.Get().Keybindings.ToggleMode))
|
||||||
acceptText := lipgloss.NewStyle().Foreground(t.ScrollInfo).Render(" Accept")
|
acceptText := lipgloss.NewStyle().Foreground(t.ScrollInfo).Render(" Accept")
|
||||||
modeText := lipgloss.NewStyle().Foreground(t.ScrollInfo).Render(" Mode")
|
modeText := lipgloss.NewStyle().Foreground(t.ScrollInfo).Render(" Mode")
|
||||||
footerInfo = fmt.Sprintf(" %s%s • %s%s ", tabKey, acceptText, ctrlRKey, modeText)
|
footerInfo = fmt.Sprintf(" %s%s • %s%s ", tabKey, acceptText, ctrlRKey, modeText)
|
||||||
|
|||||||
+59
-19
@@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
@@ -32,18 +33,20 @@ func (d Duration) MarshalText() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CoreConfig struct {
|
type CoreConfig struct {
|
||||||
Version int `toml:"version"`
|
Version int `toml:"version"`
|
||||||
Shell string `toml:"shell"`
|
Shell string `toml:"shell"`
|
||||||
Mode string `toml:"mode"`
|
Mode string `toml:"mode"`
|
||||||
Debug bool `toml:"debug"`
|
Debug bool `toml:"debug"`
|
||||||
|
ExpandAlias bool `toml:"expand-alias"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UIConfig struct {
|
type UIConfig struct {
|
||||||
Style string `toml:"style"`
|
Style string `toml:"style"`
|
||||||
GhostText bool `toml:"ghost-text"`
|
GhostText bool `toml:"ghost-text"`
|
||||||
MaxSuggestions int `toml:"max-suggestions"`
|
ShowHiddenFiles bool `toml:"hidden-files"`
|
||||||
MaxHeight int `toml:"max-height"`
|
MaxSuggestions int `toml:"max-suggestions"`
|
||||||
NerdFonts bool `toml:"nerd-fonts"`
|
MaxHeight int `toml:"max-height"`
|
||||||
|
NerdFonts bool `toml:"nerd-fonts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GitConfig struct {
|
type GitConfig struct {
|
||||||
@@ -57,6 +60,11 @@ type UpdaterConfig struct {
|
|||||||
CheckInterval Duration `toml:"check-interval"`
|
CheckInterval Duration `toml:"check-interval"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KeybindingsConfig struct {
|
||||||
|
ToggleMode string `toml:"toggle-mode"`
|
||||||
|
ToggleMenu string `toml:"toggle-menu"`
|
||||||
|
}
|
||||||
|
|
||||||
type SuggestOnEmptyConfig struct {
|
type SuggestOnEmptyConfig struct {
|
||||||
Enabled bool `toml:"enabled"`
|
Enabled bool `toml:"enabled"`
|
||||||
DebounceMS int `toml:"debounce_ms"`
|
DebounceMS int `toml:"debounce_ms"`
|
||||||
@@ -101,32 +109,64 @@ func (p *ProviderConfig) GetAPIKey() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Core CoreConfig `toml:"core"`
|
Core CoreConfig `toml:"core"`
|
||||||
UI UIConfig `toml:"ui"`
|
UI UIConfig `toml:"ui"`
|
||||||
Git GitConfig `toml:"git"`
|
Git GitConfig `toml:"git"`
|
||||||
Updater UpdaterConfig `toml:"updater"`
|
Updater UpdaterConfig `toml:"updater"`
|
||||||
AI AIConfig `toml:"ai"`
|
AI AIConfig `toml:"ai"`
|
||||||
|
Keybindings KeybindingsConfig `toml:"keybindings"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
activeConfig *Config
|
activeConfig atomic.Pointer[Config]
|
||||||
once sync.Once
|
once sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
func Get() *Config {
|
func Get() *Config {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
if activeConfig == nil {
|
if activeConfig.Load() == nil {
|
||||||
activeConfig = DefaultConfig()
|
activeConfig.Store(DefaultConfig())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return activeConfig
|
return activeConfig.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init(cfg *Config) {
|
func Init(cfg *Config) {
|
||||||
activeConfig = cfg
|
activeConfig.Store(cfg)
|
||||||
once.Do(func() {})
|
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) {
|
func Load() (*Config, error) {
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
|
|||||||
@@ -5,17 +5,19 @@ import "time"
|
|||||||
func DefaultConfig() *Config {
|
func DefaultConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Core: CoreConfig{
|
Core: CoreConfig{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Shell: "",
|
Shell: "",
|
||||||
Mode: "last",
|
Mode: "last",
|
||||||
Debug: false,
|
Debug: false,
|
||||||
|
ExpandAlias: true,
|
||||||
},
|
},
|
||||||
UI: UIConfig{
|
UI: UIConfig{
|
||||||
Style: "modern",
|
Style: "modern",
|
||||||
GhostText: true,
|
GhostText: true,
|
||||||
MaxSuggestions: 100,
|
ShowHiddenFiles: false,
|
||||||
MaxHeight: 15,
|
MaxSuggestions: 100,
|
||||||
NerdFonts: true,
|
MaxHeight: 15,
|
||||||
|
NerdFonts: true,
|
||||||
},
|
},
|
||||||
Git: GitConfig{
|
Git: GitConfig{
|
||||||
FilterActiveBranch: true,
|
FilterActiveBranch: true,
|
||||||
@@ -38,6 +40,10 @@ func DefaultConfig() *Config {
|
|||||||
MinIntervalMS: 5000,
|
MinIntervalMS: 5000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Keybindings: KeybindingsConfig{
|
||||||
|
ToggleMode: "ctrl+r",
|
||||||
|
ToggleMenu: "shift+tab",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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, "+") + ">"
|
||||||
|
}
|
||||||
@@ -50,6 +50,9 @@ mode = "last"
|
|||||||
# enable debug logging
|
# enable debug logging
|
||||||
debug = false
|
debug = false
|
||||||
|
|
||||||
|
# auto expand alias when typing space
|
||||||
|
expand-alias = true
|
||||||
|
|
||||||
[ui]
|
[ui]
|
||||||
# visual style: "modern" (icons, category pills, shortcut footer) or "classic" (minimalist, centered number, no icons)
|
# visual style: "modern" (icons, category pills, shortcut footer) or "classic" (minimalist, centered number, no icons)
|
||||||
style = "modern"
|
style = "modern"
|
||||||
@@ -60,12 +63,22 @@ nerd-fonts = true
|
|||||||
# enable inline ghost text
|
# enable inline ghost text
|
||||||
ghost-text = true
|
ghost-text = true
|
||||||
|
|
||||||
|
# show hidden files (starting with dot) in file generator
|
||||||
|
hidden-files = false
|
||||||
|
|
||||||
# maximum suggestions to display
|
# maximum suggestions to display
|
||||||
max-suggestions = 100
|
max-suggestions = 100
|
||||||
|
|
||||||
# maximum height of the overlay
|
# maximum height of the overlay
|
||||||
max-height = 15
|
max-height = 15
|
||||||
|
|
||||||
|
[keybindings]
|
||||||
|
# toggle between spec and history mode
|
||||||
|
toggle-mode = "ctrl+r"
|
||||||
|
|
||||||
|
# toggle menu visibility
|
||||||
|
toggle-menu = "ctrl+space"
|
||||||
|
|
||||||
[git]
|
[git]
|
||||||
# hide current branch in checkout/switch list
|
# hide current branch in checkout/switch list
|
||||||
filter-active-branch = true
|
filter-active-branch = true
|
||||||
|
|||||||
@@ -186,6 +186,9 @@ mode = "last"
|
|||||||
# enable debug logging
|
# enable debug logging
|
||||||
debug = false
|
debug = false
|
||||||
|
|
||||||
|
# automatically expand aliases on space
|
||||||
|
expand-alias = true
|
||||||
|
|
||||||
[ui]
|
[ui]
|
||||||
# visual style: "modern" (icons, category pills, shortcut footer) or "classic" (minimalist, centered number, no icons)
|
# visual style: "modern" (icons, category pills, shortcut footer) or "classic" (minimalist, centered number, no icons)
|
||||||
style = "modern"
|
style = "modern"
|
||||||
@@ -193,6 +196,9 @@ style = "modern"
|
|||||||
# enable Nerd Fonts icons in overlay menu
|
# enable Nerd Fonts icons in overlay menu
|
||||||
nerd-fonts = true
|
nerd-fonts = true
|
||||||
|
|
||||||
|
# show hidden files with dot prefix
|
||||||
|
hidden-files = false
|
||||||
|
|
||||||
# enable inline ghost text
|
# enable inline ghost text
|
||||||
ghost-text = true
|
ghost-text = true
|
||||||
|
|
||||||
@@ -218,6 +224,12 @@ channel = "stable"
|
|||||||
|
|
||||||
# interval between update checks, e.g. "24h", "6h", "30m"
|
# interval between update checks, e.g. "24h", "6h", "30m"
|
||||||
check-interval = "24h"
|
check-interval = "24h"
|
||||||
|
|
||||||
|
[keybindings]
|
||||||
|
toggle_mode = "ctrl+r"
|
||||||
|
toggle_menu = "shift+tab"
|
||||||
|
select_suggestion = "tab"
|
||||||
|
accept_suggestion = "right"
|
||||||
`
|
`
|
||||||
if errWrite := os.WriteFile(path, []byte(defaultContent), 0644); errWrite == nil {
|
if errWrite := os.WriteFile(path, []byte(defaultContent), 0644); errWrite == nil {
|
||||||
fmt.Printf("✓ Initialized default config file at %s\n", path)
|
fmt.Printf("✓ Initialized default config file at %s\n", path)
|
||||||
|
|||||||
+78
-49
@@ -247,6 +247,8 @@ func runWrapper() {
|
|||||||
|
|
||||||
overlay := integration.NewOverlay()
|
overlay := integration.NewOverlay()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// start background update check (async)
|
// start background update check (async)
|
||||||
pendingUpdate = startBackgroundUpdateCheck()
|
pendingUpdate = startBackgroundUpdateCheck()
|
||||||
updatePrinted := false
|
updatePrinted := false
|
||||||
@@ -256,6 +258,12 @@ func runWrapper() {
|
|||||||
shellPGID = spec.ShellPID
|
shellPGID = spec.ShellPID
|
||||||
}
|
}
|
||||||
var isCommandActive atomic.Bool
|
var isCommandActive atomic.Bool
|
||||||
|
var disableGhostText atomic.Bool
|
||||||
|
disableGhostText.Store(!config.Get().UI.GhostText)
|
||||||
|
config.AutoDetectConfigChange(func(cfg *config.Config) {
|
||||||
|
disableGhostText.Store(!cfg.UI.GhostText)
|
||||||
|
})
|
||||||
|
var renderOverlay func()
|
||||||
isExecuting := func() bool {
|
isExecuting := func() bool {
|
||||||
if isCommandActive.Load() {
|
if isCommandActive.Load() {
|
||||||
return true
|
return true
|
||||||
@@ -311,10 +319,6 @@ func runWrapper() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var disableGhostText atomic.Bool
|
|
||||||
disableGhostText.Store(!config.Get().UI.GhostText)
|
|
||||||
var renderOverlay func()
|
|
||||||
|
|
||||||
// listen for suggestion requests from shell scripts via the ipc pipe
|
// listen for suggestion requests from shell scripts via the ipc pipe
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -576,9 +580,9 @@ func runWrapper() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if renderTimer != nil {
|
if renderTimer != nil {
|
||||||
renderTimer.Stop()
|
return
|
||||||
}
|
}
|
||||||
renderTimer = time.AfterFunc(25*time.Millisecond, func() {
|
renderTimer = time.AfterFunc(20*time.Millisecond, func() {
|
||||||
renderMu.Lock()
|
renderMu.Lock()
|
||||||
renderTimer = nil
|
renderTimer = nil
|
||||||
renderMu.Unlock()
|
renderMu.Unlock()
|
||||||
@@ -614,6 +618,21 @@ func runWrapper() {
|
|||||||
b := inputSlice[i]
|
b := inputSlice[i]
|
||||||
intercepted := false
|
intercepted := false
|
||||||
|
|
||||||
|
if matched, consumed := config.MatchKey(inputSlice[i:], config.Get().Keybindings.ToggleMenu); matched {
|
||||||
|
intercepted = true
|
||||||
|
suggestionsEnabled = !suggestionsEnabled
|
||||||
|
logger.Debugf("Intercepted ToggleMenu, suggestionsEnabled=%v", suggestionsEnabled)
|
||||||
|
if !suggestionsEnabled {
|
||||||
|
writeStdout([]byte(overlay.ClearAndDisable()))
|
||||||
|
} else {
|
||||||
|
shouldOverlayDraw = true
|
||||||
|
}
|
||||||
|
i += consumed - 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if b == '\033' {
|
if b == '\033' {
|
||||||
// check for bracketed paste start/end
|
// check for bracketed paste start/end
|
||||||
if i+5 < n && inputSlice[i+1] == '[' && inputSlice[i+2] == '2' && inputSlice[i+3] == '0' {
|
if i+5 < n && inputSlice[i+1] == '[' && inputSlice[i+2] == '2' && inputSlice[i+3] == '0' {
|
||||||
@@ -628,20 +647,6 @@ func runWrapper() {
|
|||||||
}
|
}
|
||||||
// handle escape sequences like arrow keys and functional shortcuts
|
// handle escape sequences like arrow keys and functional shortcuts
|
||||||
if i+2 < n && (inputSlice[i+1] == '[' || inputSlice[i+1] == 'O') {
|
if i+2 < n && (inputSlice[i+1] == '[' || inputSlice[i+1] == 'O') {
|
||||||
// shift tab: hide/unhide menu dropdown
|
|
||||||
if inputSlice[i+1] == '[' && inputSlice[i+2] == 'Z' {
|
|
||||||
intercepted = true
|
|
||||||
suggestionsEnabled = !suggestionsEnabled
|
|
||||||
logger.Debugf("Intercepted Shift+Tab, suggestionsEnabled=%v", suggestionsEnabled)
|
|
||||||
if !suggestionsEnabled {
|
|
||||||
writeStdout([]byte(overlay.ClearAndDisable()))
|
|
||||||
} else {
|
|
||||||
shouldOverlayDraw = true
|
|
||||||
}
|
|
||||||
i += 2
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if overlay.IsVisible() && (inputSlice[i+2] == 'A' || inputSlice[i+2] == 'B') {
|
if overlay.IsVisible() && (inputSlice[i+2] == 'A' || inputSlice[i+2] == 'B') {
|
||||||
intercepted = true
|
intercepted = true
|
||||||
userNavigated.Store(true)
|
userNavigated.Store(true)
|
||||||
@@ -722,24 +727,6 @@ func runWrapper() {
|
|||||||
}
|
}
|
||||||
i += 2
|
i += 2
|
||||||
continue
|
continue
|
||||||
} else if !disableGhostText.Load() && inputSlice[i+2] == 'C' { // right arrow
|
|
||||||
bufferMu.Lock()
|
|
||||||
atEnd := (cursorOffset == 0)
|
|
||||||
ghostText := overlay.GetGhostText(naiveBuffer, atEnd)
|
|
||||||
bufferMu.Unlock()
|
|
||||||
|
|
||||||
if len(ghostText) > 0 {
|
|
||||||
intercepted = true
|
|
||||||
logger.Debugf("Intercepted Right Arrow (accepted ghost text: %q)", ghostText)
|
|
||||||
bufferMu.Lock()
|
|
||||||
naiveBuffer += ghostText
|
|
||||||
cursorOffset = 0
|
|
||||||
bufferMu.Unlock()
|
|
||||||
_, _ = ptmx.Write([]byte(ghostText))
|
|
||||||
shouldOverlayDraw = true
|
|
||||||
i += 2
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -775,6 +762,29 @@ func runWrapper() {
|
|||||||
i += 2
|
i += 2
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bufferMu.Lock()
|
||||||
|
atEnd := (cursorOffset == 0)
|
||||||
|
ghostText := ""
|
||||||
|
if !disableGhostText.Load() {
|
||||||
|
ghostText = overlay.GetGhostText(naiveBuffer, atEnd)
|
||||||
|
}
|
||||||
|
bufferMu.Unlock()
|
||||||
|
|
||||||
|
if len(ghostText) > 0 {
|
||||||
|
intercepted = true
|
||||||
|
logger.Debugf("Intercepted Right Arrow (accepted ghost text: %q)", ghostText)
|
||||||
|
bufferMu.Lock()
|
||||||
|
naiveBuffer += ghostText
|
||||||
|
cursorOffset = 0
|
||||||
|
bufferMu.Unlock()
|
||||||
|
overlay.ClearGhostTextState()
|
||||||
|
_, _ = ptmx.Write([]byte(ghostText))
|
||||||
|
shouldOverlayDraw = true
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
bufferMu.Lock()
|
bufferMu.Lock()
|
||||||
if naiveBuffer != "" || overlay.IsVisible() {
|
if naiveBuffer != "" || overlay.IsVisible() {
|
||||||
cursorOffset--
|
cursorOffset--
|
||||||
@@ -813,7 +823,8 @@ func runWrapper() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if b == 0x12 { // ctrl+r: toggle between command specs and command history
|
if matched, consumed := config.MatchKey(inputSlice[i:], config.Get().Keybindings.ToggleMode); matched { // ctrl+r: toggle between command specs and command history
|
||||||
|
i += consumed - 1
|
||||||
intercepted = true
|
intercepted = true
|
||||||
activeModeMu.Lock()
|
activeModeMu.Lock()
|
||||||
if activeMode == "spec" {
|
if activeMode == "spec" {
|
||||||
@@ -869,12 +880,33 @@ func runWrapper() {
|
|||||||
}
|
}
|
||||||
renderMu.Unlock()
|
renderMu.Unlock()
|
||||||
|
|
||||||
|
if cmdToSubmit == "" {
|
||||||
|
bufferMu.Lock()
|
||||||
|
cmdToSubmit = naiveBuffer
|
||||||
|
bufferMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(cmdToSubmit) == "iris reload" {
|
||||||
|
if newCfg, err := config.Load(); err == nil {
|
||||||
|
config.Init(newCfg)
|
||||||
|
disableGhostText.Store(!newCfg.UI.GhostText)
|
||||||
|
}
|
||||||
|
// Clear the shell line with Ctrl+U, then echo message, then Enter
|
||||||
|
msg := "echo -e '\\033[32m✓ Iris configuration reloaded successfully.\\033[0m'\r"
|
||||||
|
_, _ = ptmx.Write(append([]byte{0x15}, []byte(msg)...))
|
||||||
|
bufferMu.Lock()
|
||||||
|
naiveBuffer = ""
|
||||||
|
cursorOffset = 0
|
||||||
|
bufferMu.Unlock()
|
||||||
|
disableGhostText.Store(false)
|
||||||
|
shouldOverlayDraw = false
|
||||||
|
userNavigated.Store(false)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
isCommandActive.Store(true)
|
isCommandActive.Store(true)
|
||||||
_, _ = ptmx.Write([]byte{b})
|
_, _ = ptmx.Write([]byte{b})
|
||||||
bufferMu.Lock()
|
bufferMu.Lock()
|
||||||
if cmdToSubmit == "" {
|
|
||||||
cmdToSubmit = naiveBuffer
|
|
||||||
}
|
|
||||||
lastSubmittedCommand = strings.TrimSpace(cmdToSubmit)
|
lastSubmittedCommand = strings.TrimSpace(cmdToSubmit)
|
||||||
naiveBuffer = ""
|
naiveBuffer = ""
|
||||||
cursorOffset = 0
|
cursorOffset = 0
|
||||||
@@ -930,12 +962,8 @@ func runWrapper() {
|
|||||||
bufferMu.Unlock()
|
bufferMu.Unlock()
|
||||||
|
|
||||||
_, _ = ptmx.Write(append([]byte{0x15}, selected...))
|
_, _ = ptmx.Write(append([]byte{0x15}, selected...))
|
||||||
|
overlay.ResetCursor()
|
||||||
overlay.ResetCursor() // this prevents when you tab, it switches between suggestions non-stop
|
shouldOverlayDraw = true
|
||||||
|
|
||||||
shouldOverlayDraw = true // <- rerender after tab to choose, if you set to false,
|
|
||||||
// when you press tab continually, it will print all folder from menu suggestions
|
|
||||||
// and make the cursor jump to next line
|
|
||||||
userNavigated.Store(false)
|
userNavigated.Store(false)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -1034,8 +1062,9 @@ func runWrapper() {
|
|||||||
// track normal printable characters in the buffer for matching
|
// track normal printable characters in the buffer for matching
|
||||||
if b >= 32 && b <= 126 {
|
if b >= 32 && b <= 126 {
|
||||||
// expand alias on space, but only when typing manually (not pasting)
|
// expand alias on space, but only when typing manually (not pasting)
|
||||||
|
// and only if expand-alias configuration is enabled
|
||||||
bufferMu.Lock()
|
bufferMu.Lock()
|
||||||
isSpaceAlias := !inBracketedPaste && b == ' ' && naiveBuffer != "" && !strings.Contains(naiveBuffer, " ")
|
isSpaceAlias := config.Get().Core.ExpandAlias && !inBracketedPaste && b == ' ' && naiveBuffer != "" && !strings.Contains(naiveBuffer, " ")
|
||||||
var target string
|
var target string
|
||||||
var ok bool
|
var ok bool
|
||||||
if isSpaceAlias {
|
if isSpaceAlias {
|
||||||
|
|||||||
+5
-3
@@ -6,6 +6,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/versenilvis/iris/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// for tracking current working dir
|
// for tracking current working dir
|
||||||
@@ -34,7 +36,7 @@ func GetCWD() string {
|
|||||||
// dir = "src/", filePrefix = "mai"
|
// dir = "src/", filePrefix = "mai"
|
||||||
// search for which matches "mai" case, and show it (whici is both main.go and main.py)
|
// search for which matches "mai" case, and show it (whici is both main.go and main.py)
|
||||||
// but if you type "go run src/mai" -> it will only shows suggestion about src/main.go
|
// but if you type "go run src/mai" -> it will only shows suggestion about src/main.go
|
||||||
// NOTE: it will skip hidden file start with dot prefix
|
// NOTE: it will skip hidden file start with dot prefix unless configured otherwise
|
||||||
func FileGenerator(filters ...string) GeneratorFunc {
|
func FileGenerator(filters ...string) GeneratorFunc {
|
||||||
dirOnly := false
|
dirOnly := false
|
||||||
filterSet := make(map[string]bool)
|
filterSet := make(map[string]bool)
|
||||||
@@ -82,8 +84,8 @@ func FileGenerator(filters ...string) GeneratorFunc {
|
|||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
name := entry.Name()
|
name := entry.Name()
|
||||||
|
|
||||||
// skip hidden files
|
// skip hidden files if not configured to show them
|
||||||
if strings.HasPrefix(name, ".") {
|
if !config.Get().UI.ShowHiddenFiles && strings.HasPrefix(entry.Name(), ".") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user