[Python] Modified the paste effect in script and shell area

When a formula is pasted in a script or in the shell, some symbols are
replaced by their equivalent in python :
x turns into *
^ turns into **
√ turns into sqrt
etc
Change-Id: If6f2a22d4f3c148c2655e0892023b0e28058a9a6
This commit is contained in:
Arthur Camouseigt
2020-06-29 15:33:10 +02:00
committed by Émilie Feral
parent 4b965a0ff6
commit 8f97a332f6
10 changed files with 261 additions and 67 deletions

View File

@@ -85,11 +85,13 @@ App::App(Snapshot * snapshot) :
m_codeStackViewController(&m_modalViewController, &m_listFooter),
m_variableBoxController(snapshot->scriptStore())
{
Clipboard::sharedClipboard()->enterPython();
}
App::~App() {
assert(!m_consoleController.inputRunLoopActive());
deinitPython();
Clipboard::sharedClipboard()->exitPython();
}
bool App::handleEvent(Ion::Events::Event event) {

View File

@@ -1,44 +1,20 @@
#include "helpers.h"
#include <string.h>
#include <ion/unicode/code_point.h>
#include <ion.h>
#include <escher/clipboard.h>
namespace Code {
namespace Helpers {
class EventTextPair {
public:
constexpr EventTextPair(Ion::Events::Event event, const char * text) : m_event(event), m_text(text) {}
Ion::Events::Event event() const { return m_event; }
const char * text() const { return m_text; }
private:
const Ion::Events::Event m_event;
const char * m_text;
};
static_assert('\x11' == UCodePointEmpty, "Unicode error");
static constexpr EventTextPair sEventTextMap[] = {
EventTextPair(Ion::Events::XNT, "x"),
EventTextPair(Ion::Events::Exp, "exp(\x11)"),
EventTextPair(Ion::Events::Ln, "log(\x11)"),
EventTextPair(Ion::Events::Log, "log10(\x11)"),
EventTextPair(Ion::Events::Imaginary, "1j"),
EventTextPair(Ion::Events::Power, "**"),
EventTextPair(Ion::Events::Pi, "pi"),
EventTextPair(Ion::Events::Sqrt, "sqrt(\x11)"),
EventTextPair(Ion::Events::Square, "**2"),
EventTextPair(Ion::Events::Multiplication, "*"),
EventTextPair(Ion::Events::EE, "e"),
};
const char * PythonTextForEvent(Ion::Events::Event event) {
for (size_t i=0; i<sizeof(sEventTextMap)/sizeof(sEventTextMap[0]); i++) {
if (event == sEventTextMap[i].event()) {
return sEventTextMap[i].text();
for (size_t i=0; i<NumberOfPythonTextPairs; i++) {
UTF8Helper::TextPair pair = PythonTextPairs[i];
if (event.text() == pair.firstString()) {
return pair.secondString();
}
if (event == Ion::Events::XNT) {
return "x";
}
}
return nullptr;
}
}
}

View File

@@ -89,6 +89,7 @@ escher_src += $(addprefix escher/src/,\
)
tests_src += $(addprefix escher/test/,\
clipboard.cpp \
layout_field.cpp\
)

View File

@@ -3,15 +3,43 @@
#include <escher/text_field.h>
#include <poincare/layout.h>
#include <ion/unicode/utf8_helper.h>
class Clipboard {
public:
static Clipboard * sharedClipboard();
void store(const char * storedText, int length = -1);
const char * storedText() const { return m_textBuffer; }
const char * storedText() { return m_textBuffer; }
void reset();
void enterPython() { replaceCharForPython(true); }
void exitPython() { replaceCharForPython(false); }
static constexpr int k_bufferSize = TextField::maxBufferSize();
private:
char m_textBuffer[TextField::maxBufferSize()];
char m_textBuffer[k_bufferSize];
void replaceCharForPython(bool entersPythonApp);
};
/* The order in which the text pairs are stored is important. Indeed when leaving
* python, the text stored in the buffer is converted into an input for other
* apps. Therefore if we want to convert "3**3" into "3^3", the function must
* look for "**" paterns before "*". Otherwise, we will get "3××3". */
static constexpr int NumberOfPythonTextPairs = 12;
static constexpr UTF8Helper::TextPair PythonTextPairs[NumberOfPythonTextPairs] = {
UTF8Helper::TextPair("√(\x11)", "sqrt(\x11)", true),
UTF8Helper::TextPair("^(\x11)", "exp(\x11)", true),
UTF8Helper::TextPair("log(\x11)", "log10(\x11)", true),
UTF8Helper::TextPair("ln(\x11)", "log(\x11)", true),
UTF8Helper::TextPair("", "e"),
UTF8Helper::TextPair("𝐢", "1j"),
/* Since textPairs are also used to pair events, we need to keep both ^2 and ^
* to get the desired behavior in python when using power or square key*/
UTF8Helper::TextPair("^2", "**2"),
UTF8Helper::TextPair("^", "**"),
UTF8Helper::TextPair("π", "pi"),
UTF8Helper::TextPair("×", "*"),
UTF8Helper::TextPair("·", "*"),
UTF8Helper::TextPair("][", "], ["),
};
#endif

View File

@@ -1,4 +1,5 @@
#include <escher/clipboard.h>
#include <escher/text_field.h>
#include <algorithm>
static Clipboard s_clipboard;
@@ -14,3 +15,7 @@ void Clipboard::store(const char * storedText, int length) {
void Clipboard::reset() {
strlcpy(m_textBuffer, "", 1);
}
void Clipboard::replaceCharForPython(bool entersPythonApp) {
UTF8Helper::tryAndReplacePatternsInStringByPatterns((char *)m_textBuffer, TextField::maxBufferSize(), (UTF8Helper::TextPair *)&PythonTextPairs, NumberOfPythonTextPairs, entersPythonApp);
}

View File

@@ -123,6 +123,7 @@ bool TextField::ContentView::insertTextAtLocation(const char * text, char * loca
assert(m_isEditing);
size_t textLength = textLen < 0 ? strlen(text) : (size_t)textLen;
// TODO when paste fails because of a too big message, create a pop-up
if (m_currentDraftTextLength + textLength >= m_draftTextBufferSize || textLength == 0) {
return false;
}

21
escher/test/clipboard.cpp Normal file
View File

@@ -0,0 +1,21 @@
#include <quiz.h>
#include <string.h>
#include <escher/clipboard.h>
void assert_clipboard_enters_and_exits_python(const char * string, const char * stringResult) {
Clipboard * clipboard = Clipboard::sharedClipboard();
clipboard->store(string);
clipboard->enterPython();
quiz_assert(strcmp(clipboard->storedText(), stringResult) == 0);
clipboard->exitPython();
quiz_assert(strcmp(clipboard->storedText(), string) == 0);
}
QUIZ_CASE(escher_clipboard_enters_and_exits_python) {
assert_clipboard_enters_and_exits_python("4×4", "4*4");
assert_clipboard_enters_and_exits_python("^(ln(4))", "exp(log(4))");
assert_clipboard_enters_and_exits_python("ln(log(ln(π)))^𝐢", "log(log10(log(pi)))**1j");
assert_clipboard_enters_and_exits_python("√(1ᴇ10)", "sqrt(1e10)");
assert_clipboard_enters_and_exits_python("1×𝐢^2", "1*1j**2");
assert_clipboard_enters_and_exits_python("12^(1/4)×(π/6)×(12×π)^(1/4)", "12**(1/4)*(pi/6)*(12*pi)**(1/4)");
}

View File

@@ -6,6 +6,19 @@
namespace UTF8Helper {
class TextPair {
public:
constexpr TextPair(const char * firstString, const char * secondString, bool removeParenthesesExtention = false) : m_firstString(firstString), m_secondString(secondString), m_removeParenthesesExtention(removeParenthesesExtention){}
const char * firstString() { return m_firstString; }
const char * secondString() { return m_secondString; }
bool removeParenthesesExtention() { return m_removeParenthesesExtention; }
static constexpr int k_maxLength = 20;
private:
const char * m_firstString;
const char * m_secondString;
bool m_removeParenthesesExtention;
};
// Returns the number of occurences of a code point in a string
int CountOccurrences(const char * s, CodePoint c);
@@ -28,6 +41,25 @@ bool CopyAndRemoveCodePoints(char * dst, size_t dstSize, const char * src, CodeP
* points where removed before it. Ensure null-termination of dst. */
void RemoveCodePoint(char * buffer, CodePoint c, const char * * indexToUpdate = nullptr, const char * stoppingPosition = nullptr);
/* Slides a string by a number of chars. If slidingSize < 0, the string is slided
* to the left losing the first chars. Returns true if successful.
* Exemples :
* slideStringByNumberOfChar("12345", 2, 7) gives "1212345"
* slideStringByNumberOfChar("12345", 2, 5) gives "12123"
* slideStringByNumberOfChar("12345", -2, 5) gives "34545"*/
bool slideStringByNumberOfChar(char * text, int slidingSize, int textMaxLength);
/* Looks for patterns in a string. If a pattern is found, it is replaced by
* the one associated in the TextPair struct.
* - firstToSecond defines if replace the first string of a TextPair by the second
* or the other way around.
* - indexToUpdate is a pointer to a char in the string. It will be updated to
* point to the same place after calling the function.
* - stoppingPosition allows partial replacement in the string.
*
* Ensure null termination of the string or set the value of stoppingPosition*/
void tryAndReplacePatternsInStringByPatterns(char * text, int textMaxSize, TextPair * textPairs, int numberOfPairs, bool firstToSecond, const char * * indexToUpdate = nullptr, const char * stoppingPosition = nullptr);
/* Copy src into dst until end of dst or code point c, with null termination. Return the length of the copy */
size_t CopyUntilCodePoint(char * dst, size_t dstSize, const char * src, CodePoint c);

View File

@@ -132,37 +132,104 @@ bool CopyAndRemoveCodePoints(char * dst, size_t dstSize, const char * src, CodeP
}
void RemoveCodePoint(char * buffer, CodePoint c, const char * * pointerToUpdate, const char * stoppingPosition) {
UTF8Decoder decoder(buffer);
const char * currentPointer = buffer;
CodePoint codePoint = decoder.nextCodePoint();
const char * initialPointerToUpdate = *pointerToUpdate;
const char * nextPointer = decoder.stringPosition();
size_t bufferIndex = 0;
constexpr int patternMaxSize = CodePoint::MaxCodePointCharLength + 1; // +1 for null terminating char
char pattern[patternMaxSize];
int codePointCharSize = UTF8Decoder::CharSizeOfCodePoint(c);
(void)codePointCharSize; // Silence compilation warning about unused variable.
UTF8Decoder::CodePointToChars(c, pattern, codePointCharSize);
pattern[codePointCharSize] = '\0';
TextPair pair(pattern, "");
tryAndReplacePatternsInStringByPatterns(buffer, strlen(buffer), &pair, 1, true, pointerToUpdate, stoppingPosition);
}
while (codePoint != UCodePointNull && (stoppingPosition == nullptr || currentPointer < stoppingPosition)) {
if (codePoint != c) {
int copySize = nextPointer - currentPointer;
memmove(buffer + bufferIndex, currentPointer, copySize);
bufferIndex+= copySize;
} else if (pointerToUpdate != nullptr && currentPointer < initialPointerToUpdate) {
assert(*pointerToUpdate - buffer >= codePointCharSize);
*pointerToUpdate = *pointerToUpdate - codePointCharSize;
}
currentPointer = nextPointer;
codePoint = decoder.nextCodePoint();
nextPointer = decoder.stringPosition();
bool slideStringByNumberOfChar(char * text, int slidingSize, int textMaxLength) {
int lenText = strlen(text);
if (lenText + slidingSize > textMaxLength || lenText + slidingSize < 0) {
return false;
}
if (codePoint == UCodePointNull) {
*(buffer + bufferIndex) = 0;
} else {
assert(stoppingPosition != nullptr);
// Find the null-terminating code point
const char * nullTermination = currentPointer + strlen(currentPointer);
/* Copy what remains of the buffer after the stopping position for code
* point removal */
memmove(buffer + bufferIndex, stoppingPosition, nullTermination - stoppingPosition + 1);
if (slidingSize > 0) {
memmove(text+slidingSize, text, strlen(text)+1);
} else if (slidingSize < 0) {
memmove(text, text-slidingSize, strlen(text)+1);
}
// In case slidingSize = 0, there is nothing to do
return true;
}
/* Replaces the first chars of a string by other ones. If the sizes are different
* the rest of the string will be moved right after the replacement chars.
* If successful returns true.*/
static bool replaceFirstCharsByPattern(char * text, int lengthOfPatternToRemove, const char * replacementPattern, int textMaxLength) {
int lengthOfReplacementPattern = strlen(replacementPattern);
if (lengthOfPatternToRemove <= strlen(text) && slideStringByNumberOfChar(text, lengthOfReplacementPattern-lengthOfPatternToRemove, textMaxLength)) {
for (int i = 0; i < lengthOfReplacementPattern; i++) {
text[i] = replacementPattern[i];
}
return true;
}
return false;
}
void tryAndReplacePatternsInStringByPatterns(char * text, int textMaxLength, TextPair * textPairs, int numberOfPairs, bool firstToSecond, const char * * pointerToUpdate, const char * stoppingPosition) {
size_t i = 0;
size_t iPrev = 0;
size_t textLength = strlen(text);
size_t lengthOfParenthesisExtention = strlen("(\x11)");
while(i < textLength) {
iPrev = i;
bool didReplace = false;
for (int j = 0; j < numberOfPairs; j++) {
TextPair p = textPairs[j];
size_t firstStringLength = strlen(p.firstString());
size_t secondStringLength = strlen(p.secondString());
/* Instead of storing TextPair("√(\x11)", "sqrt(\x11)") for the keyboard
* events and TextPair("√", "sqrt") for the copy paste, we store just the
* first and register it as "function". Therefore we can decide to remove
* the (\x11) part or not depending on the application. This process is
* repeated for all 4 function keys usable in python (√, , ln, log)*/
if (p.removeParenthesesExtention()) {
firstStringLength -= lengthOfParenthesisExtention;
secondStringLength -= lengthOfParenthesisExtention;
}
char firstString[TextPair::k_maxLength];
char secondString[TextPair::k_maxLength];
// Getting rid of the eventual (\x11) part
strlcpy((char *)firstString, p.firstString(), firstStringLength+1);
strlcpy((char *)secondString, p.secondString(), secondStringLength+1);
char * matchedString = firstToSecond ? firstString : secondString;
size_t matchedStringLength = strlen(matchedString);
char * replacingString = firstToSecond ? secondString : firstString;
size_t replacingStringLength = strlen(replacingString);
if (strncmp(&text[i], matchedString, matchedStringLength) == 0) {
didReplace = replaceFirstCharsByPattern(&text[i], matchedStringLength, replacingString, textMaxLength);
if (didReplace) {
int delta = replacingStringLength - matchedStringLength;
textLength += delta;
if (pointerToUpdate != nullptr && &text[i] < *pointerToUpdate) {
// We still have to update the pointer as the modification cursor has not yet exceeded it.
*pointerToUpdate = *pointerToUpdate + delta;
}
if (stoppingPosition != nullptr) {
stoppingPosition = stoppingPosition + delta;
}
if (replacingStringLength != 0) {
i += replacingStringLength - 1;
/* When working with multiple TextPairs at the same time, it can be
* usefull to go back by one char. That is the case for empty matrixes
* Indeed, in the string ",,]", ",," is replaced by ",\x11,".
* The ",]" pattern right after would be missed if not for the -1.*/
}
}
}
}
if (iPrev == i && !didReplace) {
// In case no pattern matched with the text, we go to the next char.
i++;
}
if ((stoppingPosition != nullptr) && (&text[i] >= stoppingPosition)) {
break;
}
}
}

View File

@@ -53,9 +53,8 @@ void assert_copy_and_remove_code_points_gives(char * dst, size_t dstSize, const
quiz_assert(dst[i] == result[i]);
}
}
static int bufferSize = 100;
QUIZ_CASE(ion_utf8_copy_and_remove_code_point) {
constexpr int bufferSize = 100;
char buffer[bufferSize];
const char * s = "12345";
@@ -116,7 +115,6 @@ void assert_remove_code_point_gives(char * buffer, CodePoint c, const char * * i
}
QUIZ_CASE(ion_utf8_remove_code_point) {
constexpr int bufferSize = 100;
char buffer[bufferSize];
const char * s = "2345";
@@ -165,13 +163,77 @@ QUIZ_CASE(ion_utf8_remove_code_point) {
assert_remove_code_point_gives(buffer, c, &indexToUpdate, stoppingPosition, indexToUpdateResult, result);
}
void assert_slide_string_by_number_of_char_gives(const char * string, int slidingSize, bool successResult, const char * stringResult = nullptr) {
char buffer[bufferSize];
strlcpy(buffer, string, bufferSize);
bool success = UTF8Helper::slideStringByNumberOfChar((char *)buffer, slidingSize, bufferSize);
quiz_assert(success == successResult);
if (successResult) {
quiz_assert(strncmp(buffer, stringResult, bufferSize) == 0);
}
}
QUIZ_CASE(ion_utf8_move_string_from_index_by_number_of_char) {
const char * string1 = "12345";
assert_slide_string_by_number_of_char_gives(string1, 1, true, "112345");
const char * string2 = "(1+3)";
assert_slide_string_by_number_of_char_gives(string2, 3, true, "(1+(1+3)");
assert_slide_string_by_number_of_char_gives(string2, bufferSize - strlen(string2)/2, false);
const char * string3 = "exp(3+4)";
assert_slide_string_by_number_of_char_gives(string3, -3, true, "(3+4)");
assert_slide_string_by_number_of_char_gives(string3, -(strlen(string3)+3), false);
assert_slide_string_by_number_of_char_gives(string3, -8, true, "");
}
void assert_try_and_replace_pattern_in_string_by_pattern_gives(char * buffer, int bufferSize, UTF8Helper::TextPair * textPairs, int numberOfPairs, bool firstToSecond, const char * stringResult, const char ** indexToUpdate = nullptr, const char * indexToUpdateResult = nullptr, const char * stoppingPosition = nullptr) {
UTF8Helper::tryAndReplacePatternsInStringByPatterns(buffer, bufferSize, textPairs, numberOfPairs, firstToSecond, indexToUpdate, stoppingPosition);
quiz_assert(strncmp(buffer, stringResult, bufferSize) == 0);
if (indexToUpdateResult != nullptr) {
quiz_assert(*indexToUpdate == indexToUpdateResult);
}
}
QUIZ_CASE(ion_utf8_try_and_replace_pattern_in_string_by_pattern) {
constexpr int numberOfPairs = 2;
constexpr UTF8Helper::TextPair textPairs[numberOfPairs] = {
UTF8Helper::TextPair("12", "2.3"),
UTF8Helper::TextPair("exp", "ln"),
};
char buffer[bufferSize];
const char * string = "1234512";
strlcpy(buffer, string, bufferSize);
const char * indexToUpdate = buffer + 3;
const char * indexToUpdateResult = indexToUpdate + 1;
const char * result = "2.33452.3";
const char * stoppingPosition = nullptr;
assert_try_and_replace_pattern_in_string_by_pattern_gives(buffer, bufferSize, (UTF8Helper::TextPair *)&textPairs, numberOfPairs, true, result, &indexToUpdate, indexToUpdateResult);
string = "exp(2.3)12";
strlcpy(buffer, string, bufferSize);
indexToUpdate = buffer + 3;
indexToUpdateResult = indexToUpdate - 1;
result = "ln(2.3)12";
stoppingPosition = buffer + 5;
assert_try_and_replace_pattern_in_string_by_pattern_gives(buffer, bufferSize, (UTF8Helper::TextPair *)&textPairs, numberOfPairs, true, result, &indexToUpdate, indexToUpdateResult, stoppingPosition);
string = "12*ln(7)+ln";
strlcpy(buffer, string, bufferSize);
indexToUpdate = buffer + 7;
indexToUpdateResult = indexToUpdate + 1;
result = "12*exp(7)+ln";
stoppingPosition = buffer + 7;
assert_try_and_replace_pattern_in_string_by_pattern_gives(buffer, bufferSize, (UTF8Helper::TextPair *)&textPairs, numberOfPairs, false, result, &indexToUpdate, indexToUpdateResult, stoppingPosition);
}
void assert_string_copy_until_code_point_gives(char * dst, size_t dstSize, const char * src, CodePoint c, const char * result, size_t returnedResult) {
quiz_assert(UTF8Helper::CopyUntilCodePoint(dst, dstSize, src, c) == returnedResult);
quiz_assert(strcmp(dst, result) == 0);
}
QUIZ_CASE(ion_utf8_helper_copy_until_code_point) {
constexpr int bufferSize = 100;
char buffer[bufferSize];
const char * s = "1234";
@@ -217,7 +279,6 @@ void assert_string_remove_previous_glyph_gives(const char * text, char * locatio
}
QUIZ_CASE(ion_utf8_helper_remove_previous_glyph) {
constexpr int bufferSize = 100;
char buffer[bufferSize];
// 3é4
buffer[0] = '3';