Initial support for application specific overrides.
This commit is contained in:
1 parent
901fd58ba8
commit
b45f800489
37 files changed
+3909
-475
No files matched your search
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"git.stormux.org/storm/fenrir/internal/screen"
|
||||
)
|
||||
|
||||
// Unlike Detector, ShellDetector excludes passwords, questions and completion
|
||||
// messages. It observes raw screens, independently of application speech filters.
|
||||
type ShellDetector struct {
|
||||
previous map[string]int
|
||||
terminal int
|
||||
}
|
||||
|
||||
var shellPattern = regexp.MustCompile(`^(?:[$#>]|\[[^\r\n]+\]\s*[$#>]|[a-zA-Z0-9._-]+[$#>]|[a-zA-Z0-9._-]+@[^\s<>]+.*[$#>])\s*$`)
|
||||
|
||||
func (d *ShellDetector) Observe(next screen.Snapshot) bool {
|
||||
if !next.Active || next.Rows <= 0 || next.Cols <= 0 || len(next.Cells) != next.Rows*next.Cols {
|
||||
return false
|
||||
}
|
||||
counts := make(map[string]int)
|
||||
for row := range next.Rows {
|
||||
counts[strings.TrimSpace(next.Text(row))]++
|
||||
}
|
||||
text := ""
|
||||
if next.Y >= 0 && next.Y < next.Rows {
|
||||
text = strings.TrimSpace(next.Text(next.Y))
|
||||
}
|
||||
fresh := d.previous != nil && d.terminal == next.Terminal && shellPattern.MatchString(text) && counts[text] > d.previous[text]
|
||||
d.previous, d.terminal = counts, next.Terminal
|
||||
return fresh
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"git.stormux.org/storm/fenrir/internal/screen"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestShellDetectorRequiresFreshCursorPrompt(t *testing.T) {
|
||||
makeScreen := func(text string, cursor int) screen.Snapshot {
|
||||
s := screen.Snapshot{Terminal: 1, Active: true, Rows: 3, Cols: 80, Y: cursor, Cells: make([]rune, 240)}
|
||||
for i := range s.Cells {
|
||||
s.Cells[i] = ' '
|
||||
}
|
||||
copy(s.Line(1), []rune(text))
|
||||
return s
|
||||
}
|
||||
for _, text := range []string{"[Username@host directory] $", "Username@host:~$", "$", "#"} {
|
||||
var detector ShellDetector
|
||||
detector.Observe(makeScreen("application", 1))
|
||||
s := makeScreen(text, 1)
|
||||
s.Generation = 1
|
||||
if !detector.Observe(s) {
|
||||
t.Fatal("new shell prompt missed", text)
|
||||
}
|
||||
if detector.Observe(s) {
|
||||
t.Fatal("unchanged prompt repeated", text)
|
||||
}
|
||||
}
|
||||
for _, text := range []string{"Password:", "Confirm? [y/n]", "<Username> $", "<Username@host> $", "press any key to continue", "Worked for 2s • 1:00 PM"} {
|
||||
var detector ShellDetector
|
||||
detector.Observe(makeScreen("application", 1))
|
||||
if detector.Observe(makeScreen(text, 1)) {
|
||||
t.Fatal("non-shell prompt", text)
|
||||
}
|
||||
}
|
||||
var detector ShellDetector
|
||||
s := makeScreen("$", 0)
|
||||
detector.Observe(s)
|
||||
s.Y = 1
|
||||
if detector.Observe(s) {
|
||||
t.Fatal("old prompt became fresh after cursor motion")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user