Make autocompletion work not just at end of line
This commit is contained in:
parent
1b919ec7be
commit
43b14f3129
|
@ -32,8 +32,22 @@ TextArea {
|
||||||
|
|
||||||
signal customImagePaste()
|
signal customImagePaste()
|
||||||
|
|
||||||
function reset() { clear(); text = Qt.binding(() => defaultText || "") }
|
function reset() {
|
||||||
function insertAtCursor(text) { insert(cursorPosition, text) }
|
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 || ""
|
text: defaultText || ""
|
||||||
|
|
|
@ -23,12 +23,12 @@ HTextArea {
|
||||||
ModelStore.get("accounts").find(writingUserId)
|
ModelStore.get("accounts").find(writingUserId)
|
||||||
|
|
||||||
readonly property int cursorY:
|
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:
|
readonly property int cursorX:
|
||||||
cursorPosition - lines.slice(0, cursorY).join("").length - cursorY
|
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 lineText: lines[cursorY] || ""
|
||||||
|
|
||||||
readonly property string lineTextUntilCursor:
|
readonly property string lineTextUntilCursor:
|
||||||
|
@ -209,14 +209,17 @@ HTextArea {
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onBacktabPressed: ev => {
|
Keys.onBacktabPressed: ev => {
|
||||||
|
// if previous char isn't a space/tab/newline
|
||||||
|
if (text.slice(cursorPosition - 1, cursorPosition).trim()) {
|
||||||
ev.accepted = true
|
ev.accepted = true
|
||||||
autoCompletePrevious()
|
autoCompletePrevious()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Keys.onTabPressed: ev => {
|
Keys.onTabPressed: ev => {
|
||||||
ev.accepted = true
|
ev.accepted = true
|
||||||
|
|
||||||
if (text.slice(-1).trim()) { // previous char isn't a space/tab
|
if (text.slice(cursorPosition - 1, cursorPosition).trim()) {
|
||||||
autoCompleteNext()
|
autoCompleteNext()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,28 +6,31 @@ import "../../.."
|
||||||
import "../../../Base"
|
import "../../../Base"
|
||||||
import "../../../Base/HTile"
|
import "../../../Base/HTile"
|
||||||
|
|
||||||
// FIXME: a b -> a @p b → @p doesn't trigger completion
|
|
||||||
HListView {
|
HListView {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property HTextArea textArea
|
property HTextArea textArea
|
||||||
property bool open: false
|
property bool open: false
|
||||||
|
|
||||||
property string originalText: ""
|
property var originalWord: null
|
||||||
property bool autoOpenCompleted: false
|
property bool autoOpenCompleted: false
|
||||||
property var usersCompleted: ({}) // {displayName: userId}
|
property var usersCompleted: ({}) // {displayName: userId}
|
||||||
|
|
||||||
readonly property bool autoOpen:
|
readonly property bool autoOpen: {
|
||||||
autoOpenCompleted || textArea.text.match(/.*(^|\W)@[^\s]+$/)
|
if (autoOpenCompleted) return true
|
||||||
|
const current = textArea.getWordBehindCursor()
|
||||||
|
return current ? /^@.+/.test(current.word) : false
|
||||||
|
}
|
||||||
|
|
||||||
readonly property string wordToComplete:
|
readonly property var wordToComplete:
|
||||||
open ?
|
open ? originalWord || textArea.getWordBehindCursor() : null
|
||||||
(originalText || textArea.text).split(/\s/).slice(-1)[0].replace(
|
|
||||||
autoOpen ? /^@/ : "", "",
|
readonly property string modelFilter:
|
||||||
) :
|
autoOpen && wordToComplete ? wordToComplete.word.replace(/^@/, "") :
|
||||||
|
open && wordToComplete ? wordToComplete.word :
|
||||||
""
|
""
|
||||||
|
|
||||||
function getLastWordStart() {
|
function getCurrentWordStart() {
|
||||||
const lastWordMatch = /(?:^|\s)[^\s]+$/.exec(textArea.text)
|
const lastWordMatch = /(?:^|\s)[^\s]+$/.exec(textArea.text)
|
||||||
if (! lastWordMatch) return textArea.length
|
if (! lastWordMatch) return textArea.length
|
||||||
|
|
||||||
|
@ -37,10 +40,13 @@ HListView {
|
||||||
return lastWordMatch.index
|
return lastWordMatch.index
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceLastWord(withText) {
|
function replaceCurrentWord(withText) {
|
||||||
textArea.remove(getLastWordStart(), textArea.length)
|
const current = textArea.getWordBehindCursor()
|
||||||
|
if (current) {
|
||||||
|
textArea.remove(current.start, current.end + 1)
|
||||||
textArea.insertAtCursor(withText)
|
textArea.insertAtCursor(withText)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function previous() {
|
function previous() {
|
||||||
if (open) {
|
if (open) {
|
||||||
|
@ -49,7 +55,7 @@ HListView {
|
||||||
}
|
}
|
||||||
|
|
||||||
open = true
|
open = true
|
||||||
const args = [model.modelId, wordToComplete]
|
const args = [model.modelId, modelFilter]
|
||||||
py.callCoro("set_string_filter", args, decrementCurrentIndex)
|
py.callCoro("set_string_filter", args, decrementCurrentIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +66,7 @@ HListView {
|
||||||
}
|
}
|
||||||
|
|
||||||
open = true
|
open = true
|
||||||
const args = [model.modelId, wordToComplete]
|
const args = [model.modelId, modelFilter]
|
||||||
py.callCoro("set_string_filter", args, incrementCurrentIndex)
|
py.callCoro("set_string_filter", args, incrementCurrentIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,8 +81,7 @@ HListView {
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancel() {
|
function cancel() {
|
||||||
if (originalText)
|
if (originalWord) replaceCurrentWord(originalWord.word)
|
||||||
replaceLastWord(originalText.split(/\s/).splice(-1)[0])
|
|
||||||
|
|
||||||
currentIndex = -1
|
currentIndex = -1
|
||||||
open = false
|
open = false
|
||||||
|
@ -97,26 +102,25 @@ HListView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onCountChanged: if (! count && open) open = false
|
|
||||||
onAutoOpenChanged: open = autoOpen
|
onAutoOpenChanged: open = autoOpen
|
||||||
onOpenChanged: if (! open) {
|
onOpenChanged: if (! open) {
|
||||||
originalText = ""
|
originalWord = null
|
||||||
currentIndex = -1
|
currentIndex = -1
|
||||||
autoOpenCompleted = false
|
autoOpenCompleted = false
|
||||||
py.callCoro("set_string_filter", [model.modelId, ""])
|
py.callCoro("set_string_filter", [model.modelId, ""])
|
||||||
}
|
}
|
||||||
|
|
||||||
onWordToCompleteChanged: {
|
onModelFilterChanged: {
|
||||||
if (! open) return
|
if (! open) return
|
||||||
py.callCoro("set_string_filter", [model.modelId, wordToComplete])
|
py.callCoro("set_string_filter", [model.modelId, modelFilter])
|
||||||
}
|
}
|
||||||
|
|
||||||
onCurrentIndexChanged: {
|
onCurrentIndexChanged: {
|
||||||
if (currentIndex === -1) return
|
if (currentIndex === -1) return
|
||||||
if (! originalText) originalText = textArea.text
|
if (! originalWord) originalWord = textArea.getWordBehindCursor()
|
||||||
if (autoOpen) autoOpenCompleted = true
|
if (autoOpen) autoOpenCompleted = true
|
||||||
|
|
||||||
replaceLastWord(model.get(currentIndex).display_name)
|
replaceCurrentWord(model.get(currentIndex).display_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity { HNumberAnimation {} }
|
Behavior on opacity { HNumberAnimation {} }
|
||||||
|
@ -126,8 +130,17 @@ HListView {
|
||||||
target: root.textArea
|
target: root.textArea
|
||||||
|
|
||||||
function onCursorPositionChanged() {
|
function onCursorPositionChanged() {
|
||||||
if (root.open && root.textArea.cursorPosition < getLastWordStart())
|
if (! root.open) return
|
||||||
root.accept()
|
|
||||||
|
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() {
|
function onTextChanged() {
|
||||||
|
|
|
@ -497,4 +497,18 @@ QtObject {
|
||||||
|
|
||||||
return sum
|
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}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user