Object properties for autocompletion!
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
0e41c12e03
commit
3cd4ad6a20
5 changed files with 104 additions and 23 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue