this repo has no description
0
fork

Configure Feed

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

Merge pull request #1840 from blinry/autocomplete-console

[WIP] Advanced tab-completion for the console

authored by

Vadim Grigoruk and committed by
GitHub
831e04ad e06a577b

+315 -93
+315 -93
src/studio/screens/console.c
··· 1144 1144 } 1145 1145 } 1146 1146 1147 + static void insertInputText(Console* console, const char* text) 1148 + { 1149 + s32 size = strlen(text); 1150 + s32 offset = getInputOffset(console); 1151 + 1152 + if(size < CONSOLE_BUFFER_SIZE - offset) 1153 + { 1154 + char* pos = console->text + offset; 1155 + u8* color = console->color + offset; 1156 + 1157 + { 1158 + s32 len = strlen(pos); 1159 + memmove(pos + size, pos, len); 1160 + memmove(color + size, color, len); 1161 + } 1162 + 1163 + memcpy(pos, text, size); 1164 + memset(color, CONSOLE_INPUT_COLOR, size); 1165 + 1166 + console->input.pos += size; 1167 + } 1168 + 1169 + clearSelection(console); 1170 + } 1171 + 1172 + typedef struct 1173 + { 1174 + Console* console; 1175 + char* incompleteWord; // Original word that's being completed. 1176 + char* options; // Options to show to the user. 1177 + char* commonPrefix; // Common prefix of all options. 1178 + } TabCompleteData; 1179 + 1180 + static void addTabCompleteOption(TabCompleteData* data, const char* option) 1181 + { 1182 + if (strstr(option, data->incompleteWord) == option) 1183 + { 1184 + // Possibly reduce the common prefix of all possible options. 1185 + if (strlen(data->options) == 0) 1186 + { 1187 + // This is the first option to be added. Initialize the prefix. 1188 + strncpy(data->commonPrefix, option, CONSOLE_BUFFER_SCREEN); 1189 + } 1190 + else 1191 + { 1192 + // Only leave the longest common prefix. 1193 + char* tmpCommonPrefix = data->commonPrefix; 1194 + char* tmpOption = (char*) option; 1195 + 1196 + while (*tmpCommonPrefix && *tmpOption && *tmpCommonPrefix == *tmpOption) { 1197 + tmpCommonPrefix++; 1198 + tmpOption++; 1199 + } 1200 + 1201 + *tmpCommonPrefix = 0; 1202 + } 1203 + 1204 + // The option matches the incomplete word, add it to the list. 1205 + strncat(data->options, option, CONSOLE_BUFFER_SCREEN); 1206 + strncat(data->options, " ", CONSOLE_BUFFER_SCREEN); 1207 + } 1208 + } 1209 + 1210 + // Used to show tab-complete options, for example. 1211 + static void provideHint(Console* console, const char* hint) 1212 + { 1213 + char* input = malloc(CONSOLE_BUFFER_SCREEN); 1214 + strncpy(input, console->input.text, CONSOLE_BUFFER_SCREEN); 1215 + 1216 + printLine(console); 1217 + printBack(console, hint); 1218 + commandDone(console); 1219 + insertInputText(console, input); 1220 + 1221 + free(input); 1222 + } 1223 + 1224 + static void finishTabComplete(const TabCompleteData* data) 1225 + { 1226 + bool anyOptions = strlen(data->options) > 0; 1227 + if (anyOptions) { 1228 + // Adding one at the right because all options end with a space. 1229 + bool justOneOptionLeft = strlen(data->options) == strlen(data->commonPrefix)+1; 1230 + 1231 + if (strlen(data->commonPrefix) == strlen(data->incompleteWord) && !justOneOptionLeft) 1232 + { 1233 + provideHint(data->console, data->options); 1234 + } 1235 + processConsoleEnd(data->console); 1236 + insertInputText(data->console, data->commonPrefix+strlen(data->incompleteWord)); 1237 + 1238 + if (justOneOptionLeft) 1239 + { 1240 + insertInputText(data->console, " "); 1241 + } 1242 + } 1243 + 1244 + free(data->options); 1245 + free(data->commonPrefix); 1246 + } 1247 + 1248 + static void tabCompleteLanguages(TabCompleteData* data) 1249 + { 1250 + FOR_EACH_LANG(ln) 1251 + addTabCompleteOption(data, ln->name); 1252 + FOR_EACH_LANG_END 1253 + finishTabComplete(data); 1254 + } 1255 + 1256 + static void tabCompleteExport(TabCompleteData* data) 1257 + { 1258 + #define EXPORT_CMD_DEF(name) addTabCompleteOption(data, #name); 1259 + EXPORT_CMD_LIST(EXPORT_CMD_DEF) 1260 + #undef EXPORT_CMD_DEF 1261 + finishTabComplete(data); 1262 + } 1263 + 1264 + static void tabCompleteImport(TabCompleteData* data) 1265 + { 1266 + #define IMPORT_CMD_DEF(name) addTabCompleteOption(data, #name); 1267 + IMPORT_CMD_LIST(IMPORT_CMD_DEF) 1268 + #undef IMPORT_CMD_DEF 1269 + finishTabComplete(data); 1270 + } 1271 + 1272 + static bool addFileAndDirToTabComplete(const char* name, const char* title, const char* hash, s32 id, void* data, bool dir) 1273 + { 1274 + addTabCompleteOption(data, name); 1275 + 1276 + return true; 1277 + } 1278 + 1279 + static bool addFilenameToTabComplete(const char* name, const char* title, const char* hash, s32 id, void* data, bool dir) 1280 + { 1281 + if (!dir) 1282 + addTabCompleteOption(data, name); 1283 + 1284 + return true; 1285 + } 1286 + 1287 + static bool addDirToTabComplete(const char* name, const char* title, const char* hash, s32 id, void* data, bool dir) 1288 + { 1289 + if (dir) 1290 + addTabCompleteOption(data, name); 1291 + 1292 + return true; 1293 + } 1294 + 1295 + static void finishTabCompleteAndFreeData(void* data) { 1296 + finishTabComplete((const TabCompleteData *) data); 1297 + free(data); 1298 + } 1299 + 1300 + static void tabCompleteFiles(TabCompleteData* data) 1301 + { 1302 + tic_fs_enum(data->console->fs, addFilenameToTabComplete, finishTabCompleteAndFreeData, MOVE(*data)); 1303 + } 1304 + 1305 + static void tabCompleteDirs(TabCompleteData* data) 1306 + { 1307 + tic_fs_enum(data->console->fs, addDirToTabComplete, finishTabCompleteAndFreeData, MOVE(*data)); 1308 + } 1309 + 1310 + static void tabCompleteFilesAndDirs(TabCompleteData* data) 1311 + { 1312 + tic_fs_enum(data->console->fs, addFileAndDirToTabComplete, finishTabCompleteAndFreeData, MOVE(*data)); 1313 + } 1314 + 1315 + static void tabCompleteConfig(TabCompleteData* data) 1316 + { 1317 + addTabCompleteOption(data, "reset"); 1318 + addTabCompleteOption(data, "default"); 1319 + finishTabComplete(data); 1320 + } 1321 + 1147 1322 typedef struct 1148 1323 { 1149 1324 const char* name; ··· 2546 2721 2547 2722 #endif 2548 2723 2724 + // Declare this here to resolve a cyclic dependency with COMMANDS_LIST. 2725 + static void tabCompleteHelp(TabCompleteData* data); 2726 + 2549 2727 static const char HelpUsage[] = "help [<text>" 2550 2728 #define HELP_CMD_DEF(name) "|" #name 2551 2729 HELP_CMD_LIST(HELP_CMD_DEF) ··· 2564 2742 NULL, \ 2565 2743 "upload file to the browser local storage.", \ 2566 2744 NULL, \ 2567 - onAddCommand) \ 2745 + onAddCommand, \ 2746 + NULL, \ 2747 + NULL) \ 2568 2748 \ 2569 2749 macro("get", \ 2570 2750 NULL, \ 2571 2751 "download file from the browser local storage.", \ 2572 2752 "get <file>", \ 2573 - onGetCommand) \ 2753 + onGetCommand, \ 2754 + tabCompleteFiles, \ 2755 + NULL) \ 2574 2756 2575 2757 #else 2576 2758 #define ADDGET_FILE(macro) 2577 2759 #endif 2578 2760 2579 - // macro(name, alt, help, usage, handler) 2761 + // macro(name, alt, help, usage, handler, tab-complete for first param, for second param) 2580 2762 #define COMMANDS_LIST(macro) \ 2581 2763 macro("help", \ 2582 2764 NULL, \ 2583 2765 "show help info about commands/api/...", \ 2584 2766 HelpUsage, \ 2585 - onHelpCommand) \ 2767 + onHelpCommand, \ 2768 + tabCompleteHelp, \ 2769 + NULL) \ 2586 2770 \ 2587 2771 macro("exit", \ 2588 2772 "quit", \ 2589 2773 "exit the application.", \ 2590 2774 NULL, \ 2591 - onExitCommand) \ 2775 + onExitCommand, \ 2776 + NULL, \ 2777 + NULL) \ 2592 2778 \ 2593 2779 macro("edit", \ 2594 2780 NULL, \ ··· 2600 2786 NULL, \ 2601 2787 "creates a new `Hello World` cartridge.", \ 2602 2788 "new <$LANG_NAMES_PIPE$>", \ 2603 - onNewCommand) \ 2789 + onNewCommand, \ 2790 + tabCompleteLanguages, \ 2791 + NULL) \ 2604 2792 \ 2605 2793 macro("load", \ 2606 2794 NULL, \ ··· 2608 2796 "(there's no need to type the .tic extension).\n" \ 2609 2797 "you can also load just the section (sprites, map etc) from another cart.", \ 2610 2798 "load <cart> [code" TIC_SYNC_LIST(SECTION_DEF) "]", \ 2611 - onLoadCommand) \ 2799 + onLoadCommand, \ 2800 + tabCompleteFiles, \ 2801 + NULL) \ 2612 2802 \ 2613 2803 macro("save", \ 2614 2804 NULL, \ 2615 2805 "save cartridge to the local filesystem, use $LANG_EXTENSIONS$" \ 2616 2806 "cart extension to save it in text format (PRO feature).", \ 2617 2807 "save <cart>", \ 2618 - onSaveCommand) \ 2808 + onSaveCommand, \ 2809 + tabCompleteFiles, \ 2810 + NULL) \ 2619 2811 \ 2620 2812 macro("run", \ 2621 2813 NULL, \ 2622 2814 "run current cart / project.", \ 2623 2815 NULL, \ 2624 - onRunCommand) \ 2816 + onRunCommand, \ 2817 + NULL, \ 2818 + NULL) \ 2625 2819 \ 2626 2820 macro("resume", \ 2627 2821 NULL, \ 2628 2822 "resume last run cart / project.", \ 2629 2823 NULL, \ 2630 - onResumeCommand) \ 2824 + onResumeCommand, \ 2825 + NULL, \ 2826 + NULL) \ 2631 2827 \ 2632 2828 macro("eval", \ 2633 2829 "=", \ 2634 2830 "run code provided code.", \ 2635 2831 NULL, \ 2636 - onEvalCommand) \ 2832 + onEvalCommand, \ 2833 + NULL, \ 2834 + NULL) \ 2637 2835 \ 2638 2836 macro("dir", \ 2639 2837 "ls", \ 2640 2838 "show list of local files.", \ 2641 2839 NULL, \ 2642 - onDirCommand) \ 2840 + onDirCommand, \ 2841 + NULL, \ 2842 + NULL) \ 2643 2843 \ 2644 2844 macro("cd", \ 2645 2845 NULL, \ 2646 2846 "change directory.", \ 2647 2847 "\ncd <path>\ncd /\ncd ..", \ 2648 - onChangeDirectory) \ 2848 + onChangeDirectory, \ 2849 + tabCompleteDirs, \ 2850 + NULL) \ 2649 2851 \ 2650 2852 macro("mkdir", \ 2651 2853 NULL, \ 2652 2854 "make a directory.", \ 2653 2855 "mkdir <name>", \ 2654 - onMakeDirectory) \ 2856 + onMakeDirectory, \ 2857 + NULL, \ 2858 + NULL) \ 2655 2859 \ 2656 2860 macro("folder", \ 2657 2861 NULL, \ 2658 2862 "open working directory in OS.", \ 2659 2863 NULL, \ 2660 - onFolderCommand) \ 2864 + onFolderCommand, \ 2865 + NULL, \ 2866 + NULL) \ 2661 2867 \ 2662 2868 macro("export", \ 2663 2869 NULL, \ ··· 2665 2871 "native build (win linux rpi mac),\n" \ 2666 2872 "export sprites/map/... as a .png image " \ 2667 2873 "or export sfx and music to .wav files.", \ 2668 - "\nexport [" EXPORT_CMD_LIST(EXPORT_CMD_DEF) "...]" \ 2874 + "\nexport [" EXPORT_CMD_LIST(EXPORT_CMD_DEF) "...] " \ 2669 2875 "<file> [" EXPORT_KEYS_LIST(EXPORT_KEYS_DEF) "...]" , \ 2670 - onExportCommand) \ 2876 + onExportCommand, \ 2877 + tabCompleteExport, \ 2878 + tabCompleteFiles) \ 2671 2879 \ 2672 2880 macro("import", \ 2673 2881 NULL, \ 2674 2882 "import code/sprites/map/... from an external file.", \ 2675 - "\nimport [" IMPORT_CMD_LIST(IMPORT_CMD_DEF) "...]" \ 2883 + "\nimport [" IMPORT_CMD_LIST(IMPORT_CMD_DEF) "...] " \ 2676 2884 "<file> [" IMPORT_KEYS_LIST(IMPORT_KEYS_DEF) "...]", \ 2677 - onImportCommand) \ 2885 + onImportCommand, \ 2886 + tabCompleteImport, \ 2887 + tabCompleteFiles) \ 2678 2888 \ 2679 2889 macro("del", \ 2680 2890 NULL, \ 2681 2891 "delete from the filesystem.", \ 2682 2892 "del <file|folder>", \ 2683 - onDelCommand) \ 2893 + onDelCommand, \ 2894 + tabCompleteFilesAndDirs, \ 2895 + NULL) \ 2684 2896 \ 2685 2897 macro("cls", \ 2686 2898 "clear", \ 2687 2899 "clear console screen.", \ 2688 2900 NULL, \ 2689 - onClsCommand) \ 2901 + onClsCommand, \ 2902 + NULL, \ 2903 + NULL) \ 2690 2904 \ 2691 2905 macro("demo", \ 2692 2906 NULL, \ 2693 2907 "install demo carts to the current directory.", \ 2694 2908 NULL, \ 2695 - onInstallDemosCommand) \ 2909 + onInstallDemosCommand, \ 2910 + NULL, \ 2911 + NULL) \ 2696 2912 \ 2697 2913 macro("config", \ 2698 2914 NULL, \ ··· 2700 2916 "use `reset` param to reset current configuration,\n" \ 2701 2917 "use `default` to edit default cart template.", \ 2702 2918 "config [reset|default]", \ 2703 - onConfigCommand) \ 2919 + onConfigCommand, \ 2920 + tabCompleteConfig, \ 2921 + NULL) \ 2704 2922 \ 2705 2923 macro("surf", \ 2706 2924 NULL, \ 2707 2925 "open carts browser.", \ 2708 2926 NULL, \ 2709 - onSurfCommand) \ 2927 + onSurfCommand, \ 2928 + NULL, \ 2929 + NULL) \ 2710 2930 \ 2711 2931 macro("menu", \ 2712 2932 NULL, \ 2713 2933 "show game menu where you can setup video, sound and input options.", \ 2714 2934 NULL, \ 2715 - onGameMenuCommand) \ 2935 + onGameMenuCommand, \ 2936 + NULL, \ 2937 + NULL) \ 2716 2938 ADDGET_FILE(macro) 2717 2939 2718 2940 static struct Command ··· 2722 2944 const char* help; 2723 2945 const char* usage; 2724 2946 void(*handler)(Console*); 2947 + void(*tabComplete1)(TabCompleteData*); 2948 + void(*tabComplete2)(TabCompleteData*); 2725 2949 2726 2950 } Commands[] = 2727 2951 { 2728 - #define COMMANDS_DEF(name, alt, help, usage, handler) {name, alt, help, usage, handler}, 2952 + #define COMMANDS_DEF(name, alt, help, usage, handler, tabComplete1, tabComplete2) \ 2953 + {name, alt, help, usage, handler, tabComplete1, tabComplete2}, 2729 2954 COMMANDS_LIST(COMMANDS_DEF) 2730 2955 #undef COMMANDS_DEF 2731 2956 }; ··· 2751 2976 2752 2977 typedef struct ApiItem ApiItem; 2753 2978 2979 + static void tabCompleteHelp(TabCompleteData* data) 2980 + { 2981 + #define HELP_CMD_DEF(name) addTabCompleteOption(data, #name); 2982 + HELP_CMD_LIST(HELP_CMD_DEF) 2983 + #undef HELP_CMD_DEF 2984 + 2985 + for(s32 i = 0; i < COUNT_OF(Commands); i++) 2986 + { 2987 + addTabCompleteOption(data, Commands[i].name); 2988 + } 2989 + 2990 + #define TIC_API_DEF(name, def, help, ...) addTabCompleteOption(data, #name); 2991 + API_LIST(TIC_API_DEF) 2992 + #undef TIC_API_DEF 2993 + 2994 + finishTabComplete(data); 2995 + } 2996 + 2997 + 2754 2998 static s32 createRamTable(char* buf) 2755 2999 { 2756 3000 char* ptr = buf; ··· 2999 3243 } 3000 3244 } 3001 3245 3002 - typedef struct 3003 - { 3004 - Console* console; 3005 - char* name; 3006 - } PredictFilenameData; 3007 - 3008 - static bool predictFilename(const char* name, const char* title, const char* hash, s32 id, void* data, bool dir) 3009 - { 3010 - PredictFilenameData* predictFilenameData = data; 3011 - Console* console = predictFilenameData->console; 3012 - 3013 - if(strstr(name, predictFilenameData->name) == name) 3014 - { 3015 - strcpy(predictFilenameData->name, name); 3016 - memset(console->color + getInputOffset(console), CONSOLE_INPUT_COLOR, strlen(name)); 3017 - return false; 3018 - } 3019 - 3020 - return true; 3021 - } 3022 - 3023 - static void predictFilenameDone(void* data) 3024 - { 3025 - PredictFilenameData* predictFilenameData = data; 3026 - Console* console = predictFilenameData->console; 3027 - 3028 - console->input.pos = strlen(console->input.text); 3029 - free(predictFilenameData); 3030 - } 3031 - 3032 - static void insertInputText(Console* console, const char* text) 3033 - { 3034 - s32 size = strlen(text); 3035 - s32 offset = getInputOffset(console); 3036 - 3037 - if(size < CONSOLE_BUFFER_SIZE - offset) 3038 - { 3039 - char* pos = console->text + offset; 3040 - u8* color = console->color + offset; 3041 - 3042 - { 3043 - s32 len = strlen(pos); 3044 - memmove(pos + size, pos, len); 3045 - memmove(color + size, color, len); 3046 - } 3047 - 3048 - memcpy(pos, text, size); 3049 - memset(color, CONSOLE_INPUT_COLOR, size); 3050 - 3051 - console->input.pos += size; 3052 - } 3246 + TabCompleteData newTabCompleteData(Console* console, char* incompleteWord) { 3247 + TabCompleteData data = { console, .incompleteWord = incompleteWord }; 3248 + data.options = malloc(CONSOLE_BUFFER_SCREEN); 3249 + data.commonPrefix = malloc(CONSOLE_BUFFER_SCREEN); 3250 + data.options[0] = '\0'; 3251 + data.commonPrefix[0] = '\0'; 3053 3252 3054 - clearSelection(console); 3253 + return data; 3055 3254 } 3056 3255 3057 3256 static void processConsoleTab(Console* console) 3058 3257 { 3059 3258 char* input = console->input.text; 3259 + char* param = strchr(input, ' '); 3060 3260 3061 - if(strlen(input)) 3261 + if(param) 3062 3262 { 3063 - char* param = strchr(input, ' '); 3263 + // Tab-complete command's parameters. 3264 + param++; 3265 + char* secondParam = strchr(param, ' '); 3266 + if (secondParam) 3267 + secondParam++; 3064 3268 3065 - if(param && strlen(++param)) 3066 - { 3067 - PredictFilenameData data = { console, param }; 3068 - tic_fs_enum(console->fs, predictFilename, predictFilenameDone, MOVE(data)); 3069 - } 3070 - else 3269 + for(s32 i = 0; i < COUNT_OF(Commands); i++) 3071 3270 { 3072 - for(s32 i = 0; i < COUNT_OF(Commands); i++) 3073 - { 3074 - const char* command = Commands[i].name; 3271 + s32 commandLen = param-input-1; 3272 + bool commandMatches = (strlen(Commands[i].name) == commandLen && 3273 + strncmp(Commands[i].name, input, commandLen) == 0) || 3274 + (Commands[i].alt && 3275 + strlen(Commands[i].name) == commandLen && 3276 + strncmp(Commands[i].alt, input, commandLen) == 0); 3075 3277 3076 - if(strstr(command, input) == command) 3077 - { 3078 - insertInputText(console, command + console->input.pos); 3079 - break; 3278 + if (commandMatches) 3279 + { 3280 + if (secondParam) { 3281 + if (Commands[i].tabComplete2) { 3282 + TabCompleteData data = newTabCompleteData(console, secondParam); 3283 + Commands[i].tabComplete2(&data); 3284 + } 3285 + } else { 3286 + if (Commands[i].tabComplete1) { 3287 + TabCompleteData data = newTabCompleteData(console, param); 3288 + Commands[i].tabComplete1(&data); 3289 + } 3080 3290 } 3081 3291 } 3082 3292 } 3293 + } 3294 + else 3295 + { 3296 + // Tab-complete commands. 3297 + TabCompleteData data = newTabCompleteData(console, input); 3298 + for(s32 i = 0; i < COUNT_OF(Commands); i++) 3299 + { 3300 + addTabCompleteOption(&data, Commands[i].name); 3301 + if (Commands[i].alt) 3302 + addTabCompleteOption(&data, Commands[i].alt); 3303 + } 3304 + finishTabComplete(&data); 3083 3305 } 3084 3306 } 3085 3307