@@ -10,8 +10,10 @@ import (
1010 "time"
1111
1212 "github.com/alvinunreal/tmuxai/config"
13- "github.com/alvinunreal/tmuxai/logger"
14- "github.com/chzyer/readline"
13+ "github.com/nyaosorg/go-readline-ny"
14+ "github.com/nyaosorg/go-readline-ny/completion"
15+ "github.com/nyaosorg/go-readline-ny/keys"
16+ "github.com/nyaosorg/go-readline-ny/simplehistory"
1517)
1618
1719// Message represents a chat message
@@ -37,30 +39,42 @@ func NewCLIInterface(manager *Manager) *CLIInterface {
3739func (c * CLIInterface ) Start (initMessage string ) error {
3840 c .printWelcomeMessage ()
3941
40- rl , err := readline .NewEx (& readline.Config {
41- Prompt : c .manager .GetPrompt (),
42- HistoryFile : config .GetConfigFilePath ("history" ),
43- HistorySearchFold : true ,
44- InterruptPrompt : "^C" ,
45- EOFPrompt : "exit" ,
46- DisableAutoSaveHistory : false ,
47- AutoComplete : c .newCompleter (),
48- })
49- if err != nil {
50- return fmt .Errorf ("failed to initialize readline: %w" , err )
42+ // Initialize history
43+ history := simplehistory .New ()
44+ historyFilePath := config .GetConfigFilePath ("history" )
45+
46+ // Load history from file if it exists
47+ if historyData , err := os .ReadFile (historyFilePath ); err == nil {
48+ for _ , line := range strings .Split (string (historyData ), "\n " ) {
49+ if line = strings .TrimSpace (line ); line != "" {
50+ history .Add (line )
51+ }
52+ }
53+ }
54+
55+ // Initialize editor
56+ editor := & readline.Editor {
57+ PromptWriter : func (w io.Writer ) (int , error ) {
58+ return io .WriteString (w , c .manager .GetPrompt ())
59+ },
60+ History : history ,
61+ HistoryCycling : true ,
5162 }
52- defer rl .Close ()
63+
64+ // Bind TAB key to completion
65+ editor .BindKey (keys .CtrlI , c .newCompleter ())
5366
5467 if initMessage != "" {
5568 fmt .Printf ("%s%s\n " , c .manager .GetPrompt (), initMessage )
5669 c .processInput (initMessage )
5770 }
5871
72+ ctx := context .Background ()
73+
5974 for {
60- rl . SetPrompt ( c . manager . GetPrompt () )
75+ line , err := editor . ReadLine ( ctx )
6176
62- line , err := rl .Readline ()
63- if err == readline .ErrInterrupt {
77+ if err == readline .CtrlC {
6478 // Ctrl+C pressed, clear the line and continue
6579 continue
6680 } else if err == io .EOF {
@@ -70,15 +84,31 @@ func (c *CLIInterface) Start(initMessage string) error {
7084 return err
7185 }
7286
73- input := strings .TrimSpace (line )
74- if input == "exit" || input == "quit" {
87+ // Save history
88+ if line != "" {
89+ history .Add (line )
90+
91+ // Build history data by iterating through all entries
92+ historyLines := make ([]string , 0 , history .Len ())
93+ for i := 0 ; i < history .Len (); i ++ {
94+ historyLines = append (historyLines , history .At (i ))
95+ }
96+ historyData := strings .Join (historyLines , "\n " )
97+ os .WriteFile (historyFilePath , []byte (historyData ), 0644 )
98+ }
99+
100+ // Process the input (preserving multiline content)
101+ input := line // Keep the original line including newlines
102+
103+ // Check for exit/quit commands (only if it's the entire line content)
104+ trimmed := strings .TrimSpace (input )
105+ if trimmed == "exit" || trimmed == "quit" {
75106 return nil
76107 }
77- if input == "" {
108+ if trimmed == "" {
78109 continue
79110 }
80111
81- logger .Debug ("Processing User input: %s" , input )
82112 c .processInput (input )
83113 }
84114}
@@ -128,33 +158,26 @@ func (c *CLIInterface) processInput(input string) {
128158 signal .Stop (sigChan )
129159}
130160
131- // newCompleter creates a readline.AutoCompleter for command completion
132- func (c * CLIInterface ) newCompleter () readline.AutoCompleter {
133- configCompleter := readline .PcItem ("/config" ,
134- readline .PcItem ("set" ,
135- readline .PcItemDynamic (func (_ string ) []string {
136- // Only return the allowed keys
137- return AllowedConfigKeys
138- }),
139- ),
140- readline .PcItem ("get" ,
141- readline .PcItemDynamic (func (_ string ) []string {
142- // Only return the allowed keys
143- return AllowedConfigKeys
144- }),
145- ),
146- )
147-
148- // Create completers for each base command using the global subCommands variable
149- completers := make ([]readline.PrefixCompleterInterface , 0 , len (commands ))
150- for _ , cmd := range commands {
151- // Special handling for config to add nested completion
152- if cmd == "/config" {
153- completers = append (completers , configCompleter )
154- } else {
155- completers = append (completers , readline .PcItem (cmd ))
156- }
161+ // newCompleter creates a completion handler for command completion
162+ func (c * CLIInterface ) newCompleter () * completion.CmdCompletionOrList2 {
163+ return & completion.CmdCompletionOrList2 {
164+ Delimiter : " " ,
165+ Postfix : " " ,
166+ Candidates : func (field []string ) (forComp []string , forList []string ) {
167+ // Handle top-level commands
168+ if len (field ) == 0 || (len (field ) == 1 && ! strings .HasSuffix (field [0 ], " " )) {
169+ return commands , commands
170+ }
171+
172+ // Handle /config subcommands
173+ if len (field ) > 0 && field [0 ] == "/config" {
174+ if len (field ) == 1 || (len (field ) == 2 && ! strings .HasSuffix (field [1 ], " " )) {
175+ return []string {"set" , "get" }, []string {"set" , "get" }
176+ } else if len (field ) == 2 || (len (field ) == 3 && ! strings .HasSuffix (field [2 ], " " )) {
177+ return AllowedConfigKeys , AllowedConfigKeys
178+ }
179+ }
180+ return nil , nil
181+ },
157182 }
158-
159- return readline .NewPrefixCompleter (completers ... )
160183}
0 commit comments