remove inline autocomplete state machine from prompt input
This commit is contained in:
parent
9bab0e73c2
commit
95cf201f4c
190
gui/main.qml
190
gui/main.qml
|
|
@ -80,95 +80,6 @@ ApplicationWindow {
|
|||
{ name: "help", desc: "Show help" }
|
||||
]
|
||||
|
||||
// --- Inline autocomplete state machine ---
|
||||
// States: none -> start -> autocompleting -> done (tab/esc/no-match)
|
||||
property string _acBase: "" // text before the slash command (includes "/")
|
||||
property string _acTyped: "" // what user typed after /
|
||||
property var _acMatches: [] // matching options
|
||||
property bool _acActive: false // in autocomplete mode
|
||||
property int _acMatchIndex: 0 // which match is currently shown
|
||||
property string _acType: "cmd" // "cmd", "model", or "backend"
|
||||
|
||||
function acFindMatches(prefix, type) {
|
||||
if (type === "model") {
|
||||
var models = ollie.getAvailableModels(ollie.currentBackend || "")
|
||||
var m = []
|
||||
for (var i = 0; i < models.length; ++i) {
|
||||
if (models[i].toLowerCase().indexOf(prefix.toLowerCase()) >= 0)
|
||||
m.push(models[i])
|
||||
}
|
||||
return m
|
||||
} else if (type === "backend") {
|
||||
var bks = ollie.availableBackends
|
||||
var m = []
|
||||
for (var j = 0; j < bks.length; ++j) {
|
||||
if (bks[j].toLowerCase().indexOf(prefix.toLowerCase()) >= 0)
|
||||
m.push(bks[j])
|
||||
}
|
||||
return m
|
||||
} else {
|
||||
var m = []
|
||||
for (var k = 0; k < root.slashCommands.length; ++k) {
|
||||
if (root.slashCommands[k].name.indexOf(prefix) >= 0)
|
||||
m.push(root.slashCommands[k].name)
|
||||
}
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
||||
function acLCP(arr) {
|
||||
if (arr.length === 0) return ""
|
||||
if (arr.length === 1) return arr[0]
|
||||
var lcp = arr[0]
|
||||
for (var i = 1; i < arr.length; ++i) {
|
||||
while (!arr[i].startsWith(lcp)) {
|
||||
lcp = lcp.substring(0, lcp.length - 1)
|
||||
if (lcp === "") return ""
|
||||
}
|
||||
}
|
||||
return lcp
|
||||
}
|
||||
|
||||
function acEnter(prefix, type) {
|
||||
_acActive = true
|
||||
_acTyped = prefix
|
||||
_acMatches = acFindMatches(prefix, type)
|
||||
_acMatchIndex = 0
|
||||
if (_acMatches.length > 0) {
|
||||
var lcp = acLCP(_acMatches)
|
||||
if (lcp.length > prefix.length) {
|
||||
promptInput.text = _acBase + lcp
|
||||
promptInput.cursorPosition = promptInput.text.length
|
||||
}
|
||||
} else {
|
||||
// No options — transition to done immediately
|
||||
_acActive = false
|
||||
_acBase = ""
|
||||
_acTyped = ""
|
||||
_acMatches = []
|
||||
}
|
||||
}
|
||||
|
||||
function acCycle() {
|
||||
if (!_acActive || _acMatches.length <= 1) return
|
||||
_acMatchIndex = (_acMatchIndex + 1) % _acMatches.length
|
||||
var parts = promptInput.text.substring(promptInput.text.lastIndexOf("/") + 1).split(" ")
|
||||
parts[0] = _acMatches[_acMatchIndex]
|
||||
promptInput.text = _acBase + "/" + parts.join(" ")
|
||||
promptInput.cursorPosition = promptInput.text.length
|
||||
}
|
||||
|
||||
function acCancel() {
|
||||
if (!_acActive) return
|
||||
promptInput.text = _acBase + _acTyped
|
||||
promptInput.cursorPosition = promptInput.text.length
|
||||
_acActive = false
|
||||
_acBase = ""
|
||||
_acTyped = ""
|
||||
_acMatches = []
|
||||
}
|
||||
|
||||
|
||||
// Deferred scroll timer — fires once after model changes settle
|
||||
Timer {
|
||||
id: scrollTimer
|
||||
|
|
@ -850,105 +761,8 @@ ApplicationWindow {
|
|||
property int _lastCursorPos: 0
|
||||
|
||||
onTextChanged: {
|
||||
var oldText = _lastText
|
||||
var newText = text
|
||||
var cursorPos = cursorPosition
|
||||
|
||||
// Check if we just completed a command name and added a space
|
||||
if (_acActive && _acMatches.length > 0) {
|
||||
var afterSlash = newText.substring(newText.lastIndexOf("/") + 1)
|
||||
var parts = afterSlash.split(" ")
|
||||
// If first part matches a known command exactly and there's a second part
|
||||
if (parts.length >= 2) {
|
||||
var cmdName = parts[0].toLowerCase()
|
||||
if (cmdName === "model") {
|
||||
// Switch to model arg completion — update base to include command
|
||||
_acBase = newText.substring(0, newText.lastIndexOf("/") + 1) + parts[0] + " "
|
||||
acEnter(parts.slice(1).join(" "), "model")
|
||||
return
|
||||
} else if (cmdName === "backend") {
|
||||
// Switch to backend arg completion
|
||||
_acBase = newText.substring(0, newText.lastIndexOf("/") + 1) + parts[0] + " "
|
||||
acEnter(parts.slice(1).join(" "), "backend")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!_acActive && newText.indexOf("/") >= 0) {
|
||||
var slashPos = newText.lastIndexOf("/")
|
||||
if (slashPos < cursorPos) {
|
||||
var prefix = newText.substring(slashPos + 1, cursorPos)
|
||||
if (prefix.length > 0 && prefix !== " ") {
|
||||
_acBase = newText.substring(0, slashPos + 1)
|
||||
acEnter(prefix, "cmd")
|
||||
}
|
||||
}
|
||||
} else if (_acActive) {
|
||||
// User is typing inside autocomplete — re-enter with updated prefix
|
||||
var slashPos = newText.lastIndexOf("/")
|
||||
var prefix = newText.substring(slashPos + 1, cursorPos)
|
||||
if (prefix.length > 0 && prefix !== " ") {
|
||||
_acBase = newText.substring(0, slashPos + 1)
|
||||
acEnter(prefix, _acType)
|
||||
} else {
|
||||
acCancel()
|
||||
}
|
||||
} else if (newText.indexOf("/") < 0) {
|
||||
// No slash at all — cancel any lingering state
|
||||
acCancel()
|
||||
}
|
||||
|
||||
_lastText = newText
|
||||
_lastCursorPos = cursorPos
|
||||
}
|
||||
|
||||
onCursorPositionChanged: {
|
||||
if (!_acActive && text.indexOf("/") >= 0) {
|
||||
var slashPos = text.lastIndexOf("/")
|
||||
if (slashPos < cursorPosition) {
|
||||
var prefix = text.substring(slashPos + 1, cursorPosition)
|
||||
if (prefix.length > 0 && prefix !== " ") {
|
||||
_acBase = text.substring(0, slashPos + 1)
|
||||
acEnter(prefix, "cmd")
|
||||
}
|
||||
}
|
||||
} else if (_acActive) {
|
||||
var slashPos = text.lastIndexOf("/")
|
||||
var prefix = text.substring(slashPos + 1, cursorPosition)
|
||||
if (prefix.length > 0 && prefix !== " ") {
|
||||
_acBase = text.substring(0, slashPos + 1)
|
||||
acEnter(prefix, _acType)
|
||||
} else {
|
||||
acCancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: function(event) {
|
||||
if (_acActive) {
|
||||
if (event.key === Qt.Key_Tab) {
|
||||
acCycle()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Escape) {
|
||||
acCancel()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Down) {
|
||||
if (_acMatches.length > 1) {
|
||||
acCycle()
|
||||
event.accepted = true
|
||||
}
|
||||
} else if (event.key === Qt.Key_Up) {
|
||||
if (_acMatches.length > 1) {
|
||||
_acMatchIndex = (_acMatchIndex - 1 + _acMatches.length) % _acMatches.length
|
||||
var parts = text.substring(text.lastIndexOf("/") + 1).split(" ")
|
||||
parts[0] = _acMatches[_acMatchIndex]
|
||||
text = _acBase + "/" + parts.join(" ")
|
||||
cursorPosition = text.length
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
_lastText = text
|
||||
_lastCursorPos = cursorPosition
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: function(event) {
|
||||
|
|
|
|||
Reference in New Issue