Make autocompletion work not just at end of line

This commit is contained in:
miruka 2020-08-21 04:44:55 -04:00
parent 1b919ec7be
commit 43b14f3129
4 changed files with 76 additions and 32 deletions

View File

@ -32,8 +32,22 @@ TextArea {
signal customImagePaste()
function reset() { clear(); text = Qt.binding(() => defaultText || "") }
function insertAtCursor(text) { insert(cursorPosition, text) }
function reset() {
clear()
text = Qt.binding(() => defaultText || "")
}
function insertAtCursor(text) {
insert(cursorPosition, text)
}
function getWordAt(position) {
return utils.getWordAtPosition(text, position)
}
function getWordBehindCursor() {
return cursorPosition === 0 ? null : getWordAt(cursorPosition - 1)
}
text: defaultText || ""

View File

@ -23,12 +23,12 @@ HTextArea {
ModelStore.get("accounts").find(writingUserId)
readonly property int cursorY:
area.text.substring(0, cursorPosition).split("\n").length - 1
text.substring(0, cursorPosition).split("\n").length - 1
readonly property int cursorX:
cursorPosition - lines.slice(0, cursorY).join("").length - cursorY
readonly property var lines: area.text.split("\n")
readonly property var lines: text.split("\n")
readonly property string lineText: lines[cursorY] || ""
readonly property string lineTextUntilCursor:
@ -209,14 +209,17 @@ HTextArea {
}
Keys.onBacktabPressed: ev => {
ev.accepted = true
autoCompletePrevious()
// if previous char isn't a space/tab/newline
if (text.slice(cursorPosition - 1, cursorPosition).trim()) {
ev.accepted = true
autoCompletePrevious()
}
}
Keys.onTabPressed: ev => {
ev.accepted = true
if (text.slice(-1).trim()) { // previous char isn't a space/tab
if (text.slice(cursorPosition - 1, cursorPosition).trim()) {
autoCompleteNext()
return
}

View File

@ -6,28 +6,31 @@ import "../../.."
import "../../../Base"
import "../../../Base/HTile"
// FIXME: a b -> a @p b @p doesn't trigger completion
HListView {
id: root
property HTextArea textArea
property bool open: false
property string originalText: ""
property var originalWord: null
property bool autoOpenCompleted: false
property var usersCompleted: ({}) // {displayName: userId}
readonly property bool autoOpen:
autoOpenCompleted || textArea.text.match(/.*(^|\W)@[^\s]+$/)
readonly property bool autoOpen: {
if (autoOpenCompleted) return true
const current = textArea.getWordBehindCursor()
return current ? /^@.+/.test(current.word) : false
}
readonly property string wordToComplete:
open ?
(originalText || textArea.text).split(/\s/).slice(-1)[0].replace(
autoOpen ? /^@/ : "", "",
) :
readonly property var wordToComplete:
open ? originalWord || textArea.getWordBehindCursor() : null
readonly property string modelFilter:
autoOpen && wordToComplete ? wordToComplete.word.replace(/^@/, "") :
open && wordToComplete ? wordToComplete.word :
""
function getLastWordStart() {
function getCurrentWordStart() {
const lastWordMatch = /(?:^|\s)[^\s]+$/.exec(textArea.text)
if (! lastWordMatch) return textArea.length
@ -37,9 +40,12 @@ HListView {
return lastWordMatch.index
}
function replaceLastWord(withText) {
textArea.remove(getLastWordStart(), textArea.length)
textArea.insertAtCursor(withText)
function replaceCurrentWord(withText) {
const current = textArea.getWordBehindCursor()
if (current) {
textArea.remove(current.start, current.end + 1)
textArea.insertAtCursor(withText)
}
}
function previous() {
@ -49,7 +55,7 @@ HListView {
}
open = true
const args = [model.modelId, wordToComplete]
const args = [model.modelId, modelFilter]
py.callCoro("set_string_filter", args, decrementCurrentIndex)
}
@ -60,7 +66,7 @@ HListView {
}
open = true
const args = [model.modelId, wordToComplete]
const args = [model.modelId, modelFilter]
py.callCoro("set_string_filter", args, incrementCurrentIndex)
}
@ -75,8 +81,7 @@ HListView {
}
function cancel() {
if (originalText)
replaceLastWord(originalText.split(/\s/).splice(-1)[0])
if (originalWord) replaceCurrentWord(originalWord.word)
currentIndex = -1
open = false
@ -97,26 +102,25 @@ HListView {
}
}
onCountChanged: if (! count && open) open = false
onAutoOpenChanged: open = autoOpen
onOpenChanged: if (! open) {
originalText = ""
originalWord = null
currentIndex = -1
autoOpenCompleted = false
py.callCoro("set_string_filter", [model.modelId, ""])
}
onWordToCompleteChanged: {
onModelFilterChanged: {
if (! open) return
py.callCoro("set_string_filter", [model.modelId, wordToComplete])
py.callCoro("set_string_filter", [model.modelId, modelFilter])
}
onCurrentIndexChanged: {
if (currentIndex === -1) return
if (! originalText) originalText = textArea.text
if (! originalWord) originalWord = textArea.getWordBehindCursor()
if (autoOpen) autoOpenCompleted = true
replaceLastWord(model.get(currentIndex).display_name)
replaceCurrentWord(model.get(currentIndex).display_name)
}
Behavior on opacity { HNumberAnimation {} }
@ -126,8 +130,17 @@ HListView {
target: root.textArea
function onCursorPositionChanged() {
if (root.open && root.textArea.cursorPosition < getLastWordStart())
root.accept()
if (! root.open) return
const pos = root.textArea.cursorPosition
const start = root.wordToComplete.start
const end =
currentIndex === -1 ?
root.wordToComplete.end + 1 :
root.wordToComplete.start +
model.get(currentIndex).display_name.length
if (pos < start || pos > end) root.accept()
}
function onTextChanged() {

View File

@ -497,4 +497,18 @@ QtObject {
return sum
}
function getWordAtPosition(text, position) {
// getWordAtPosition("foo bar", 1) {word: "foo", start: 0, end: 2}
let seen = -1
for (var word of text.split(/(\s+)/)) {
var start = seen + 1
seen += word.length
if (seen >= position) return {word, start, end: seen}
}
return {word, start, end: seen}
}
}