this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #2117 from sthilaid/editor-upgrade

Code Editor upgrades: structured s-expresion edition and emacs-mode

authored by

Vadim Grigoruk and committed by
GitHub
7be33dc4 504aefc7

+568 -99
+8
src/api.h
··· 57 57 typedef void(*tic_scanline)(tic_mem* memory, s32 row, void* data); 58 58 typedef void(*tic_border)(tic_mem* memory, s32 row, void* data); 59 59 typedef void(*tic_gamemenu)(tic_mem* memory, s32 index, void* data); 60 + typedef bool(*tic_lang_isalnum)(char c); 60 61 61 62 typedef struct 62 63 { ··· 102 103 103 104 const char* const * keywords; 104 105 s32 keywordsCount; 106 + 107 + tic_lang_isalnum lang_isalnum; 108 + bool useStructuredEdition; 109 + 110 + s32 api_keywordsCount; 111 + const char** api_keywords; 112 + 105 113 } tic_script_config; 106 114 107 115 extern tic_script_config* Languages[];
+52 -37
src/api/scheme.c
··· 876 876 "define", "lambda", "begin", "set!", "=", "<", "<=", ">", ">=", "+", "*", 877 877 "/", "'", "`", "`@", "define-macro", "let", "let*", "letrec", 878 878 "if", "cond", "floor", "ceiling", "sin", "cos", "log", "sqrt", "abs" 879 - "expt", "tan", "acos", "asin", "atan", "truncate", "round", 880 - "modulo", "remainder", "gcd", "lcm", "and", "or", 879 + "exp", "expt", "tan", "acos", "asin", "atan", "truncate", "round", 880 + "exact->inexact", "inexact->exact", "exact?", "inexact?", 881 + "modulo", "quotient", "remainder", "gcd", "lcm", "and", "or", 881 882 "eq?", "eqv?", "equal?", "equivalent?", "boolean?", "pair?", 882 883 "cons", "car", "cdr", "set-car!", "set-cdr!", "cadr", "cddr", 883 884 "cdar", "caar", "caadr", "caddr", "cadar", "caaar", "cdadr", 884 885 "cdddr", "caddar", "cadaar", "cdaadr", "cdaddr", "cdadar", 885 886 "cdaaar", "cddadr", "cddddr", "cdddar", "cddaar", "list?" 886 887 "proper-list?", "length", "make-list", "list", "reverse", 887 - "append", "list-ref", "assoc", "assq", "member", "memq", 888 + "map", "for-each", "list->vector", "list->string", "list-ref", "null?", 889 + "not", "number->string", "odd?", "even?", "zero?", 890 + "append", "list-ref", "assoc", "assq", "member", "memq", "memv", 888 891 "tree-memq", "string?", "string-length", "character?", 889 892 "number?", "integer?", "real?", "rationnal?", "rationalize", 890 893 "numerator", "denominator", "random", "random-state", ··· 894 897 "byte-vector?", "vector-ref", "vector-set!", "make-vector", 895 898 "vector-fill!", "vector->list", "make-hash-table", 896 899 "hash-table-ref", "hash-table-set!", "hash-code", 897 - "hook-functions", "input-port?", "output-port?", 898 - "port-filename", "port-line-number", "current-input-port", 899 - "current-output-port", "set-current-input-port", 900 - "set-current-output-port", "current-error-port", 901 - "set-current-error-port", "close-input-port", 902 - "close-output-port", "open-input-file", "open-output-file", 903 - "open-input-string", "open-output-string", "get-output-string", 904 - "flush-output-port", "read-char", "peek-char", "read", 900 + "hook-functions", "open-input-string", "open-output-string", 901 + "get-output-string", "read-char", "peek-char", "read", 905 902 "newline", "write-char", "write", "display", "format", 906 903 "syntax?", "symbol?", "symbol->string", "string->symbol", 907 904 "gensym", "keyword?", "string->keyword", "keyword->string", ··· 909 906 "let?", "let-ref", "openlet", "openlet?" 910 907 }; 911 908 912 - static inline bool isalnum_(char c) {return isalnum(c) || c == '_' || c == '-' || c == ':' || c == '#';} 909 + static inline bool scheme_isalnum(char c) 910 + { 911 + return isalnum(c) || c == '_' || c == '-' || c == ':' || c == '#' || c == '!' 912 + || c == '+' || c == '=' || c == '&' || c == '^' || c == '%' || c == '$' || c == '@'; 913 + } 913 914 914 915 static const tic_outline_item* getSchemeOutline(const char* code, s32* size) 915 916 { ··· 943 944 { 944 945 char c = *ptr; 945 946 946 - if(isalnum_(c)); 947 + if(scheme_isalnum(c)); 947 948 else 948 949 { 949 950 end = ptr; ··· 983 984 s7_eval_c_string(sc, code); 984 985 } 985 986 987 + static const char* SchemeAPIKeywords[] = { 988 + #define TIC_CALLBACK_DEF(name, ...) #name, 989 + TIC_CALLBACK_LIST(TIC_CALLBACK_DEF) 990 + #undef TIC_CALLBACK_DEF 991 + 992 + #define API_KEYWORD_DEF(name, ...) "t80::" #name, 993 + TIC_API_LIST(API_KEYWORD_DEF) 994 + #undef API_KEYWORD_DEF 995 + }; 996 + 986 997 tic_script_config SchemeSyntaxConfig = 987 998 { 988 - .id = 19, 989 - .name = "scheme", 990 - .fileExtension = ".scm", 991 - .projectComment = ";;", 999 + .id = 19, 1000 + .name = "scheme", 1001 + .fileExtension = ".scm", 1002 + .projectComment = ";;", 992 1003 { 993 - .init = initScheme, 994 - .close = closeScheme, 995 - .tick = callSchemeTick, 996 - .boot = callSchemeBoot, 1004 + .init = initScheme, 1005 + .close = closeScheme, 1006 + .tick = callSchemeTick, 1007 + .boot = callSchemeBoot, 997 1008 998 - .callback = 1009 + .callback = 999 1010 { 1000 - .scanline = callSchemeScanline, 1001 - .border = callSchemeBorder, 1002 - .menu = callSchemeMenu, 1011 + .scanline = callSchemeScanline, 1012 + .border = callSchemeBorder, 1013 + .menu = callSchemeMenu, 1003 1014 }, 1004 1015 }, 1005 1016 1006 - .getOutline = getSchemeOutline, 1007 - .eval = evalScheme, 1017 + .getOutline = getSchemeOutline, 1018 + .eval = evalScheme, 1008 1019 1009 - .blockCommentStart = NULL, 1010 - .blockCommentEnd = NULL, 1011 - .blockCommentStart2 = NULL, 1012 - .blockCommentEnd2 = NULL, 1013 - .singleComment = ";;", 1014 - .blockStringStart = "\"", 1015 - .blockStringEnd = "\"", 1016 - .blockEnd = NULL, 1020 + .blockCommentStart = NULL, 1021 + .blockCommentEnd = NULL, 1022 + .blockCommentStart2 = NULL, 1023 + .blockCommentEnd2 = NULL, 1024 + .singleComment = ";;", 1025 + .blockStringStart = "\"", 1026 + .blockStringEnd = "\"", 1027 + .blockEnd = NULL, 1028 + .lang_isalnum = scheme_isalnum, 1029 + .api_keywords = SchemeAPIKeywords, 1030 + .api_keywordsCount = COUNT_OF(SchemeAPIKeywords), 1031 + .useStructuredEdition = true, 1017 1032 1018 - .keywords = SchemeKeywords, 1019 - .keywordsCount = COUNT_OF(SchemeKeywords), 1033 + .keywords = SchemeKeywords, 1034 + .keywordsCount = COUNT_OF(SchemeKeywords), 1020 1035 }; 1021 1036 1022 1037 #endif /* defined(TIC_BUILD_WITH_SCHEME) */
+1
src/studio/config.c
··· 246 246 .fullscreen = false, 247 247 .integerScale = INTEGER_SCALE_DEFAULT, 248 248 #if defined(BUILD_EDITORS) 249 + .emacsMode = false, 249 250 .devmode = false, 250 251 #endif 251 252 },
+452 -59
src/studio/editors/code.c
··· 40 40 # define MAX_CODE TIC_BANK_SIZE 41 41 #endif 42 42 43 + #define noop (void)0 44 + 43 45 typedef struct CodeState CodeState; 44 46 45 47 static_assert(sizeof(CodeState) == 2, "CodeStateSize"); ··· 399 401 400 402 static inline bool islineend(char c) {return c == '\n' || c == '\0';} 401 403 static inline bool isalpha_(char c) {return isalpha(c) || c == '_';} 402 - static inline bool isalnum_(char c) {return isalnum(c) || c == '_';} 404 + static inline bool isdoublequote(char c) {return c == '"'; } 405 + static inline bool isopenparen_(Code* code, char c) {return c == '(' || c == '{' || c == '[';} 406 + static inline bool iscloseparen_(Code* code, char c) {return c == ')' || c == '}' || c == ']';} 407 + 408 + static inline bool config_isalnum_(const tic_script_config* config, char c) 409 + { 410 + if (config->lang_isalnum) 411 + return config->lang_isalnum(c); 412 + else 413 + return isalnum(c) || c == '_'; 414 + } 415 + 416 + static inline bool isalnum_(Code* code, char c) 417 + { 418 + tic_mem* tic = code->tic; 419 + const tic_script_config* config = tic_core_script_config(tic); 420 + return config_isalnum_(config, c); 421 + } 403 422 404 423 static void setCodeState(CodeState* state, u8 color, s32 start, s32 size) 405 424 { ··· 491 510 } 492 511 else if(wordStart) 493 512 { 494 - while(!islineend(*ptr) && isalnum_(*ptr)) ptr++; 513 + while(!islineend(*ptr) && config_isalnum_(config, *ptr)) ptr++; 495 514 496 515 s32 len = (s32)(ptr - wordStart); 497 516 bool keyword = false; ··· 507 526 508 527 if(!keyword) 509 528 { 510 - static const char* const ApiKeywords[] = 511 - { 529 + static const char* const ApiKeywords[] = { 512 530 #define TIC_CALLBACK_DEF(name, ...) #name, 513 531 TIC_CALLBACK_LIST(TIC_CALLBACK_DEF) 514 532 #undef TIC_CALLBACK_DEF ··· 517 535 TIC_API_LIST(API_KEYWORD_DEF) 518 536 #undef API_KEYWORD_DEF 519 537 }; 538 + const char* const* keywords = config->api_keywordsCount > 0 ? config->api_keywords : ApiKeywords; 539 + const s32 apiCount = config->api_keywordsCount > 0 ? config->api_keywordsCount : COUNT_OF(ApiKeywords); 520 540 521 - for(s32 i = 0; i < COUNT_OF(ApiKeywords); i++) 522 - if(len == strlen(ApiKeywords[i]) && memcmp(wordStart, ApiKeywords[i], len) == 0) 541 + for(s32 i = 0; i < apiCount; i++) 542 + if(len == strlen(keywords[i]) && memcmp(wordStart, keywords[i], len) == 0) 523 543 { 524 544 setCodeState(state, SyntaxType_API, (s32)(wordStart - start), len); 525 545 break; ··· 716 736 updateCursorPosition(code, pointer); 717 737 } 718 738 739 + static void startLine(Code* code) 740 + { 741 + char* start = code->src; 742 + while(code->cursor.position > start+1) 743 + { 744 + if (islineend(*(code->cursor.position-1))) 745 + break; 746 + --code->cursor.position; 747 + } 748 + updateColumn(code); 749 + } 750 + 719 751 static void endLine(Code* code) 720 752 { 721 753 while(*code->cursor.position) 722 754 { 755 + if (islineend(*code->cursor.position)) 756 + break; 723 757 code->cursor.position++; 724 758 } 725 759 updateColumn(code); ··· 763 797 } 764 798 } 765 799 766 - static char* leftWordPos(Code* code) 800 + typedef bool(*tic_code_predicate)(Code* code, char c); 801 + 802 + static char* leftPos(Code* code, char* pos, tic_code_predicate pred) 767 803 { 768 804 const char* start = code->src; 769 - char* pos = code->cursor.position - 1; 770 - 771 805 if(pos > start) 772 806 { 773 - if(isalnum_(*pos)) while(pos > start && isalnum_(*(pos-1))) pos--; 774 - else while(pos > start && !isalnum_(*(pos-1))) pos--; 807 + if(pred(code, *pos)) while(pos > start && pred(code, *(pos-1))) pos--; 808 + else while(pos > start && !pred(code, *(pos-1))) pos--; 775 809 return pos; 776 810 } 777 811 778 812 return code->cursor.position; 779 813 } 780 814 815 + static char* leftWordPos(Code* code) 816 + { 817 + return leftPos(code, code->cursor.position-1, isalnum_); 818 + } 819 + 781 820 static void leftWord(Code* code) 782 821 { 783 822 code->cursor.position = leftWordPos(code); 784 823 updateColumn(code); 785 824 } 786 825 787 - static char* rightWordPos(Code* code) 826 + static char* leftQuote(Code* code, char* pos) 827 + { 828 + char* start = code->src; 829 + while (pos > start) 830 + { 831 + if (isdoublequote(*pos) && *(pos-1) != '\\') 832 + return pos; 833 + --pos; 834 + } 835 + return start; 836 + } 837 + 838 + static char* rightQuote(Code* code, char* pos) 839 + { 840 + char* end = code->src + strlen(code->src); 841 + while (pos < end) 842 + { 843 + if (isdoublequote(*pos) && *(pos-1) != '\\') 844 + return pos; 845 + ++pos; 846 + } 847 + return end; 848 + } 849 + 850 + static char* leftSexp(Code* code, char* pos) 851 + { 852 + char* start = code->src; 853 + int nesting = 0; 854 + 855 + while (pos >= start) 856 + { 857 + if (isopenparen_(code, *pos)) 858 + { 859 + if (nesting == 0) 860 + return pos; 861 + else 862 + --nesting; 863 + } 864 + else if (iscloseparen_(code, *pos)) 865 + { 866 + ++nesting; 867 + } 868 + --pos; 869 + } 870 + return start; // fallback 871 + } 872 + 873 + static char* rightPos(Code* code, char* pos, tic_code_predicate pred) 788 874 { 789 875 const char* end = code->src + strlen(code->src); 790 - char* pos = code->cursor.position; 791 876 792 877 if(pos < end) 793 878 { 794 - if(isalnum_(*pos)) while(pos < end && isalnum_(*pos)) pos++; 795 - else while(pos < end && !isalnum_(*pos)) pos++; 796 - 879 + if(pred(code, *pos)) while(pos < end && pred(code, *pos)) pos++; 880 + else while(pos < end && !pred(code, *pos)) pos++; 797 881 } 798 882 799 883 return pos; 800 884 } 801 885 886 + static char* rightWordPos(Code* code) 887 + { 888 + return rightPos(code, code->cursor.position, isalnum_); 889 + } 890 + 802 891 static void rightWord(Code* code) 803 892 { 804 893 code->cursor.position = rightWordPos(code); 805 894 updateColumn(code); 806 895 } 807 896 897 + static char* rightSexp(Code* code, char* pos) 898 + { 899 + char* end = code->src + strlen(code->src); 900 + int nesting = 0; 901 + 902 + while (pos < end) 903 + { 904 + if (iscloseparen_(code, *pos)) 905 + { 906 + if (nesting == 0) 907 + return pos; 908 + else 909 + --nesting; 910 + } 911 + else if (isopenparen_(code, *pos)) 912 + { 913 + ++nesting; 914 + } 915 + ++pos; 916 + } 917 + return end; // fallback 918 + } 919 + 808 920 static void goHome(Code* code) 809 921 { 810 922 code->cursor.position = getLine(code); ··· 904 1016 return false; 905 1017 } 906 1018 1019 + static bool structuredDeleteOverride(Code* code, char* pos) 1020 + { 1021 + const bool useStructuredEdit = tic_core_script_config(code->tic)->useStructuredEdition; 1022 + if (!useStructuredEdit) 1023 + return false; 1024 + 1025 + const char* end = code->src + strlen(code->src); 1026 + if (pos >= end-1) 1027 + return false; 1028 + 1029 + const bool isopen = isopenparen_(code, *pos); 1030 + const bool isclose = iscloseparen_(code, *pos); 1031 + if (isopen || isclose) 1032 + { 1033 + if (isopen && iscloseparen_(code, *(pos+1)) 1034 + || isclose && isopenparen_(code, *(pos+1))) 1035 + { 1036 + deleteCode(code, pos, pos+2); 1037 + history(code); 1038 + parseSyntaxColor(code); 1039 + updateEditor(code); 1040 + } 1041 + // if not, disallow deletion until it's empty 1042 + return true; 1043 + } 1044 + 1045 + if (isdoublequote(*pos)) 1046 + { 1047 + const bool canRemoveDblQuote = isdoublequote(*(pos+1)); 1048 + if (canRemoveDblQuote) 1049 + { 1050 + deleteCode(code, pos, pos+2); 1051 + history(code); 1052 + parseSyntaxColor(code); 1053 + updateEditor(code); 1054 + } 1055 + // only allow deletion when string is empty 1056 + return true; 1057 + } 1058 + 1059 + return false; 1060 + } 1061 + 907 1062 static void deleteChar(Code* code) 908 1063 { 909 1064 if(!replaceSelection(code)) 910 1065 { 1066 + if (structuredDeleteOverride(code, code->cursor.position)) 1067 + return; 1068 + 911 1069 deleteCode(code, code->cursor.position, code->cursor.position + 1); 912 1070 history(code); 913 1071 parseSyntaxColor(code); ··· 921 1079 if(!replaceSelection(code) && code->cursor.position > code->src) 922 1080 { 923 1081 char* pos = --code->cursor.position; 1082 + 1083 + if (structuredDeleteOverride(code, pos)) 1084 + return; 1085 + 924 1086 deleteCode(code, pos, pos + 1); 925 1087 history(code); 926 1088 parseSyntaxColor(code); ··· 936 1098 937 1099 if(pos < end) 938 1100 { 939 - if(isalnum_(*pos)) while(pos < end && isalnum_(*pos)) pos++; 940 - else while(pos < end && !isalnum_(*pos)) pos++; 1101 + if(isalnum_(code, *pos)) while(pos < end && isalnum_(code, *pos)) pos++; 1102 + else while(pos < end && !isalnum_(code, *pos)) pos++; 941 1103 942 1104 deleteCode(code, code->cursor.position, pos); 943 1105 ··· 953 1115 954 1116 if(pos > start) 955 1117 { 956 - if(isalnum_(*pos)) while(pos > start && isalnum_(*(pos-1))) pos--; 957 - else while(pos > start && !isalnum_(*(pos-1))) pos--; 1118 + if(isalnum_(code, *pos)) while(pos > start && isalnum_(code, *(pos-1))) pos--; 1119 + else while(pos > start && !isalnum_(code, *(pos-1))) pos--; 958 1120 959 1121 deleteCode(code, pos, code->cursor.position); 960 1122 ··· 964 1126 } 965 1127 } 966 1128 1129 + char* findLineEnd(Code* code, char* pos) 1130 + { 1131 + const bool useStructuredEdit = tic_core_script_config(code->tic)->useStructuredEdition; 1132 + 1133 + char* lineend = pos+1; 1134 + const char* end = code->src + strlen(code->src); 1135 + 1136 + while (lineend < end) 1137 + { 1138 + if (islineend(*lineend)) break; 1139 + if (useStructuredEdit && iscloseparen_(code, *lineend)) break; 1140 + ++lineend; 1141 + } 1142 + return lineend; 1143 + } 967 1144 1145 + static void deleteLine(Code* code) 1146 + { 1147 + char* linestart = code->cursor.position; 1148 + char* lineend = linestart+1; 1149 + const char* end = code->src + strlen(code->src); 1150 + 1151 + const bool useStructuredEdit = tic_core_script_config(code->tic)->useStructuredEdition; 1152 + if (useStructuredEdit) 1153 + { 1154 + if (islineend(*linestart) || islineend(*lineend)) 1155 + noop; 1156 + else if (isopenparen_(code, *linestart)) 1157 + lineend = rightSexp(code, linestart+1)+1; 1158 + else if (isdoublequote(*linestart)) 1159 + { 1160 + char* nextQuote = rightQuote(code, linestart+1); 1161 + lineend = findLineEnd(code, linestart); 1162 + if (nextQuote < lineend) 1163 + lineend = nextQuote+1; 1164 + else 1165 + return; // skip deletion if on last " of a string 1166 + } 1167 + else 1168 + { 1169 + const char* start = code->src; 1170 + const char* sexpStart = leftSexp(code, linestart); 1171 + const char* strStart = leftQuote(code, linestart); 1172 + char* sexpEnd = rightSexp(code, linestart); 1173 + char* strEnd = rightQuote(code, linestart); 1174 + const bool isInsideStr = strStart > sexpStart && strEnd < sexpEnd; 1175 + if (isInsideStr) 1176 + lineend = strEnd; 1177 + else if (sexpStart != start) 1178 + lineend = sexpEnd; 1179 + else 1180 + lineend = findLineEnd(code, linestart); 1181 + } 1182 + } 1183 + else 1184 + { 1185 + lineend = findLineEnd(code, linestart); 1186 + } 1187 + 1188 + deleteCode(code, linestart, lineend); 1189 + code->cursor.position = linestart; 1190 + history(code); 1191 + parseSyntaxColor(code); 1192 + updateEditor(code); 1193 + } 968 1194 969 1195 static void inputSymbolBase(Code* code, char sym) 970 1196 { 971 1197 if (strlen(code->src) >= MAX_CODE) 972 1198 return; 973 - if(getConfig(code->studio)->theme.code.autoDelimiters && (sym == '(' || sym == '[' || sym == '{')) 1199 + 1200 + const bool useStructuredEdit = tic_core_script_config(code->tic)->useStructuredEdition; 1201 + if((useStructuredEdit || getConfig(code->studio)->theme.code.autoDelimiters) && (sym == '(' || sym == '[' || sym == '{')) 974 1202 { 975 1203 insertCode(code, code->cursor.position++, (const char[]){sym, matchingDelim(sym), '\0'}); 976 - } else { 1204 + } else if (useStructuredEdit && isdoublequote(sym)) { 1205 + insertCode(code, code->cursor.position++, (const char[]){sym, sym, '\0'}); 1206 + } 1207 + else { 977 1208 insertCode(code, code->cursor.position++, (const char[]){sym, '\0'}); 978 1209 } 979 1210 980 1211 history(code); 981 - 982 1212 updateColumn(code); 983 - 984 1213 parseSyntaxColor(code); 985 1214 } 986 1215 ··· 1016 1245 static void selectAll(Code* code) 1017 1246 { 1018 1247 code->cursor.selection = code->src; 1019 - code->cursor.position = code->cursor.selection + strlen(code->cursor.selection); 1248 + code->cursor.position = code->cursor.selection + strlen(code->cursor.selection); 1020 1249 } 1021 1250 1022 - static void copyToClipboard(Code* code) 1251 + static void killSelection(Code* code) 1252 + { 1253 + code->cursor.selection = NULL; 1254 + } 1255 + 1256 + static void copyToClipboard(Code* code, bool killSelection) 1023 1257 { 1024 1258 char* pos = code->cursor.position; 1025 1259 char* sel = code->cursor.selection; ··· 1047 1281 tic_sys_clipboard_set(clipboard); 1048 1282 free(clipboard); 1049 1283 } 1284 + 1285 + if (killSelection) 1286 + code->cursor.selection = NULL; 1050 1287 } 1051 1288 1052 - static void cutToClipboard(Code* code) 1289 + static void cutToClipboard(Code* code, bool killSelection) 1053 1290 { 1054 1291 if(code->cursor.selection == NULL || code->cursor.position == code->cursor.selection) 1055 1292 { 1056 1293 code->cursor.position = getLine(code); 1057 1294 code->cursor.selection = getNextLine(code); 1058 1295 } 1059 - 1060 - copyToClipboard(code); 1296 + 1297 + copyToClipboard(code, false); 1061 1298 replaceSelection(code); 1299 + 1300 + if (killSelection) 1301 + code->cursor.selection = NULL; 1302 + 1062 1303 history(code); 1063 1304 } 1064 1305 1065 - static void copyFromClipboard(Code* code) 1306 + static void copyFromClipboard(Code* code, bool killSelection) 1066 1307 { 1067 1308 if(tic_sys_clipboard_has()) 1068 1309 { ··· 1089 1330 } 1090 1331 1091 1332 insertCode(code, code->cursor.position, clipboard); 1333 + code->cursor.position += size; 1092 1334 1093 - code->cursor.position += size; 1335 + if (killSelection) 1336 + code->cursor.selection = NULL; 1094 1337 1095 1338 history(code); 1096 - 1097 1339 parseSyntaxColor(code); 1098 1340 } 1099 1341 ··· 1243 1485 } 1244 1486 } 1245 1487 1246 - static void centerScroll(Code* code) 1488 + static void recenterScroll(Code* code, bool emacsMode) 1247 1489 { 1248 1490 s32 col, line; 1249 1491 getCursorPosition(code, &col, &line); 1250 - code->scroll.x = col - CODE_EDITOR_WIDTH / getFontWidth(code) / 2; 1251 - code->scroll.y = line - TEXT_BUFFER_HEIGHT / 2; 1492 + 1493 + s32 desiredCol = col - CODE_EDITOR_WIDTH / getFontWidth(code) / 2; 1494 + s32 desiredLine = MAX(0, line - TEXT_BUFFER_HEIGHT / 2); 1495 + 1496 + if (emacsMode && code->scroll.y == desiredLine) 1497 + { 1498 + desiredLine = line; 1499 + } 1500 + else if (emacsMode && code->scroll.y == line) 1501 + { 1502 + desiredLine = line - TEXT_BUFFER_HEIGHT; 1503 + } 1504 + 1505 + code->scroll.x = desiredCol; 1506 + code->scroll.y = desiredLine; 1252 1507 1253 1508 normalizeScroll(code); 1509 + } 1510 + 1511 + static void centerScroll(Code* code) 1512 + { 1513 + static const bool emacsMode = false; 1514 + recenterScroll(code, emacsMode); 1254 1515 } 1255 1516 1256 1517 static void updateSidebarCode(Code* code) ··· 1483 1744 parseSyntaxColor(code); 1484 1745 } 1485 1746 1747 + static void sexpify(Code* code) 1748 + { 1749 + const bool useStructuredEdit = tic_core_script_config(code->tic)->useStructuredEdition; 1750 + if (!useStructuredEdit) 1751 + return; 1752 + 1753 + char* pos = code->cursor.position; 1754 + char* start = NULL; 1755 + char* end = NULL; 1756 + char* sel = code->cursor.selection; 1757 + if (sel) 1758 + { 1759 + start = sel<pos ? sel : pos; 1760 + end = sel<pos ? pos : sel; 1761 + } 1762 + else if (isopenparen_(code, *pos)) 1763 + { 1764 + start = pos; 1765 + end = rightSexp(code, pos+1); 1766 + } 1767 + else if (isalnum_(code, *pos)) 1768 + { 1769 + start = leftPos(code, pos, isalnum_); 1770 + end = rightPos(code, pos, isalnum_); 1771 + } 1772 + 1773 + if (start == NULL || end == NULL) 1774 + return; 1775 + 1776 + insertCode(code, start, (const char[]){'(', '\0'}); 1777 + insertCode(code, end+1, (const char[]){')', '\0'}); 1778 + 1779 + ++code->cursor.position; 1780 + history(code); 1781 + parseSyntaxColor(code); 1782 + updateEditor(code); 1783 + } 1784 + 1785 + static void extirpSExp(Code* code) 1786 + { 1787 + const bool useStructuredEdit = tic_core_script_config(code->tic)->useStructuredEdition; 1788 + if (!useStructuredEdit) 1789 + return; 1790 + 1791 + const char* start = code->src; 1792 + char* pos = code->cursor.position; 1793 + const char* end = code->src + strlen(code->src); 1794 + 1795 + char* sexp_start = NULL; 1796 + char* sexp_end = NULL; 1797 + 1798 + if(pos > start) 1799 + { 1800 + if (isopenparen_(code, *pos)) 1801 + { 1802 + sexp_start = pos; 1803 + sexp_end = rightSexp(code, pos+1); 1804 + } 1805 + if (iscloseparen_(code, *pos)) 1806 + { 1807 + sexp_start = leftSexp(code, pos-1); 1808 + sexp_end = pos; 1809 + } 1810 + if (isalnum_(code, *pos)) 1811 + { 1812 + sexp_start = leftPos(code, pos, isalnum_); 1813 + sexp_end = rightPos(code, pos, isalnum_)-1; 1814 + } 1815 + if (sexp_start != NULL && sexp_end != NULL) 1816 + { 1817 + char* sexp_outer_start = leftSexp(code, sexp_start-1); 1818 + char* sexp_outer_end = rightSexp(code, sexp_end+1); 1819 + if (sexp_start > start && sexp_outer_start > start && sexp_end < end && sexp_outer_end < end) 1820 + { 1821 + deleteCode(code, sexp_end+1, sexp_outer_end+1); 1822 + deleteCode(code, sexp_outer_start, sexp_start); 1823 + code->cursor.position = sexp_outer_start; 1824 + history(code); 1825 + parseSyntaxColor(code); 1826 + updateEditor(code); 1827 + } 1828 + } 1829 + } 1830 + } 1831 + 1832 + static void toggleMark(Code* code) 1833 + { 1834 + if (code->cursor.selection) 1835 + code->cursor.selection = NULL; 1836 + else 1837 + code->cursor.selection = code->cursor.position; 1838 + 1839 + parseSyntaxColor(code); 1840 + updateEditor(code); 1841 + } 1842 + 1486 1843 static void commentLine(Code* code) 1487 1844 { 1488 1845 const char* comment = tic_core_script_config(code->tic)->singleComment; ··· 1579 1936 1580 1937 if(tic->ram->input.keyboard.data == 0) return; 1581 1938 1582 - bool usedClipboard = true; 1939 + const bool emacsMode = getConfig(code->studio)->options.emacsMode; 1583 1940 1584 - switch(getClipboardEvent(code->studio)) 1941 + if (!emacsMode) 1585 1942 { 1586 - case TIC_CLIPBOARD_CUT: cutToClipboard(code); break; 1587 - case TIC_CLIPBOARD_COPY: copyToClipboard(code); break; 1588 - case TIC_CLIPBOARD_PASTE: copyFromClipboard(code); break; 1589 - default: usedClipboard = false; break; 1590 - } 1943 + bool usedClipboard = true; 1944 + switch(getClipboardEvent(code->studio)) 1945 + { 1946 + case TIC_CLIPBOARD_CUT: cutToClipboard(code, false); break; 1947 + case TIC_CLIPBOARD_COPY: copyToClipboard(code, false); break; 1948 + case TIC_CLIPBOARD_PASTE: copyFromClipboard(code, false); break; 1949 + default: usedClipboard = false; break; 1950 + } 1591 1951 1592 - if(usedClipboard) 1593 - { 1594 - updateEditor(code); 1595 - return; 1952 + if(usedClipboard) 1953 + { 1954 + updateEditor(code); 1955 + return; 1956 + } 1596 1957 } 1597 1958 1598 1959 bool shift = tic_api_key(tic, tic_key_shift); ··· 1609 1970 || keyWasPressed(code->studio, tic_key_pageup) 1610 1971 || keyWasPressed(code->studio, tic_key_pagedown)) 1611 1972 { 1973 + changedSelection = true; 1612 1974 if(!shift) code->cursor.selection = NULL; 1613 1975 else if(code->cursor.selection == NULL) code->cursor.selection = code->cursor.position; 1614 - changedSelection = true; 1976 + else changedSelection = false; 1615 1977 } 1616 1978 1617 1979 bool usedKeybinding = true; ··· 1641 2003 } 1642 2004 else if(ctrl || alt) 1643 2005 { 1644 - bool ctrlHandled = true; 2006 + bool ctrlHandled = false; 1645 2007 if(ctrl) 1646 2008 { 2009 + ctrlHandled = true; 1647 2010 if(keyWasPressed(code->studio, tic_key_tab)) doTab(code, shift, ctrl); 1648 - else if(keyWasPressed(code->studio, tic_key_a)) selectAll(code); 2011 + else if(keyWasPressed(code->studio, tic_key_a)) emacsMode ? startLine(code) : selectAll(code); 1649 2012 else if(keyWasPressed(code->studio, tic_key_z)) undo(code); 1650 - else if(keyWasPressed(code->studio, tic_key_y)) redo(code); 1651 - else if(keyWasPressed(code->studio, tic_key_f)) setCodeMode(code, TEXT_FIND_MODE); 1652 - else if(keyWasPressed(code->studio, tic_key_g)) setCodeMode(code, TEXT_GOTO_MODE); 1653 - else if(keyWasPressed(code->studio, tic_key_b)) setCodeMode(code, TEXT_BOOKMARK_MODE); 2013 + else if(keyWasPressed(code->studio, tic_key_y)) emacsMode ? copyFromClipboard(code, true) : redo(code); 2014 + else if(keyWasPressed(code->studio, tic_key_w)) emacsMode ? cutToClipboard(code, true) : noop; 2015 + else if(keyWasPressed(code->studio, tic_key_f)) emacsMode ? rightColumn(code) : setCodeMode(code, TEXT_FIND_MODE); 2016 + else if(keyWasPressed(code->studio, tic_key_g)) emacsMode ? killSelection(code) : setCodeMode(code, TEXT_GOTO_MODE); 2017 + else if(keyWasPressed(code->studio, tic_key_b)) emacsMode ? leftColumn(code) : setCodeMode(code, TEXT_BOOKMARK_MODE); 1654 2018 else if(keyWasPressed(code->studio, tic_key_o)) setCodeMode(code, TEXT_OUTLINE_MODE); 1655 2019 else if(keyWasPressed(code->studio, tic_key_n)) downLine(code); 1656 2020 else if(keyWasPressed(code->studio, tic_key_p)) upLine(code); 1657 2021 else if(keyWasPressed(code->studio, tic_key_e)) endLine(code); 1658 - else if(keyWasPressed(code->studio, tic_key_d)) dupLine(code); 1659 - else if(keyWasPressed(code->studio, tic_key_slash)) commentLine(code); 2022 + else if(keyWasPressed(code->studio, tic_key_d)) emacsMode ? deleteChar(code) : dupLine(code); 2023 + else if(keyWasPressed(code->studio, tic_key_j)) newLine(code); 2024 + else if(keyWasPressed(code->studio, tic_key_slash)) emacsMode ? undo(code) : commentLine(code); 1660 2025 else if(keyWasPressed(code->studio, tic_key_home)) goCodeHome(code); 1661 2026 else if(keyWasPressed(code->studio, tic_key_end)) goCodeEnd(code); 2027 + else if(keyWasPressed(code->studio, tic_key_up)) extirpSExp(code); 2028 + else if(keyWasPressed(code->studio, tic_key_down)) sexpify(code); 2029 + else if(keyWasPressed(code->studio, tic_key_k)) deleteLine(code); 2030 + else if(keyWasPressed(code->studio, tic_key_space)) emacsMode ? toggleMark(code) : noop; 2031 + else if(keyWasPressed(code->studio, tic_key_l)) recenterScroll(code, emacsMode); 2032 + else if(keyWasPressed(code->studio, tic_key_v)) emacsMode ? pageDown(code) : noop; 1662 2033 else ctrlHandled = false; 1663 2034 } 1664 2035 2036 + bool altHandled = false; 2037 + if (alt) 2038 + { 2039 + char sym = getKeyboardText(code->studio); 2040 + altHandled = true; 2041 + if(keyWasPressed(code->studio, tic_key_p)) pageUp(code); 2042 + else if(keyWasPressed(code->studio, tic_key_n)) pageDown(code); 2043 + else if(keyWasPressed(code->studio, tic_key_v)) emacsMode ? pageUp(code) : noop; 2044 + else if(keyWasPressed(code->studio, tic_key_b)) leftWord(code); 2045 + else if(keyWasPressed(code->studio, tic_key_f)) rightWord(code); 2046 + else if(keyWasPressed(code->studio, tic_key_k)) emacsMode ? dupLine(code) : noop; 2047 + else if(keyWasPressed(code->studio, tic_key_d)) emacsMode ? deleteWord(code) : noop; 2048 + else if(keyWasPressed(code->studio, tic_key_r)) emacsMode ? extirpSExp(code) : noop; 2049 + else if(keyWasPressed(code->studio, tic_key_w)) emacsMode ? copyToClipboard(code, true) : noop; 2050 + else if(keyWasPressed(code->studio, tic_key_g)) emacsMode ? setCodeMode(code, TEXT_GOTO_MODE) : noop; 2051 + else if(keyWasPressed(code->studio, tic_key_s)) emacsMode ? setCodeMode(code, TEXT_FIND_MODE) : noop; 2052 + else if(shift && sym && sym == '(') emacsMode? sexpify(code) : noop; 2053 + else if(keyWasPressed(code->studio, tic_key_slash)) emacsMode ? redo(code) : noop; 2054 + else if(keyWasPressed(code->studio, tic_key_semicolon)) emacsMode ? commentLine(code) : noop; 2055 + else altHandled = false; 2056 + } 2057 + 1665 2058 bool ctrlAltHandled = true; 1666 2059 if(keyWasPressed(code->studio, tic_key_left)) leftWord(code); 1667 2060 else if(keyWasPressed(code->studio, tic_key_right)) rightWord(code); ··· 1669 2062 else if(keyWasPressed(code->studio, tic_key_backspace)) backspaceWord(code); 1670 2063 else ctrlAltHandled = false; 1671 2064 1672 - usedKeybinding = (ctrlHandled || ctrlAltHandled); 2065 + usedKeybinding = (ctrlHandled || altHandled || ctrlAltHandled); 1673 2066 } 1674 2067 else 1675 2068 { ··· 2327 2720 { 2328 2721 switch(event) 2329 2722 { 2330 - case TIC_TOOLBAR_CUT: cutToClipboard(code); break; 2331 - case TIC_TOOLBAR_COPY: copyToClipboard(code); break; 2332 - case TIC_TOOLBAR_PASTE: copyFromClipboard(code); break; 2723 + case TIC_TOOLBAR_CUT: cutToClipboard(code, false); break; 2724 + case TIC_TOOLBAR_COPY: copyToClipboard(code, false); break; 2725 + case TIC_TOOLBAR_PASTE: copyFromClipboard(code, false); break; 2333 2726 case TIC_TOOLBAR_UNDO: undo(code); break; 2334 2727 case TIC_TOOLBAR_REDO: redo(code); break; 2335 2728 }
+54 -2
src/studio/screens/mainmenu.c
··· 194 194 optionVolumeSet, 195 195 }; 196 196 197 + 198 + 199 + 200 + static s32 optionEmacsModeGet(void* data) 201 + { 202 + #if defined(BUILD_EDITORS) 203 + StudioMainMenu* main = data; 204 + return main->options->emacsMode ? 1 : 0; 205 + #endif 206 + } 207 + 208 + static void optionEmacsModeSet(void* data, s32 pos) 209 + { 210 + #if defined(BUILD_EDITORS) 211 + StudioMainMenu* main = data; 212 + main->options->emacsMode = pos == 1; 213 + #endif 214 + } 215 + 216 + static MenuOption EmacsModeOption = 217 + { 218 + OPTION_VALUES({OffValue, OnValue}), 219 + optionEmacsModeGet, 220 + optionEmacsModeSet, 221 + }; 222 + 197 223 #if defined(BUILD_EDITORS) 198 224 199 225 static s32 optionDevModeGet(void* data) ··· 226 252 initGamepadMenu(main); 227 253 } 228 254 255 + static void showCodeEditorMenu(void* data, s32 pos); 256 + 229 257 static const MenuItem OptionMenu[] = 230 258 { 231 259 #if defined(CRT_SHADER_SUPPORT) ··· 238 266 {"FULLSCREEN", NULL, &FullscreenOption}, 239 267 {"INTEGER SCALE", NULL, &IntegerScaleOption}, 240 268 {"VOLUME", NULL, &VolumeOption}, 241 - {"SETUP GAMEPAD", showGamepadMenu}, 269 + {"SETUP GAMEPAD", showGamepadMenu}, 270 + #if defined(BUILD_EDITORS) 271 + {"CODE EDITOR OPTIONS", showCodeEditorMenu}, 272 + #endif 242 273 {""}, 243 274 {"BACK", showMainMenu, .back = true}, 244 275 }; ··· 250 281 tic_mem* tic = main->tic; 251 282 resumeGame(main->studio); 252 283 tic_core_script_config(tic)->callback.menu(tic, pos, NULL); 284 + } 285 + 286 + static const MenuItem CodeEditorMenu[] = 287 + { 288 + {"EMACS MODE", NULL, &EmacsModeOption, "For the cool kids only"}, 289 + {""}, 290 + {"BACK", showOptionsMenu, .back = true}, 291 + }; 292 + 293 + static void showCodeEditorMenu(void* data, s32 pos) 294 + { 295 + StudioMainMenu* main = data; 296 + 297 + studio_menu_init(main->menu, CodeEditorMenu, 298 + COUNT_OF(CodeEditorMenu), 0, COUNT_OF(OptionMenu)-3, showOptionsMenu, main); 253 299 } 254 300 255 301 static void freeItems(StudioMainMenu* menu) ··· 494 540 495 541 initGamepadButtons(main); 496 542 543 + #if defined(BUILD_EDITORS) 544 + s32 backPos = COUNT_OF(OptionMenu) - 4; 545 + #else 546 + s32 backPos = COUNT_OF(OptionMenu) - 3; 547 + #endif 548 + 497 549 studio_menu_init(main->menu, GamepadMenu, COUNT_OF(GamepadMenu), 498 550 main->gamepads.key < 0 ? KeyMappingStart : main->gamepads.key + KeyMappingStart, 499 - COUNT_OF(OptionMenu) - 3, showOptionsMenu, main); 551 + backPos, showOptionsMenu, main); 500 552 501 553 main->gamepads.key = -1; 502 554 }
+1 -1
src/studio/system.h
··· 129 129 bool integerScale; 130 130 s32 volume; 131 131 tic_mapping mapping; 132 - 133 132 #if defined(BUILD_EDITORS) 133 + bool emacsMode; 134 134 bool devmode; 135 135 #endif 136 136 } options;