Skip to content

Commit

Permalink
Implement IntelliSense
Browse files Browse the repository at this point in the history
  • Loading branch information
vicr123 committed Jun 6, 2024
1 parent d87ef8b commit cc5bc41
Show file tree
Hide file tree
Showing 8 changed files with 585 additions and 111 deletions.
1 change: 1 addition & 0 deletions application/calculator/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ cntp_add_qml_module(calc thecalculator
SOURCES
calculatorcontroller.h calculatorcontroller.cpp
historymodel.h historymodel.cpp
functiondatabase.h functiondatabase.cpp
)

target_link_libraries(thecalculator-qmlmodule-calc PUBLIC libtcalc)
124 changes: 88 additions & 36 deletions application/calculator/Calculator.qml
Original file line number Diff line number Diff line change
Expand Up @@ -179,52 +179,104 @@ Item {
color: Contemporary.line
}

Layer {
Item {
id: intellisenseLayerContainer
Layout.fillWidth: true
Layout.preferredHeight: controller.intellisenseAvailable ? intellisenseLayout.childrenRect.height + 20 : 0
Layout.preferredHeight: targetHeight

property int targetHeight: 0
opacity: 0

states: [
State {
name: "opened"
when: controller.intellisenseAvailable
PropertyChanges {
target: intellisenseLayerContainer
targetHeight: intellisenseLayout.childrenRect.height + 20
opacity: 1
}
},
State {
name: "closed"
when: !controller.intellisenseAvailable
PropertyChanges {
target: intellisenseLayerContainer
targetHeight: 0
opacity: 0
}
}
]
state: "closed"

clip: true
Behavior on targetHeight {
NumberAnimation {
duration: 150
easing.type: Easing.OutCubic
}
}

GridLayout {
id: intellisenseLayout
anchors.fill: parent
anchors.margins: 10
Behavior on opacity {
NumberAnimation {
duration: 150
easing.type: Easing.OutCubic
}
}

columns: 4
Layer {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
height: intellisenseLayout.childrenRect.height + 20

Label {
Layout.fillWidth: true
text: controller.intellisenseFunction
}
clip: true

Button {
id: intellisensePreviousButton
icon.name: "go-previous"
implicitWidth: intellisensePreviousButton.height
flat: true
Layout.rowSpan: 2
}
GridLayout {
id: intellisenseLayout
anchors.fill: parent
anchors.margins: 10

Label {
text: "1/3"
Layout.rowSpan: 2
}
columns: 4

Button {
id: intellisenseNextButton
icon.name: "go-next"
implicitWidth: intellisenseNextButton.height
flat: true
Layout.rowSpan: 2
}
Label {
Layout.fillWidth: true
text: controller.intellisenseFunction
}

Label {
text: controller.intellisenseDescription
}
Button {
id: intellisensePreviousButton
icon.name: "go-previous"
implicitWidth: intellisensePreviousButton.height
flat: true
Layout.rowSpan: 2
enabled: controller.intellisenseCurrentOverload !== 0
onClicked: controller.intellisensePreviousOverload()
}

Label {
text: controller.intellisenseArguments
Layout.columnSpan: 4
Label {
text: `${controller.intellisenseCurrentOverload + 1}/${controller.intellisenseTotalOverloads}`
Layout.rowSpan: 2
}

Button {
id: intellisenseNextButton
icon.name: "go-next"
implicitWidth: intellisenseNextButton.height
flat: true
Layout.rowSpan: 2
enabled: controller.intellisenseCurrentOverload !== controller.intellisenseTotalOverloads - 1
onClicked: controller.intellisenseNextOverload()
}

Label {
text: controller.intellisenseDescription
}

Label {
text: controller.intellisenseArguments
Layout.columnSpan: 4
textFormat: Text.MarkdownText
}
}
}
}
Expand Down
154 changes: 149 additions & 5 deletions application/calculator/calculatorcontroller.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#include "calculatorcontroller.h"

#include <QClipboard>
#include <QRegularExpression>
#include <QStack>
#include <libcontemporary_global.h>
#include <ranges/trange.h>

#include "functiondatabase.h"
#include "historymodel.h"
#include <libtcalc/tc_evaluator.h>
#include <libtcalc/tc_lexer.h>
Expand All @@ -19,6 +23,13 @@ struct CalculatorControllerPrivate {

int errorStartLocation = 0;
int errorEndLocation = 0;

QString intellisenseFunction;
FunctionDatabase::Function intellisenseFunctionData;
int intellisenseCurrentOverload = 0;
int intellisenseCurrentArgument = 0;
FunctionDatabase functionDatabase;
bool intellisenseAutoChangeOverload = true;
};

CalculatorController::CalculatorController(QObject* parent) :
Expand Down Expand Up @@ -101,23 +112,61 @@ QString CalculatorController::instantResult() {
}

bool CalculatorController::intellisenseAvailable() {
return false;
return !d->intellisenseFunction.isEmpty();
}

QString CalculatorController::intellisenseFunction() {
return "pow(base, exponent)";
if (d->intellisenseFunctionData.overloads.count() <= d->intellisenseCurrentOverload) return {};
auto overload = d->intellisenseFunctionData.overloads.at(d->intellisenseCurrentOverload);

return QStringLiteral("%1(%2)").arg(d->intellisenseFunctionData.name, tRange(overload.arguments).map<QString>([](FunctionDatabase::Function::Overload::Argument argument) {
return argument.name;
}).toList()
.join(QLocale().decimalPoint() == "," ? "; " : ", "));
}

QString CalculatorController::intellisenseDescription() {
return "Describe the pow function";
if (d->intellisenseFunctionData.overloads.count() <= d->intellisenseCurrentOverload) return {};
auto overload = d->intellisenseFunctionData.overloads.at(d->intellisenseCurrentOverload);

return overload.description;
}

QString CalculatorController::intellisenseArguments() {
QStringList args;
args.append("base: the base of the exponent");
if (d->intellisenseFunctionData.overloads.count() <= d->intellisenseCurrentOverload) return {};
auto overload = d->intellisenseFunctionData.overloads.at(d->intellisenseCurrentOverload);
auto args = tRange(overload.arguments).map<QString>([this](FunctionDatabase::Function::Overload::Argument argument, int index) {
if (d->intellisenseCurrentArgument == index) {
return QStringLiteral("**%1: %2**").arg(argument.name, argument.description);
} else {
return argument.name;
}
}).toList();
return args.join(libContemporaryCommon::humanReadablePartJoinString());
}

int CalculatorController::intellisenseCurrentOverload() {
return d->intellisenseCurrentOverload;
}

int CalculatorController::intellisenseTotalOverloads() {
return d->intellisenseFunctionData.overloads.length();
}

void CalculatorController::intellisenseNextOverload() {
if (d->intellisenseCurrentOverload == d->intellisenseFunctionData.overloads.length() - 1) return;
d->intellisenseCurrentOverload += 1;
d->intellisenseAutoChangeOverload = false;
emit intellisenseChanged();
}

void CalculatorController::intellisensePreviousOverload() {
if (d->intellisenseCurrentOverload == 0) return;
d->intellisenseCurrentOverload -= 1;
d->intellisenseAutoChangeOverload = false;
emit intellisenseChanged();
}

CalculatorController::TrigonometricUnit CalculatorController::trigonometricUnit() {
switch (d->evaluator.trig_unit()) {
case tcalc::angle_unit::radians:
Expand Down Expand Up @@ -288,4 +337,99 @@ void CalculatorController::expressionStringUpdated() {

void CalculatorController::calculateIntellisense() {
// Step back until we find a bracket with a function name we understand
QString relevantText = d->expressionString.left(d->cursorPosition);

// Find the previous function
QRegularExpression regex("\\w+?(?=[⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻ⁱ]*\\()");
QRegularExpressionMatchIterator matchIterator = regex.globalMatch(relevantText);

// Select the appropriate match
QStack<QString> matchSelector;
QStack<int> matchPositions;
QChar lastChar = ' ';
for (int i = 0; i < relevantText.length(); i++) {
QChar c = relevantText.at(i);
if (c == '(') {
if (matchIterator.hasNext() && QRegularExpression("\\w").match(lastChar).hasMatch()) {
QRegularExpressionMatch m = matchIterator.next();
matchSelector.push(m.captured());
matchPositions.push(m.capturedEnd());
} else {
// Push an empty string so it will be popped when it finds )
matchSelector.push("");
matchPositions.push(i);
}
} else if (c == ')') {
if (!matchSelector.isEmpty()) {
matchSelector.pop();
matchPositions.pop();
// Otherwise we'll continue and try to get the function anyway
}
}

// Ignore exponents
if (!QRegularExpression("[⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻ⁱ]").match(c).hasMatch()) {
lastChar = c;
}
}

QChar argSep = ',';
if (QLocale().decimalPoint() == ',') argSep = ';';

if (!matchSelector.isEmpty()) {
// We're currently in a function definition
auto currentFunction = matchSelector.pop();
auto currentPosition = matchPositions.pop();
while (currentFunction == "" && !matchSelector.isEmpty()) {
currentFunction = matchSelector.pop();
currentPosition = matchPositions.pop();
}

if (d->intellisenseFunction != currentFunction) {
d->intellisenseAutoChangeOverload = true;
d->intellisenseCurrentOverload = 0;
}

if (d->functionDatabase.haveFunction(currentFunction)) {
// Figure out the current argument
int currentArgument = 0;

int bracketCount = -1;
for (int i = currentPosition; i < relevantText.size(); i++) {
QChar c = relevantText.at(i);
if (c == '(') {
bracketCount++;
} else if (c == ')') {
bracketCount--;
if (bracketCount < 0) break; // Too many closing brackets
} else if (c == argSep) {
if (bracketCount == 0) currentArgument++;
}
}

if (currentArgument != -1) {
d->intellisenseCurrentArgument = currentArgument;
d->intellisenseFunctionData = d->functionDatabase.function(currentFunction);
d->intellisenseFunction = currentFunction;

if (d->intellisenseAutoChangeOverload) {
// Find the first overload with n arguments
for (auto i = 0; i < d->intellisenseFunctionData.overloads.length(); i++) {
if (d->intellisenseFunctionData.overloads.at(i).arguments.length() > d->intellisenseCurrentArgument) {
d->intellisenseCurrentOverload = i;
break;
}
}
}

emit intellisenseChanged();
return;
}
}
}

d->intellisenseFunction.clear();
d->intellisenseAutoChangeOverload = true;
d->intellisenseCurrentOverload = 0;
emit intellisenseChanged();
}
6 changes: 6 additions & 0 deletions application/calculator/calculatorcontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class CalculatorController : public QObject {
Q_PROPERTY(QString intellisenseFunction READ intellisenseFunction NOTIFY intellisenseChanged FINAL)
Q_PROPERTY(QString intellisenseDescription READ intellisenseDescription NOTIFY intellisenseChanged FINAL)
Q_PROPERTY(QString intellisenseArguments READ intellisenseArguments NOTIFY intellisenseChanged FINAL)
Q_PROPERTY(int intellisenseCurrentOverload READ intellisenseCurrentOverload NOTIFY intellisenseChanged FINAL)
Q_PROPERTY(int intellisenseTotalOverloads READ intellisenseTotalOverloads NOTIFY intellisenseChanged FINAL)
Q_PROPERTY(int errorStartLocation READ errorStartLocation NOTIFY instantResultChanged FINAL)
Q_PROPERTY(int errorEndLocation READ errorEndLocation NOTIFY instantResultChanged FINAL)
Q_PROPERTY(QAbstractItemModel* history READ history CONSTANT FINAL)
Expand Down Expand Up @@ -50,6 +52,10 @@ class CalculatorController : public QObject {
QString intellisenseFunction();
QString intellisenseDescription();
QString intellisenseArguments();
int intellisenseCurrentOverload();
int intellisenseTotalOverloads();
Q_SCRIPTABLE void intellisenseNextOverload();
Q_SCRIPTABLE void intellisensePreviousOverload();

TrigonometricUnit trigonometricUnit();
void setTrigonometricUnit(TrigonometricUnit unit);
Expand Down
Loading

0 comments on commit cc5bc41

Please sign in to comment.