Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/code/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ app_code_src = $(addprefix apps/code/,\
script_name_cell.cpp \
script_parameter_controller.cpp \
toolbox_ion_keys.cpp \
brackets.c \
)

app_code_test_src = $(addprefix apps/code/,\
Expand Down
103 changes: 103 additions & 0 deletions apps/code/brackets.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#include "brackets.h"

void initTable(DynamicTable* table) {
table->size = 0;
table->capacity = 10;
table->positions = (int*)malloc(table->capacity * sizeof(int));
}

void initStaticTable(StaticTable* table) {
table->size = 0;
}

void addToTable(DynamicTable* table, int position) {
if (table->size == table->capacity) {
table->capacity *= 2;
table->positions = (int*)realloc(table->positions, table->capacity * sizeof(int));
}
table->positions[table->size++] = position;
}


void addToStaticTable(StaticTable* table, int position) {
if (table->size < MAX_PARENTHESES) {
table->positions[table->size++] = position;
}
}
bool isInTable(DynamicTable* table, int position) {
for (int i = 0; i < table->size; i++) {
if (table->positions[i] == position) {
return true;
}
}
return false;
}

bool isInStaticTable(StaticTable* table, int position) {
for (int i = 0; i < table->size; i++) {
if (table->positions[i] == position) {
return true;
}
}
return false;
}

void freeTable(DynamicTable* mismatches) {
free(mismatches->positions);
}

int getBracketBalance(const char* str) {
int counter = 0;
for (int i = 0; str[i] != '\0' && str[i] != '\n'; i++) {
if (
str[i] =='(' ||
str[i] =='[' ||
str[i] =='{'
) {
counter++;
}
else if (
str[i] ==')' ||
str[i] ==']' ||
str[i] =='}'
) {
counter--;
}
}
return counter;
}


void findMismatchedParentheses(const char* str, StaticTable* mismatches) {
DynamicTable stack;
initTable(&stack);
for (int i = 0; str[i] != '\0'; i++) {
if (str[i] == '(' || str[i] == '{' || str[i] == '[') {
addToTable(&stack, i);
} else if (str[i] == ')' || str[i] == '}' || str[i] == ']') {
if (stack.size == 0) {
addToStaticTable(mismatches, i);
} else {
char open = str[stack.positions[stack.size - 1]];
if ((str[i] == ')' && open == '(') ||
(str[i] == '}' && open == '{') ||
(str[i] == ']' && open == '[')) { // Matching Pair
stack.size--;
} else {
addToStaticTable(mismatches, i);

}
}
}
}

while (stack.size > 0) {
addToStaticTable(mismatches, stack.positions[--stack.size]);
}

free(stack.positions);

}



30 changes: 30 additions & 0 deletions apps/code/brackets.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef PYTHON_TEXT_AREA_BRACKETS
#define PYTHON_TEXT_AREA_BRACKETS
#define MAX_PARENTHESES 1000
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

typedef struct {
int* positions;
int size;
int capacity;
} DynamicTable;

typedef struct {
int positions[MAX_PARENTHESES];
int size;
} StaticTable;

void initTable(DynamicTable* table);
void initStaticTable(StaticTable* table);
void addToTable(DynamicTable* table, int position);
void addToStaticTable(StaticTable* table, int position);
bool isInTable(DynamicTable* table, int position);
bool isInStaticTable(StaticTable* table, int position);
void freeTable(DynamicTable* mismatches);
int getBracketBalance(const char* str);
void findMismatchedParentheses(const char* str, StaticTable* mismatches);

#endif
63 changes: 58 additions & 5 deletions apps/code/python_text_area.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include <ion/unicode/utf8_helper.h>
#include <python/port/port.h>
#include "../global_preferences.h"

#include <stdio.h>
extern "C" {
#include "py/nlr.h"
#include "py/lexer.h"
Expand All @@ -23,6 +23,11 @@ constexpr KDColor StringColor = Palette::CodeString;
constexpr KDColor BackgroundColor = Palette::CodeBackground;
constexpr KDColor HighlightColor = Palette::CodeBackgroundSelected;
constexpr KDColor AutocompleteColor = KDColor::RGB24(0xC6C6C6); // TODO Palette change
constexpr KDColor matchingParenthesisColors[3] = { // TODO Palette change
KDColor::RGB24(0x0431FA),
KDColor::RGB24(0x319331),
KDColor::RGB24(0x7B3814)
};

bool isItalic(mp_token_kind_t tokenKind) {
if (!GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()) {
Expand All @@ -34,6 +39,30 @@ bool isItalic(mp_token_kind_t tokenKind) {
return false;
}

bool isOpeningBracket(mp_token_kind_t tokenKind) {
if (!GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()) {
return false;
}
if (tokenKind == MP_TOKEN_DEL_PAREN_OPEN ||
tokenKind == MP_TOKEN_DEL_BRACE_OPEN ||
tokenKind == MP_TOKEN_DEL_BRACKET_OPEN) {
return true;
}
return false;
}

bool isClosingBracket(mp_token_kind_t tokenKind) {
if (!GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()) {
return false;
}
if (tokenKind == MP_TOKEN_DEL_PAREN_CLOSE ||
tokenKind == MP_TOKEN_DEL_BRACE_CLOSE ||
tokenKind == MP_TOKEN_DEL_BRACKET_CLOSE) {
return true;
}
return false;
}

static inline KDColor TokenColor(mp_token_kind_t tokenKind) {
if (!GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()) {
return Palette::CodeText;
Expand Down Expand Up @@ -242,16 +271,17 @@ void PythonTextArea::ContentView::clearRect(KDContext * ctx, KDRect rect) const
#define LOG_DRAW(...)
#endif

void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char * text, size_t byteLength, int fromColumn, int toColumn, const char * selectionStart, const char * selectionEnd) const {
void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char * text, size_t byteLength, int fromColumn, int toColumn, const char * selectionStart, const char * selectionEnd, StaticTable* mismatchedParenthesesPositions, int charBefore, int bracketBalance) const {
LOG_DRAW("Drawing \"%.*s\"\n", byteLength, text);

assert(m_pythonDelegate->isPythonUser(this));

/* We're using the MicroPython lexer to do syntax highlighting on a per-line
* basis. This can work, however the MicroPython lexer won't accept a line
* starting with a whitespace. So we're discarding leading whitespaces
* beforehand. */

const char * firstNonSpace = UTF8Helper::NotCodePointSearch(text, ' ');
int numberOfLeadingSpaces = strlen(text) - strlen(firstNonSpace);
if (firstNonSpace != text) {
// Color the discarded leading whitespaces
const char * spacesStart = UTF8Helper::CodePointAtGlyphOffset(text, fromColumn);
Expand Down Expand Up @@ -282,6 +312,9 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char
const char * tokenFrom = firstNonSpace;
size_t tokenLength = 0;
const char * tokenEnd = firstNonSpace;

int bracketLineBalance = 0;
int currentPosition = charBefore + numberOfLeadingSpaces;
while (lex->tok_kind != MP_TOKEN_NEWLINE && lex->tok_kind != MP_TOKEN_END && lex->tok_kind != MP_TOKEN_FSTRING_RAW) {
tokenFrom = firstNonSpace + lex->tok_column - 1;
if (tokenFrom != tokenEnd) {
Expand All @@ -298,14 +331,33 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char
selectionEnd,
HighlightColor,
false);
currentPosition += 1; // Add the number of space
}
tokenLength = TokenLength(lex, tokenFrom);
tokenEnd = tokenFrom + tokenLength;

// If the token is being autocompleted, use DefaultColor/Font
KDColor color = (tokenFrom <= autocompleteStart && autocompleteStart < tokenEnd) ? Palette::CodeText : TokenColor(lex->tok_kind);
bool italic = (tokenFrom <= autocompleteStart && autocompleteStart < tokenEnd) ? false : isItalic(lex->tok_kind);


bool mismatched = isInStaticTable(mismatchedParenthesesPositions, currentPosition);

// TODO: don't count Parentheses in a comment
if (isOpeningBracket(lex->tok_kind)) {
if (mismatched) color = KDColor::RGB24(0xFF0000);
else {
color = matchingParenthesisColors[(bracketLineBalance + bracketBalance)% 3];
bracketLineBalance++;
}
} else if (isClosingBracket(lex->tok_kind)) {
if (mismatched) color = KDColor::RGB24(0xFF0000);
else {
bracketLineBalance--;
color = matchingParenthesisColors[(bracketLineBalance + bracketBalance)% 3];
}
}


LOG_DRAW("Draw \"%.*s\" for token %d\n", tokenLength, tokenFrom, lex->tok_kind);
drawStringAt(ctx, line,
UTF8Helper::GlyphOffsetAtCodePoint(text, tokenFrom),
Expand All @@ -321,8 +373,9 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char

mp_lexer_to_next(lex);
LOG_DRAW("Pop token %d\n", lex->tok_kind);
currentPosition = currentPosition + 1;
}

tokenFrom += tokenLength;

KDColor color = CommentColor;
Expand Down
2 changes: 1 addition & 1 deletion apps/code/python_text_area.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class PythonTextArea : public TextArea {
void loadSyntaxHighlighter();
void unloadSyntaxHighlighter();
void clearRect(KDContext * ctx, KDRect rect) const override;
void drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn, const char * selectionStart, const char * selectionEnd) const override;
void drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn, const char * selectionStart, const char * selectionEnd, StaticTable* mismatchedParenthesesPositions, int charBefore, int bracketBalance) const override;
KDRect dirtyRectFromPosition(const char * position, bool includeFollowingLines) const override;
private:
App * m_pythonDelegate;
Expand Down
7 changes: 5 additions & 2 deletions escher/include/escher/text_area.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
#include <escher/text_area_delegate.h>
#include <assert.h>
#include <string.h>

extern "C" {
#include <apps/code/brackets.h>
}
// See TODO in EditableField

class TextArea : public TextInput, public InputEventHandler {
Expand Down Expand Up @@ -112,7 +114,7 @@ class TextArea : public TextInput, public InputEventHandler {
}
void drawRect(KDContext * ctx, KDRect rect) const override;
void drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor, bool isItalic = false) const;
virtual void drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn, const char * selectionStart, const char * selectionEnd) const = 0;
virtual void drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn, const char * selectionStart, const char * selectionEnd, StaticTable* mismatchedParenthesesPositions, int charBefore, int bracketBalance) const = 0;
virtual void clearRect(KDContext * ctx, KDRect rect) const = 0;
KDSize minimalSizeForOptimalDisplay() const override;
void setText(char * textBuffer, size_t textBufferSize);
Expand All @@ -121,6 +123,7 @@ class TextArea : public TextInput, public InputEventHandler {
size_t editedTextLength() const override { return m_text.textLength(); }
const Text * getText() const { return &m_text; }
bool isAbleToInsertTextAt(int textLength, const char * location, bool shouldRemoveLastCharacter) const override;
void reloadParentheses(const char * text, bool lineBreak);
void insertTextAtLocation(const char * text, char * location, int textLength = -1) override;
void moveCursorGeo(int deltaX, int deltaY);
bool removePreviousGlyph() override;
Expand Down
Loading
Loading