gui: B3-click on word expands to file path (acme-style)

RMB on text without selection now expands to the word at cursor using
acme's isfilec character set: alphanumeric + . - + / : @

This matches acme behavior for clicking on file paths like foo.go:42
This commit is contained in:
Levi Neely 2026-10-08 16:20:29 +02:00
parent 5bf29198ff
commit b119668965
2 changed files with 85 additions and 8 deletions

View File

@ -250,10 +250,19 @@ Column {
}
mouse.accepted = true
} else if (mouse.button === Qt.RightButton) {
// B3: Plumb selection (no context menu)
// B3: Plumb selection or word under cursor
if (!plumber.available()) {
mouse.accepted = true
return
}
var sel = mainTextEdit.selectedText
if (sel && sel.trim() !== "" && plumber.available()) {
if (sel && sel.trim() !== "") {
plumber.plumb(sel.trim(), sessionCwd())
} else {
// No selection - expand word at click position (acme-style)
var pos = mainTextEdit.positionAt(mouse.x, mouse.y)
var word = wordAtCursor(mainTextEdit, pos)
if (word) plumber.plumb(word, sessionCwd())
}
mouse.accepted = true
}
@ -339,6 +348,33 @@ Column {
return cfg ? (cfg.cwd || "") : ""
}
// Expand cursor position to surrounding word/path for plumbing
// Matches acme's isfilec: alphanumeric + . - + / : @
function wordAtCursor(textEdit, cursorPos) {
var text = textEdit.getText(0, textEdit.length)
if (cursorPos < 0 || cursorPos > text.length) return ""
// isfilec from acme: alphanumeric + . - + / : @
function isfilec(ch) {
return /[a-zA-Z0-9.\-+\/:@]/.test(ch)
}
// Find start of word (acme also allows isaddrc chars going left)
var start = cursorPos
while (start > 0 && isfilec(text.charAt(start - 1))) {
start--
}
// Find end of word
var end = cursorPos
while (end < text.length && isfilec(text.charAt(end))) {
end++
}
if (start === end) return ""
return text.substring(start, end)
}
// Text segment component
Component {
id: textComponent
@ -396,10 +432,18 @@ Column {
}
mouse.accepted = true
} else if (mouse.button === Qt.RightButton) {
// B3: Plumb selection (no context menu)
// B3: Plumb selection or word under cursor
if (!plumber.available()) {
mouse.accepted = true
return
}
var sel = textEdit.selectedText
if (sel && sel.trim() !== "" && plumber.available()) {
if (sel && sel.trim() !== "") {
plumber.plumb(sel.trim(), sessionCwd())
} else {
var pos = textEdit.positionAt(mouse.x, mouse.y)
var word = wordAtCursor(textEdit, pos)
if (word) plumber.plumb(word, sessionCwd())
}
mouse.accepted = true
}
@ -505,10 +549,18 @@ Column {
if (sel && sel.trim() !== "") executeText(sel)
mouse.accepted = true
} else if (mouse.button === Qt.RightButton) {
// B3: Plumb selection (no context menu)
// B3: Plumb selection or word under cursor
if (!plumber.available()) {
mouse.accepted = true
return
}
var sel = sourceEdit.selectedText
if (sel && sel.trim() !== "" && plumber.available()) {
if (sel && sel.trim() !== "") {
plumber.plumb(sel.trim(), sessionCwd())
} else {
var pos = sourceEdit.positionAt(mouse.x, mouse.y)
var word = wordAtCursor(sourceEdit, pos)
if (word) plumber.plumb(word, sessionCwd())
}
mouse.accepted = true
}

View File

@ -44,6 +44,26 @@ Item {
return cfg ? (cfg.cwd || "") : ""
}
// Expand cursor position to surrounding word/path for plumbing
// Matches acme's isfilec: alphanumeric + . - + / : @
function wordAtCursor(textEdit, cursorPos) {
var text = textEdit.getText(0, textEdit.length)
if (cursorPos < 0 || cursorPos > text.length) return ""
function isfilec(ch) {
return /[a-zA-Z0-9.\-+\/:@]/.test(ch)
}
var start = cursorPos
while (start > 0 && isfilec(text.charAt(start - 1))) start--
var end = cursorPos
while (end < text.length && isfilec(text.charAt(end))) end++
if (start === end) return ""
return text.substring(start, end)
}
Component.onCompleted: {
loadChat()
scrollToBottom()
@ -503,10 +523,15 @@ Item {
// No selection - ignore
}
} else if (mouse.button === Qt.RightButton) {
// B3: Plumb selection (no context menu)
// B3: Plumb selection or word under cursor
if (!plumber.available()) return
var sel = promptInput.selectedText
if (sel && sel.trim() !== "" && plumber.available()) {
if (sel && sel.trim() !== "") {
plumber.plumb(sel.trim(), sessionCwd())
} else {
var pos = promptInput.positionAt(mouse.x, mouse.y)
var word = wordAtCursor(promptInput, pos)
if (word) plumber.plumb(word, sessionCwd())
}
}
}