Skip to content

Commit b56207b

Browse files
will-osborneclaude
andcommitted
Fix file picker swallowing Enter when @mention has no matches
Unescaped spaces now terminate the @ token, dismissing the picker so Enter always sends the prompt. Escaped spaces (\ ) are supported for file paths that contain literal spaces. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2a9658a commit b56207b

1 file changed

Lines changed: 23 additions & 5 deletions

File tree

tui.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -829,14 +829,15 @@ func isSubsequence(needle, haystack string) bool {
829829
// filePickerDetect checks the textarea value and cursor position and returns
830830
// (active, query, atPosition). It only activates when @ is preceded by
831831
// whitespace or is at the start of the line, and the cursor is within the
832-
// @ token (no whitespace between @ and cursor).
832+
// @ token. An unescaped space terminates the token (dismissing the picker);
833+
// escaped spaces (backslash-space) are treated as literal spaces in the path.
833834
func filePickerDetect(value string, cursorPos int) (bool, string, int) {
834835
runes := []rune(value)
835836
if cursorPos < 0 || cursorPos > len(runes) {
836837
return false, "", 0
837838
}
838839
// Walk backwards from cursor to find the @ character.
839-
// Stop if we hit whitespace (no active @ mention at cursor).
840+
// Stop if we hit an unescaped whitespace character.
840841
pos := cursorPos - 1
841842
for pos >= 0 {
842843
r := runes[pos]
@@ -845,18 +846,35 @@ func filePickerDetect(value string, cursorPos int) (bool, string, int) {
845846
if pos > 0 && !unicode.IsSpace(runes[pos-1]) {
846847
return false, "", 0
847848
}
848-
query := string(runes[pos+1 : cursorPos])
849+
query := filePickerUnescapeSpaces(string(runes[pos+1 : cursorPos]))
849850
return true, query, pos
850851
}
851852
if unicode.IsSpace(r) {
852-
// Hit whitespace before finding @.
853+
// Check if this space is escaped with a preceding backslash.
854+
if pos > 0 && runes[pos-1] == '\\' {
855+
pos--
856+
continue
857+
}
858+
// Unescaped whitespace — no active @ mention at cursor.
853859
return false, "", 0
854860
}
855861
pos--
856862
}
857863
return false, "", 0
858864
}
859865

866+
// filePickerUnescapeSpaces converts escaped spaces ("\ ") to literal spaces
867+
// in a file picker query.
868+
func filePickerUnescapeSpaces(s string) string {
869+
return strings.ReplaceAll(s, "\\ ", " ")
870+
}
871+
872+
// filePickerEscapeSpaces converts literal spaces to escaped spaces ("\ ")
873+
// for insertion into the textarea.
874+
func filePickerEscapeSpaces(s string) string {
875+
return strings.ReplaceAll(s, " ", "\\ ")
876+
}
877+
860878
// ── model ─────────────────────────────────────────────────────────────────────
861879

862880
type tuiModel struct {
@@ -6810,7 +6828,7 @@ func (m *tuiModel) filePickerComplete() {
68106828
if atPos > 0 {
68116829
newVal = string(runes[:atPos])
68126830
}
6813-
insertion := "@" + selected
6831+
insertion := "@" + filePickerEscapeSpaces(selected)
68146832
newVal += insertion
68156833
if cursorPos < len(runes) {
68166834
newVal += string(runes[cursorPos:])

0 commit comments

Comments
 (0)