From 77ae54fa18f546f32949ef299917eb320a2d02f7 Mon Sep 17 00:00:00 2001 From: Ad5001 Date: Thu, 20 Oct 2022 00:37:02 +0200 Subject: [PATCH] More autocompletion! --- .../Setting/AutocompletionCategory.qml | 101 ++++++++++++++++++ .../Setting/ExpressionEditor.qml | 87 +++++++-------- .../eu/ad5001/LogarithmPlotter/Setting/qmldir | 1 + .../ad5001/LogarithmPlotter/js/math/common.js | 1 + .../ad5001/LogarithmPlotter/js/math/latex.js | 6 +- .../eu/ad5001/LogarithmPlotter/js/objects.js | 9 +- .../LogarithmPlotter/js/parsing/parsing.js | 1 + 7 files changed, 151 insertions(+), 55 deletions(-) create mode 100644 LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/AutocompletionCategory.qml diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/AutocompletionCategory.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/AutocompletionCategory.qml new file mode 100644 index 0000000..77805fb --- /dev/null +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/AutocompletionCategory.qml @@ -0,0 +1,101 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2022 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +/*! + \qmltype AutocompletionCategory + \inqmlmodule eu.ad5001.LogarithmPlotter.Setting + \brief ListView representing a category of autocompletion. + + \sa ExpressionEditor +*/ +ListView { + id: listFiltered + /*! + \qmlproperty int AutocompletionCategory::itemStartIndex + Start index of the first element in this list compared to the global autocompletion index. + */ + property int itemStartIndex: 0 + + /*! + \qmlproperty string AutocompletionCategory::category + Name of the category. + */ + property string category: "" + + /*! + \qmlproperty var AutocompletionCategory::categoryItems + List of items in this category. To be filtered by the autocomplete to filters. + */ + property var categoryItems: [] + + /*! + \qmlproperty var AutocompletionCategory::autocompleteGenerator + Javascript function taking the name of the item to create an autocompletion item (dictionary with + a 'text', 'autocomplete', and 'cursorFinalOffset' keys. + */ + property var autocompleteGenerator: (item) => {return {'text': item, 'autocomplete': item, 'cursorFinalOffset': 0}} + + /*! + \qmlproperty string AutocompletionCategory::baseText + Text to autocomplete. + */ + property string baseText: "" + width: parent.width + visible: model.length > 0 + implicitHeight: contentItem.childrenRect.height + headerItem.height + model: parent.visible ? categoryItems.filter((item) => item.includes(baseText)).map(autocompleteGenerator) : [] + + header: Column { + width: listFiltered.width + spacing: 2 + topPadding: 5 + bottomPadding: 5 + + Text { + leftPadding: 5 + text: listFiltered.category + } + + Rectangle { + height: 1 + color: 'black' + width: parent.width + } + } + + delegate: Rectangle { + property bool selected: index + listFiltered.itemStartIndex == acPopupContent.itemSelected + + width: autocompleteText.width + height: autocompleteText.height + color: selected ? sysPalette.highlight : 'transparent' + + Text { + id: autocompleteText + topPadding: 2 + bottomPadding: 2 + leftPadding: 15 + text: listFiltered.model[index].text + width: listFiltered.width + color: parent.selected ? sysPalette.highlightedText : sysPalette.windowText + } + } +} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ExpressionEditor.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ExpressionEditor.qml index 6ab5f49..2450a1d 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ExpressionEditor.qml +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ExpressionEditor.qml @@ -22,6 +22,7 @@ import QtQuick.Dialogs 1.3 as D import eu.ad5001.LogarithmPlotter.Popup 1.0 as P import "../js/mathlib.js" as MathLib import "../js/utils.js" as Utils +import "../js/objects.js" as Objects import "../js/parsing/parsing.js" as Parsing @@ -30,7 +31,7 @@ import "../js/parsing/parsing.js" as Parsing \inqmlmodule eu.ad5001.LogarithmPlotter.Setting \brief Setting to edit strings and numbers. - \sa EditorDialog, Settings, Icon + \sa EditorDialog, AutocompletionCategory */ Item { id: control @@ -177,12 +178,18 @@ Item { } Keys.onUpPressed: function(event) { - acPopupContent.itemSelected = Math.max(0, acPopupContent.itemSelected-1) + if(acPopupContent.itemSelected == 0) + acPopupContent.itemSelected = acPopupContent.itemCount-1 + else + acPopupContent.itemSelected = acPopupContent.itemSelected-1 event.accepted = true } Keys.onDownPressed: function(event) { - acPopupContent.itemSelected = Math.max(0,Math.min(acPopupContent.itemCount-1, acPopupContent.itemSelected+1)) + if(acPopupContent.itemSelected == Math.min(acPopupContent.itemCount-1)) + acPopupContent.itemSelected = 0 + else + acPopupContent.itemSelected = acPopupContent.itemSelected+1 event.accepted = true } @@ -243,8 +250,8 @@ Item { visible: currentToken != null && identifierTokenTypes.includes(currentToken.type) // Focus handling. - readonly property var lists: [functionsList] - readonly property int itemCount: functionsList.model.length + readonly property var lists: [constantsList, functionsList, executableObjectList] + readonly property int itemCount: constantsList.model.length + functionsList.model.length + executableObjectList.model.length property int itemSelected: 0 /*! @@ -275,52 +282,34 @@ Item { editor.cursorPosition = startPos+autotext.autocomplete.length+autotext.cursorFinalOffset } - ListView { - id: functionsList - //anchors.fill: parent - property int itemStartIndex: 0 - width: parent.width - visible: model.length > 0 - implicitHeight: contentItem.childrenRect.height + headerItem.height - model: parent.visible ? - Parsing.FUNCTIONS_LIST.filter((name) => name.includes(acPopupContent.currentToken.value)) - .map((name) => {return {'text': name, 'autocomplete': name+'()', 'cursorFinalOffset': -1}}) : [] + AutocompletionCategory { + id: constantsList - header: Column { - width: functionsList.width - spacing: 2 - topPadding: 5 - bottomPadding: 5 - - Text { - leftPadding: 5 - text: qsTr("Functions") - } - - Rectangle { - height: 1 - color: 'black' - width: parent.width - } - } + itemStartIndex: 0 + category: qsTr("Constants") + categoryItems: Parsing.CONSTANTS_LIST + autocompleteGenerator: (item) => {return {'text': item, 'autocomplete': item + " ", 'cursorFinalOffset': 0}} + baseText: parent.currentToken.value + } - delegate: Rectangle { - property bool selected: index + functionsList.itemStartIndex == acPopupContent.itemSelected - - width: funcText.width - height: funcText.height - color: selected ? sysPalette.highlight : 'transparent' - - Text { - id: funcText - topPadding: 2 - bottomPadding: 2 - leftPadding: 15 - text: functionsList.model[index].text - width: functionsList.width - color: parent.selected ? sysPalette.highlightedText : sysPalette.windowText - } - } + AutocompletionCategory { + id: functionsList + + itemStartIndex: constantsList.model.length + category: qsTr("Functions") + categoryItems: Parsing.FUNCTIONS_LIST + autocompleteGenerator: (item) => {return {'text': item, 'autocomplete': item+'()', 'cursorFinalOffset': -1}} + baseText: parent.currentToken.value + } + + AutocompletionCategory { + id: executableObjectList + + itemStartIndex: constantsList.model.length + functionsList.model.length + category: qsTr("Executable Objects") + categoryItems: Objects.getObjectsName("ExecutableObject") + autocompleteGenerator: (item) => {return {'text': item, 'autocomplete': item+'()', 'cursorFinalOffset': -1}} + baseText: parent.currentToken.value } } } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/qmldir b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/qmldir index d6e55e5..8106e26 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/qmldir +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/qmldir @@ -5,3 +5,4 @@ Icon 1.0 Icon.qml ListSetting 1.0 ListSetting.qml TextSetting 1.0 TextSetting.qml ExpressionEditor 1.0 ExpressionEditor.qml +AutocompletionCategory 1.0 AutocompletionCategory.qml diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/common.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/common.js index 80f9402..156394b 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/common.js +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/common.js @@ -28,6 +28,7 @@ var evalVariables = { // Variables not provided by expr-eval.js, needs to be pro "pi": Math.PI, "π": Math.PI, "inf": Infinity, + "infinity": Infinity, "Infinity": Infinity, "∞": Infinity, "e": Math.E, diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/latex.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/latex.js index 06d0bfa..9ebdf6d 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/latex.js +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/latex.js @@ -148,9 +148,11 @@ function expression(tokens) { switch(type) { case ExprEval.INUMBER: - if (typeof item.value === 'number' && item.value < 0) { + if(item.value == Infinity) { + nstack.push("\\infty") + } else if(typeof item.value === 'number' && item.value < 0) { nstack.push(par(item.value)); - } else if (Array.isArray(item.value)) { + } else if(Array.isArray(item.value)) { nstack.push('[' + item.value.map(ExprEval.escapeValue).join(', ') + ']'); } else { nstack.push(ExprEval.escapeValue(item.value)); diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objects.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objects.js index 9f3e972..5fc5d6a 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objects.js +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objects.js @@ -58,11 +58,12 @@ function getObjectsName(objType) { * @return {array} List of names of the objects. */ if(objType == "ExecutableObject") { - var types = getExecutableTypes() - var elementNames = [''] - types.forEach(function(elemType){ + // NOTE: QMLJS does not support flatMap. + // return getExecutableTypes().flatMap(elemType => currentObjects[elemType].map(obj => obj.name)) + let types = getExecutableTypes() + let elementNames = [''] + for(let elemType of types) elementNames = elementNames.concat(currentObjects[elemType].map(obj => obj.name)) - }) return elementNames } if(currentObjects[objType] == undefined) return [] diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/parsing.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/parsing.js index 63e52dc..6dfe932 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/parsing.js +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/parsing.js @@ -28,3 +28,4 @@ var Token = TK.Token var Tokenizer = TK.ExpressionTokenizer var FUNCTIONS_LIST = Reference.FUNCTIONS_LIST +var CONSTANTS_LIST = Reference.CONSTANTS_LIST