Enha: shellmode within inputfield
This commit is contained in:
18
helpfuncs.go
18
helpfuncs.go
@@ -426,12 +426,11 @@ func deepseekModelValidator() error {
|
|||||||
|
|
||||||
func toggleShellMode() {
|
func toggleShellMode() {
|
||||||
shellMode = !shellMode
|
shellMode = !shellMode
|
||||||
|
setShellMode(shellMode)
|
||||||
if shellMode {
|
if shellMode {
|
||||||
// Update input placeholder to indicate shell mode
|
shellInput.SetLabel(fmt.Sprintf("[%s]$ ", cfg.FilePickerDir))
|
||||||
textArea.SetPlaceholder("SHELL MODE: Enter command and press <Esc> to execute")
|
|
||||||
} else {
|
} else {
|
||||||
// Reset to normal mode
|
textArea.SetPlaceholder("input is multiline; press <Enter> to start the next line;\npress <Esc> to send the message.")
|
||||||
textArea.SetPlaceholder("input is multiline; press <Enter> to start the next line;\npress <Esc> to send the message. Alt+1 to exit shell mode")
|
|
||||||
}
|
}
|
||||||
updateStatusLine()
|
updateStatusLine()
|
||||||
}
|
}
|
||||||
@@ -443,7 +442,11 @@ func updateFlexLayout() {
|
|||||||
}
|
}
|
||||||
flex.Clear()
|
flex.Clear()
|
||||||
flex.AddItem(textView, 0, 40, false)
|
flex.AddItem(textView, 0, 40, false)
|
||||||
|
if shellMode {
|
||||||
|
flex.AddItem(shellInput, 0, 10, false)
|
||||||
|
} else {
|
||||||
flex.AddItem(textArea, 0, 10, false)
|
flex.AddItem(textArea, 0, 10, false)
|
||||||
|
}
|
||||||
if positionVisible {
|
if positionVisible {
|
||||||
flex.AddItem(statusLineWidget, 0, 2, false)
|
flex.AddItem(statusLineWidget, 0, 2, false)
|
||||||
}
|
}
|
||||||
@@ -451,6 +454,8 @@ func updateFlexLayout() {
|
|||||||
focused := app.GetFocus()
|
focused := app.GetFocus()
|
||||||
if focused == textView {
|
if focused == textView {
|
||||||
app.SetFocus(textView)
|
app.SetFocus(textView)
|
||||||
|
} else if shellMode {
|
||||||
|
app.SetFocus(shellInput)
|
||||||
} else {
|
} else {
|
||||||
app.SetFocus(textArea)
|
app.SetFocus(textArea)
|
||||||
}
|
}
|
||||||
@@ -515,6 +520,11 @@ func executeCommandAndDisplay(cmdText string) {
|
|||||||
textView.ScrollToEnd()
|
textView.ScrollToEnd()
|
||||||
}
|
}
|
||||||
colorText()
|
colorText()
|
||||||
|
// Add command to history (avoid duplicates at the end)
|
||||||
|
if len(shellHistory) == 0 || shellHistory[len(shellHistory)-1] != cmdText {
|
||||||
|
shellHistory = append(shellHistory, cmdText)
|
||||||
|
}
|
||||||
|
shellHistoryPos = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseCommand splits command string handling quotes properly
|
// parseCommand splits command string handling quotes properly
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -13,6 +13,8 @@ var (
|
|||||||
injectRole = true
|
injectRole = true
|
||||||
selectedIndex = int(-1)
|
selectedIndex = int(-1)
|
||||||
shellMode = false
|
shellMode = false
|
||||||
|
shellHistory []string
|
||||||
|
shellHistoryPos int = -1
|
||||||
thinkingCollapsed = false
|
thinkingCollapsed = false
|
||||||
statusLineTempl = "help (F12) | [%s:-:b]llm writes[-:-:-] (F6 to interrupt) | chat: [orange:-:b]%s[-:-:-] (F1) | [%s:-:b]tool use[-:-:-] (ctrl+k) | model: [%s:-:b]%s[-:-:-] (ctrl+l) | [%s:-:b]skip LLM resp[-:-:-] (F10)\nAPI: [orange:-:b]%s[-:-:-] (ctrl+v) | writing as: [orange:-:b]%s[-:-:-] (ctrl+q) | bot will write as [orange:-:b]%s[-:-:-] (ctrl+x)"
|
statusLineTempl = "help (F12) | [%s:-:b]llm writes[-:-:-] (F6 to interrupt) | chat: [orange:-:b]%s[-:-:-] (F1) | [%s:-:b]tool use[-:-:-] (ctrl+k) | model: [%s:-:b]%s[-:-:-] (ctrl+l) | [%s:-:b]skip LLM resp[-:-:-] (F10)\nAPI: [orange:-:b]%s[-:-:-] (ctrl+v) | writing as: [orange:-:b]%s[-:-:-] (ctrl+q) | bot will write as [orange:-:b]%s[-:-:-] (ctrl+x)"
|
||||||
focusSwitcher = map[tview.Primitive]tview.Primitive{}
|
focusSwitcher = map[tview.Primitive]tview.Primitive{}
|
||||||
|
|||||||
60
popups.go
60
popups.go
@@ -405,6 +405,66 @@ func showFileCompletionPopup(filter string) {
|
|||||||
app.SetFocus(widget)
|
app.SetFocus(widget)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func showShellFileCompletionPopup(filter string) {
|
||||||
|
baseDir := cfg.FilePickerDir
|
||||||
|
if baseDir == "" {
|
||||||
|
baseDir = "."
|
||||||
|
}
|
||||||
|
complMatches := scanFiles(baseDir, filter)
|
||||||
|
if len(complMatches) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(complMatches) == 1 {
|
||||||
|
currentText := shellInput.GetText()
|
||||||
|
atIdx := strings.LastIndex(currentText, "@")
|
||||||
|
if atIdx >= 0 {
|
||||||
|
before := currentText[:atIdx]
|
||||||
|
shellInput.SetText(before + complMatches[0])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
widget := tview.NewList().ShowSecondaryText(false).
|
||||||
|
SetSelectedBackgroundColor(tcell.ColorGray)
|
||||||
|
widget.SetTitle("file completion").SetBorder(true)
|
||||||
|
for _, m := range complMatches {
|
||||||
|
widget.AddItem(m, "", 0, nil)
|
||||||
|
}
|
||||||
|
widget.SetSelectedFunc(func(index int, mainText string, secondaryText string, shortcut rune) {
|
||||||
|
currentText := shellInput.GetText()
|
||||||
|
atIdx := strings.LastIndex(currentText, "@")
|
||||||
|
if atIdx >= 0 {
|
||||||
|
before := currentText[:atIdx]
|
||||||
|
shellInput.SetText(before + mainText)
|
||||||
|
}
|
||||||
|
pages.RemovePage("shellFileCompletionPopup")
|
||||||
|
app.SetFocus(shellInput)
|
||||||
|
})
|
||||||
|
widget.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||||
|
if event.Key() == tcell.KeyEscape {
|
||||||
|
pages.RemovePage("shellFileCompletionPopup")
|
||||||
|
app.SetFocus(shellInput)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if event.Key() == tcell.KeyRune && event.Rune() == 'x' {
|
||||||
|
pages.RemovePage("shellFileCompletionPopup")
|
||||||
|
app.SetFocus(shellInput)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return event
|
||||||
|
})
|
||||||
|
modal := func(p tview.Primitive, width, height int) tview.Primitive {
|
||||||
|
return tview.NewFlex().
|
||||||
|
AddItem(nil, 0, 1, false).
|
||||||
|
AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
|
||||||
|
AddItem(nil, 0, 1, false).
|
||||||
|
AddItem(p, height, 1, true).
|
||||||
|
AddItem(nil, 0, 1, false), width, 1, true).
|
||||||
|
AddItem(nil, 0, 1, false)
|
||||||
|
}
|
||||||
|
pages.AddPage("shellFileCompletionPopup", modal(widget, 80, 20), true, true)
|
||||||
|
app.SetFocus(widget)
|
||||||
|
}
|
||||||
|
|
||||||
func updateWidgetColors(theme *tview.Theme) {
|
func updateWidgetColors(theme *tview.Theme) {
|
||||||
bgColor := theme.PrimitiveBackgroundColor
|
bgColor := theme.PrimitiveBackgroundColor
|
||||||
fgColor := theme.PrimaryTextColor
|
fgColor := theme.PrimaryTextColor
|
||||||
|
|||||||
80
tui.go
80
tui.go
@@ -34,6 +34,7 @@ var (
|
|||||||
indexPickWindow *tview.InputField
|
indexPickWindow *tview.InputField
|
||||||
renameWindow *tview.InputField
|
renameWindow *tview.InputField
|
||||||
roleEditWindow *tview.InputField
|
roleEditWindow *tview.InputField
|
||||||
|
shellInput *tview.InputField
|
||||||
fullscreenMode bool
|
fullscreenMode bool
|
||||||
positionVisible bool = true
|
positionVisible bool = true
|
||||||
scrollToEndEnabled bool = true
|
scrollToEndEnabled bool = true
|
||||||
@@ -124,12 +125,75 @@ Press <Enter> or 'x' to return
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func setShellMode(enabled bool) {
|
||||||
|
shellMode = enabled
|
||||||
|
go func() {
|
||||||
|
app.QueueUpdateDraw(func() {
|
||||||
|
updateFlexLayout()
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Start background goroutine to update model color cache
|
// Start background goroutine to update model color cache
|
||||||
startModelColorUpdater()
|
startModelColorUpdater()
|
||||||
tview.Styles = colorschemes["default"]
|
tview.Styles = colorschemes["default"]
|
||||||
app = tview.NewApplication()
|
app = tview.NewApplication()
|
||||||
pages = tview.NewPages()
|
pages = tview.NewPages()
|
||||||
|
shellInput = tview.NewInputField().
|
||||||
|
SetLabel(fmt.Sprintf("[%s]$ ", cfg.FilePickerDir)). // dynamic prompt
|
||||||
|
SetFieldWidth(0).
|
||||||
|
SetDoneFunc(func(key tcell.Key) {
|
||||||
|
if key == tcell.KeyEnter {
|
||||||
|
cmd := shellInput.GetText()
|
||||||
|
if cmd != "" {
|
||||||
|
executeCommandAndDisplay(cmd)
|
||||||
|
}
|
||||||
|
shellInput.SetText("")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Copy your file completion logic to shellInput's InputCapture
|
||||||
|
shellInput.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||||
|
if !shellMode {
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
// Handle Up arrow for history previous
|
||||||
|
if event.Key() == tcell.KeyUp {
|
||||||
|
if len(shellHistory) > 0 {
|
||||||
|
if shellHistoryPos < len(shellHistory)-1 {
|
||||||
|
shellHistoryPos++
|
||||||
|
shellInput.SetText(shellHistory[len(shellHistory)-1-shellHistoryPos])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Handle Down arrow for history next
|
||||||
|
if event.Key() == tcell.KeyDown {
|
||||||
|
if shellHistoryPos > 0 {
|
||||||
|
shellHistoryPos--
|
||||||
|
shellInput.SetText(shellHistory[len(shellHistory)-1-shellHistoryPos])
|
||||||
|
} else if shellHistoryPos == 0 {
|
||||||
|
shellHistoryPos = -1
|
||||||
|
shellInput.SetText("")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Reset history position when user types
|
||||||
|
if event.Key() == tcell.KeyRune {
|
||||||
|
shellHistoryPos = -1
|
||||||
|
}
|
||||||
|
// Handle Tab key for @ file completion
|
||||||
|
if event.Key() == tcell.KeyTab {
|
||||||
|
currentText := shellInput.GetText()
|
||||||
|
atIndex := strings.LastIndex(currentText, "@")
|
||||||
|
if atIndex >= 0 {
|
||||||
|
filter := currentText[atIndex+1:]
|
||||||
|
showShellFileCompletionPopup(filter)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return event
|
||||||
|
})
|
||||||
textArea = tview.NewTextArea().
|
textArea = tview.NewTextArea().
|
||||||
SetPlaceholder("input is multiline; press <Enter> to start the next line;\npress <Esc> to send the message.")
|
SetPlaceholder("input is multiline; press <Enter> to start the next line;\npress <Esc> to send the message.")
|
||||||
textArea.SetBorder(true).SetTitle("input")
|
textArea.SetBorder(true).SetTitle("input")
|
||||||
@@ -948,14 +1012,16 @@ func init() {
|
|||||||
}
|
}
|
||||||
// cannot send msg in editMode or botRespMode
|
// cannot send msg in editMode or botRespMode
|
||||||
if event.Key() == tcell.KeyEscape && !editMode && !botRespMode {
|
if event.Key() == tcell.KeyEscape && !editMode && !botRespMode {
|
||||||
msgText := textArea.GetText()
|
if shellMode {
|
||||||
if shellMode && msgText != "" {
|
cmdText := shellInput.GetText()
|
||||||
// In shell mode, execute command instead of sending to LLM
|
if cmdText != "" {
|
||||||
executeCommandAndDisplay(msgText)
|
executeCommandAndDisplay(cmdText)
|
||||||
textArea.SetText("", true) // Clear the input area
|
shellInput.SetText("")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
} else if !shellMode {
|
}
|
||||||
// Normal mode - send to LLM
|
msgText := textArea.GetText()
|
||||||
|
if msgText != "" {
|
||||||
nl := "\n\n" // keep empty lines between messages
|
nl := "\n\n" // keep empty lines between messages
|
||||||
prevText := textView.GetText(true)
|
prevText := textView.GetText(true)
|
||||||
persona := cfg.UserRole
|
persona := cfg.UserRole
|
||||||
|
|||||||
Reference in New Issue
Block a user