ux improvements
This commit is contained in:
@@ -1631,6 +1631,29 @@ func (e *editor) histNav(up bool) {
|
|||||||
e.pos = len(e.buf)
|
e.pos = len(e.buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wordBack moves the cursor back to the start of the current/previous word.
|
||||||
|
func (e *editor) wordBack() {
|
||||||
|
for e.pos > 0 && unicode.IsSpace(e.buf[e.pos-1]) { e.pos-- }
|
||||||
|
for e.pos > 0 && !unicode.IsSpace(e.buf[e.pos-1]) { e.pos-- }
|
||||||
|
}
|
||||||
|
|
||||||
|
// wordFwd moves the cursor forward to the end of the current/next word.
|
||||||
|
func (e *editor) wordFwd() {
|
||||||
|
n := len(e.buf)
|
||||||
|
for e.pos < n && unicode.IsSpace(e.buf[e.pos]) { e.pos++ }
|
||||||
|
for e.pos < n && !unicode.IsSpace(e.buf[e.pos]) { e.pos++ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// delWordBack deletes the word preceding the cursor (Emacs Ctrl+W).
|
||||||
|
func (e *editor) delWordBack() {
|
||||||
|
if e.pos == 0 { return }
|
||||||
|
start := e.pos
|
||||||
|
for start > 0 && unicode.IsSpace(e.buf[start-1]) { start-- }
|
||||||
|
for start > 0 && !unicode.IsSpace(e.buf[start-1]) { start-- }
|
||||||
|
e.buf = append(e.buf[:start], e.buf[e.pos:]...)
|
||||||
|
e.pos = start
|
||||||
|
}
|
||||||
|
|
||||||
func readPlain(prompt string) (string, bool) {
|
func readPlain(prompt string) (string, bool) {
|
||||||
fmt.Print(prompt)
|
fmt.Print(prompt)
|
||||||
line, err := stdin.ReadString('\n')
|
line, err := stdin.ReadString('\n')
|
||||||
@@ -1681,6 +1704,25 @@ func readLine(prompt string) (string, bool) {
|
|||||||
fmt.Print("\r\n")
|
fmt.Print("\r\n")
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
// Ctrl+D with non-empty buffer: delete character under cursor
|
||||||
|
if e.pos < len(e.buf) {
|
||||||
|
e.buf = append(e.buf[:e.pos], e.buf[e.pos+1:]...)
|
||||||
|
}
|
||||||
|
case 0x01:
|
||||||
|
e.pos = 0 // Ctrl+A: beginning of line
|
||||||
|
case 0x05:
|
||||||
|
e.pos = len(e.buf) // Ctrl+E: end of line
|
||||||
|
case 0x02:
|
||||||
|
if e.pos > 0 { e.pos-- } // Ctrl+B: backward char
|
||||||
|
case 0x06:
|
||||||
|
if e.pos < len(e.buf) { e.pos++ } // Ctrl+F: forward char
|
||||||
|
case 0x17:
|
||||||
|
e.delWordBack() // Ctrl+W: delete previous word
|
||||||
|
case 0x0b:
|
||||||
|
e.buf = e.buf[:e.pos] // Ctrl+K: kill to end of line
|
||||||
|
case 0x15:
|
||||||
|
e.buf = e.buf[e.pos:] // Ctrl+U: kill to start of line
|
||||||
|
e.pos = 0
|
||||||
case 0x7f, 0x08:
|
case 0x7f, 0x08:
|
||||||
if e.pos > 0 {
|
if e.pos > 0 {
|
||||||
e.buf = append(e.buf[:e.pos-1], e.buf[e.pos:]...)
|
e.buf = append(e.buf[:e.pos-1], e.buf[e.pos:]...)
|
||||||
@@ -1688,10 +1730,56 @@ func readLine(prompt string) (string, bool) {
|
|||||||
}
|
}
|
||||||
case 0x1b:
|
case 0x1b:
|
||||||
b1, err1 := stdin.ReadByte()
|
b1, err1 := stdin.ReadByte()
|
||||||
b2, err2 := stdin.ReadByte()
|
if err1 != nil { continue }
|
||||||
if err1 != nil || err2 != nil { continue }
|
|
||||||
if b1 == '[' {
|
if b1 == '[' {
|
||||||
|
// Read the full CSI sequence (terminated by a byte in 0x40-0x7e).
|
||||||
|
var seq []byte
|
||||||
|
for {
|
||||||
|
b, err := stdin.ReadByte()
|
||||||
|
if err != nil { break }
|
||||||
|
seq = append(seq, b)
|
||||||
|
if b >= 0x40 && b <= 0x7e { break }
|
||||||
|
}
|
||||||
|
if len(seq) == 0 { continue }
|
||||||
|
last := seq[len(seq)-1]
|
||||||
|
switch last {
|
||||||
|
case 'A':
|
||||||
|
e.histNav(true)
|
||||||
|
case 'B':
|
||||||
|
e.histNav(false)
|
||||||
|
case 'C':
|
||||||
|
if strings.Contains(string(seq), "5") {
|
||||||
|
e.wordFwd() // Ctrl+Right
|
||||||
|
} else if e.pos < len(e.buf) {
|
||||||
|
e.pos++
|
||||||
|
}
|
||||||
|
case 'D':
|
||||||
|
if strings.Contains(string(seq), "5") {
|
||||||
|
e.wordBack() // Ctrl+Left
|
||||||
|
} else if e.pos > 0 {
|
||||||
|
e.pos--
|
||||||
|
}
|
||||||
|
case 'H':
|
||||||
|
e.pos = 0 // Home (ESC[H)
|
||||||
|
case 'F':
|
||||||
|
e.pos = len(e.buf) // End (ESC[F)
|
||||||
|
case '~':
|
||||||
|
// Home/End on some terminals: ESC[1~ / ESC[4~
|
||||||
|
if len(seq) >= 1 && seq[0] == '1' {
|
||||||
|
e.pos = 0
|
||||||
|
} else if len(seq) >= 1 && seq[0] == '4' {
|
||||||
|
e.pos = len(e.buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if b1 == 'O' {
|
||||||
|
// xterm application cursor keys: ESC O H/F (Home/End), A-D (arrows)
|
||||||
|
b2, err2 := stdin.ReadByte()
|
||||||
|
if err2 != nil { continue }
|
||||||
switch b2 {
|
switch b2 {
|
||||||
|
case 'H':
|
||||||
|
e.pos = 0
|
||||||
|
case 'F':
|
||||||
|
e.pos = len(e.buf)
|
||||||
case 'A':
|
case 'A':
|
||||||
e.histNav(true)
|
e.histNav(true)
|
||||||
case 'B':
|
case 'B':
|
||||||
@@ -1808,6 +1896,17 @@ func main() {
|
|||||||
u = strings.TrimSpace(u)
|
u = strings.TrimSpace(u)
|
||||||
if u == "" { continue }
|
if u == "" { continue }
|
||||||
addHistory(u)
|
addHistory(u)
|
||||||
|
// Command aliases: /model -> /cfg model, /endpoint -> /cfg endpoint.
|
||||||
|
// (Note: /models is a distinct command and is intentionally not matched.)
|
||||||
|
if u == "/model" {
|
||||||
|
u = "/cfg model"
|
||||||
|
} else if strings.HasPrefix(u, "/model ") {
|
||||||
|
u = "/cfg model " + strings.TrimSpace(strings.TrimPrefix(u, "/model "))
|
||||||
|
} else if u == "/endpoint" {
|
||||||
|
u = "/cfg endpoint"
|
||||||
|
} else if strings.HasPrefix(u, "/endpoint ") {
|
||||||
|
u = "/cfg endpoint " + strings.TrimSpace(strings.TrimPrefix(u, "/endpoint "))
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case u == "/quit":
|
case u == "/quit":
|
||||||
goto done
|
goto done
|
||||||
@@ -1911,6 +2010,8 @@ func main() {
|
|||||||
{"/load <id>", "load session"},
|
{"/load <id>", "load session"},
|
||||||
{"/compact", "compact context"},
|
{"/compact", "compact context"},
|
||||||
{"/cfg <k> [v]", "get/set config"},
|
{"/cfg <k> [v]", "get/set config"},
|
||||||
|
{"/model [m]", "alias for /cfg model"},
|
||||||
|
{"/endpoint [e]", "alias for /cfg endpoint"},
|
||||||
{"!<cmd>", "run shell command directly"},
|
{"!<cmd>", "run shell command directly"},
|
||||||
{"/models", "list models at endpoint"},
|
{"/models", "list models at endpoint"},
|
||||||
{"/help", "show help"},
|
{"/help", "show help"},
|
||||||
|
|||||||
Reference in New Issue
Block a user