Object properties for autocompletion!
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Adsooi 2022-10-20 16:23:12 +02:00
parent 0e41c12e03
commit 3cd4ad6a20
Signed by: Ad5001
GPG key ID: 7251B1AF90B960F9
5 changed files with 104 additions and 23 deletions

View file

@ -214,9 +214,9 @@ Item {
Parses a given \c value as an expression or a number depending on the type of \c propertyName of all \c objType. Parses a given \c value as an expression or a number depending on the type of \c propertyName of all \c objType.
*/ */
function parseValue(value, objType, propertyName) { function parseValue(value, objType, propertyName) {
return { if(Objects.types[objType].properties()[propertyName] == 'number')
'Expression': () => new MathLib.Expression(value), return parseFloat(value)
'number': () => parseFloat(value) else
}[Objects.types[objType].properties()[propertyName]]() return new MathLib.Expression(value)
} }
} }

View file

@ -63,10 +63,17 @@ ListView {
Text to autocomplete. Text to autocomplete.
*/ */
property string baseText: "" property string baseText: ""
/*!
\qmlproperty bool AutocompletionCategory::visbilityCondition
Condition to be met for the category to be visible.
*/
property bool visbilityCondition: true
width: parent.width width: parent.width
visible: model.length > 0 visible: model.length > 0
implicitHeight: contentItem.childrenRect.height implicitHeight: contentItem.childrenRect.height
model: parent.visible ? categoryItems.filter((item) => item.includes(baseText)).map(autocompleteGenerator) : [] model: visbilityCondition ? categoryItems.filter((item) => item.includes(baseText)).map(autocompleteGenerator) : []
header: Column { header: Column {
width: listFiltered.width width: listFiltered.width

View file

@ -200,7 +200,8 @@ Item {
Keys.onPressed: function(event) { Keys.onPressed: function(event) {
// Autocomplete popup events // Autocomplete popup events
//console.log("Pressed key:", event.key, Qt.Key_Return, Qt.Key_Enter, event.text) //console.log(acPopupContent.currentToken.dot, acPopupContent.previousToken.dot, "@", acPopupContent.currentToken.identifier, acPopupContent.previousToken.identifier, acPopupContent.previousToken2.identifier, objectPropertiesList.objectName, JSON.stringify(objectPropertiesList.baseText), objectPropertiesList.model.length, JSON.stringify(objectPropertiesList.categoryItems))
//console.log("Pressed key:", event.key, Qt.Key_Return, Qt.Key_Enter, event.text, acPopupContent.itemCount)
if((event.key == Qt.Key_Enter || event.key == Qt.Key_Return) && acPopupContent.itemCount > 0) { if((event.key == Qt.Key_Enter || event.key == Qt.Key_Return) && acPopupContent.itemCount > 0) {
acPopupContent.autocomplete() acPopupContent.autocomplete()
event.accepted = true event.accepted = true
@ -251,21 +252,40 @@ Item {
Parsing.TokenType.FUNCTION, Parsing.TokenType.FUNCTION,
Parsing.TokenType.CONSTANT Parsing.TokenType.CONSTANT
] ]
property var currentToken: getTokenAt(editor.tokens, editor.cursorPosition) property var currentToken: generateTokenInformation(getTokenAt(editor.tokens, editor.cursorPosition))
visible: currentToken != null && identifierTokenTypes.includes(currentToken.type) property var previousToken: generateTokenInformation(getPreviousToken(currentToken.token))
property var previousToken2: generateTokenInformation(getPreviousToken(previousToken.token))
property var previousToken3: generateTokenInformation(getPreviousToken(previousToken2.token))
visible: currentToken.exists
// Focus handling. // Focus handling.
readonly property var lists: [objectPropertiesList, variablesList, constantsList, functionsList, executableObjectsList, objectsList] readonly property var lists: [objectPropertiesList, variablesList, constantsList, functionsList, executableObjectsList, objectsList]
readonly property int itemCount: objectPropertiesList.model.length + variablesList.model.length, constantsList.model.length + functionsList.model.length + executableObjectsList.model.length + objectsList.model.length readonly property int itemCount: objectPropertiesList.model.length + variablesList.model.length + constantsList.model.length + functionsList.model.length + executableObjectsList.model.length + objectsList.model.length
property int itemSelected: 0 property int itemSelected: 0
/*! /*!
\qmlmethod var ExpressionEditor::autocompleteAt(int idx) \qmlmethod var ExpressionEditor::generateTokenInformation(var token)
Generates basic information about the given token (existence and type) used in autocompletion).
*/
function generateTokenInformation(token) {
let exists = token != null
return {
'token': token,
'exists': exists,
'value': exists ? token.value : null,
'type': exists ? token.type : null,
'startPosition': exists ? token.startPosition : 0,
'dot': exists ? (token.type == Parsing.TokenType.PUNCT && token.value == ".") : false,
'identifier': exists ? identifierTokenTypes.includes(token.type) : false
}
}
/*!
\qmlmethod void ExpressionEditor::autocompleteInfoAt(int idx)
Returns the autocompletion text information at a given position. Returns the autocompletion text information at a given position.
The information contains key 'text' (description text), 'autocomplete' (text to insert) The information contains key 'text' (description text), 'autocomplete' (text to insert)
and 'cursorFinalOffset' (amount to add to the cursor's position after the end of the autocomplete) and 'cursorFinalOffset' (amount to add to the cursor's position after the end of the autocomplete)
*/ */
function autocompleteAt(idx) { function autocompleteInfoAt(idx) {
if(idx >= itemCount) return "" if(idx >= itemCount) return ""
let startIndex = 0 let startIndex = 0
for(let list of lists) { for(let list of lists) {
@ -274,37 +294,67 @@ Item {
startIndex += list.model.length startIndex += list.model.length
} }
} }
/*! /*!
\qmlmethod var ExpressionEditor::autocomplete() \qmlmethod void ExpressionEditor::autocomplete()
Autocompletes with the current selected word. Autocompletes with the current selected word.
*/ */
function autocomplete() { function autocomplete() {
let autotext = autocompleteAt(itemSelected) let autotext = autocompleteInfoAt(itemSelected)
let startPos = currentToken.startPosition let startPos = currentToken.startPosition
console.log("Replacing", currentToken.value, "at", startPos, "with", autotext.autocomplete)
editor.remove(startPos, startPos+currentToken.value.length) editor.remove(startPos, startPos+currentToken.value.length)
editor.insert(startPos, autotext.autocomplete) editor.insert(startPos, autotext.autocomplete)
editor.cursorPosition = startPos+autotext.autocomplete.length+autotext.cursorFinalOffset editor.cursorPosition = startPos+autotext.autocomplete.length+autotext.cursorFinalOffset
} }
/*!
\qmlmethod var ExpressionEditor::getPreviousToken(var token)
Returns the token before this one.
*/
function getPreviousToken(token) {
let newToken = getTokenAt(editor.tokens, token.startPosition)
if(newToken != null && newToken.type == Parsing.TokenType.WHITESPACE)
return getPreviousToken(newToken)
return newToken
}
AutocompletionCategory { AutocompletionCategory {
id: objectPropertiesList id: objectPropertiesList
category: qsTr("Object Properties") category: qsTr("Object Properties")
visbilityCondition: isEnteringProperty
itemStartIndex: 0 itemStartIndex: 0
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: [] property bool isEnteringProperty: (
property var objectName: null // Current token is dot.
autocompleteGenerator: (item) => {return { (parent.currentToken.dot && parent.previousToken.identifier && !parent.previousToken2.dot) ||
'text': item, 'annotation': Objects.currentObjectsByName[objectName].constructor.properties()[item], // Current token is property identifier
'autocomplete': item + " ", 'cursorFinalOffset': 0 (parent.currentToken.identifier && parent.previousToken.dot && parent.previousToken2.identifier && !parent.previousToken3.dot))
}} property string objectName: isEnteringProperty ?
baseText: parent.visible ? parent.currentToken.value : "" (parent.currentToken.dot ? parent.previousToken.value : parent.previousToken2.value)
: ""
property var objectProperties: isEnteringProperty ?
Objects.currentObjectsByName[objectName].constructor.properties() :
{}
categoryItems: Object.keys(objectProperties)
autocompleteGenerator: (item) => {
let propType = objectProperties[item]
return {
'text': item, 'annotation': propType == null ? '' : propType.toString(),
'autocomplete': parent.currentToken.dot ? `.${item} ` : `${item} `,
'cursorFinalOffset': 0
}
}
baseText: parent.visible && !parent.currentToken.dot ? parent.currentToken.value : ""
} }
AutocompletionCategory { AutocompletionCategory {
id: variablesList id: variablesList
category: qsTr("Variables") category: qsTr("Variables")
visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot
itemStartIndex: objectPropertiesList.model.length itemStartIndex: objectPropertiesList.model.length
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: control.variables categoryItems: control.variables
@ -319,6 +369,7 @@ Item {
id: constantsList id: constantsList
category: qsTr("Constants") category: qsTr("Constants")
visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot
itemStartIndex: variablesList.itemStartIndex + variablesList.model.length itemStartIndex: variablesList.itemStartIndex + variablesList.model.length
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: Parsing.CONSTANTS_LIST categoryItems: Parsing.CONSTANTS_LIST
@ -333,6 +384,7 @@ Item {
id: functionsList id: functionsList
category: qsTr("Functions") category: qsTr("Functions")
visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot
itemStartIndex: constantsList.itemStartIndex + constantsList.model.length itemStartIndex: constantsList.itemStartIndex + constantsList.model.length
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: Parsing.FUNCTIONS_LIST categoryItems: Parsing.FUNCTIONS_LIST
@ -347,11 +399,12 @@ Item {
id: executableObjectsList id: executableObjectsList
category: qsTr("Executable Objects") category: qsTr("Executable Objects")
visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot
itemStartIndex: functionsList.itemStartIndex + functionsList.model.length itemStartIndex: functionsList.itemStartIndex + functionsList.model.length
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: Objects.getObjectsName("ExecutableObject").filter(obj => obj != self) categoryItems: Objects.getObjectsName("ExecutableObject").filter(obj => obj != self)
autocompleteGenerator: (item) => {return { autocompleteGenerator: (item) => {return {
'text': item, 'annotation': `${Objects.currentObjectsByName[item].constructor.displayType()}`, 'text': item, 'annotation': Objects.currentObjectsByName[item] == null ? '' : Objects.currentObjectsByName[item].constructor.displayType(),
'autocomplete': item+'()', 'cursorFinalOffset': -1 'autocomplete': item+'()', 'cursorFinalOffset': -1
}} }}
baseText: parent.visible ? parent.currentToken.value : "" baseText: parent.visible ? parent.currentToken.value : ""
@ -361,6 +414,7 @@ Item {
id: objectsList id: objectsList
category: qsTr("Objects") category: qsTr("Objects")
visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot
itemStartIndex: executableObjectsList.itemStartIndex + executableObjectsList.model.length itemStartIndex: executableObjectsList.itemStartIndex + executableObjectsList.model.length
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: Object.keys(Objects.currentObjectsByName).filter(obj => obj != self) categoryItems: Object.keys(Objects.currentObjectsByName).filter(obj => obj != self)

View file

@ -21,6 +21,10 @@ class Expression {
this.type = 'Expression' this.type = 'Expression'
this.variables = variables this.variables = variables
} }
toString() {
return this.variables.length == 0 ? 'Number' : `Expression(${this.variables.join(', ')})`
}
} }
class Enum { class Enum {
@ -29,6 +33,10 @@ class Enum {
this.values = values this.values = values
this.translatedValues = values.map(x => qsTr(x)) this.translatedValues = values.map(x => qsTr(x))
} }
toString() {
return this.type
}
} }
class ObjectType { class ObjectType {
@ -36,6 +44,10 @@ class ObjectType {
this.type = 'ObjectType' this.type = 'ObjectType'
this.objType = objType this.objType = objType
} }
toString() {
return this.objType
}
} }
class List { class List {
@ -47,6 +59,10 @@ class List {
this.label = label this.label = label
this.forbidAdding = forbidAdding this.forbidAdding = forbidAdding
} }
toString() {
return this.objType
}
} }
class Dictionary { class Dictionary {
@ -61,6 +77,10 @@ class Dictionary {
this.postKeyLabel = postKeyLabel this.postKeyLabel = postKeyLabel
this.forbidAdding = forbidAdding this.forbidAdding = forbidAdding
} }
toString() {
return 'Dictionary'
}
} }
// Common parameters for Enums // Common parameters for Enums

View file

@ -24,7 +24,7 @@ const WHITESPACES = " \t\n\r"
const STRING_LIMITORS = '"\'`'; const STRING_LIMITORS = '"\'`';
const OPERATORS = "+-*/^%?:=!><"; const OPERATORS = "+-*/^%?:=!><";
const PUNCTUTATION = "()[]{},."; const PUNCTUTATION = "()[]{},.";
const NUMBER_CHARS = "0123456789." const NUMBER_CHARS = "0123456789"
const IDENTIFIER_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789_₀₁₂₃₄₅₆₇₈₉αβγδεζηθκλμξρςστφχψωₐₑₒₓₔₕₖₗₘₙₚₛₜ" const IDENTIFIER_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789_₀₁₂₃₄₅₆₇₈₉αβγδεζηθκλμξρςστφχψωₐₑₒₓₔₕₖₗₘₙₚₛₜ"
var TokenType = { var TokenType = {
@ -92,7 +92,7 @@ class ExpressionTokenizer {
readNumber() { readNumber() {
let included = ""; let included = "";
let hasDot = false; let hasDot = false;
while(!this.input.atEnd() && NUMBER_CHARS.includes(this.input.peek())) { while(!this.input.atEnd() && (NUMBER_CHARS.includes(this.input.peek()) || this.input.peek() == '.')) {
if(this.input.peek() == ".") { if(this.input.peek() == ".") {
if(hasDot) this.input.raise("Unexpected '.'. Expected digit") if(hasDot) this.input.raise("Unexpected '.'. Expected digit")
hasDot = true; hasDot = true;