diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..06826cb3 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,44 @@ +on: [push, workflow_dispatch] +name: Build Image +jobs: + build-image: + runs-on: ubuntu-20.04 + steps: + - name: Clone Repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + submodules: true + - name: Pull Builder Image + run: docker pull toaruos/build-tools:1.8.x + - name: Run Builder + run: docker run -v ${GITHUB_WORKSPACE}:/root/toaruos -w /root/toaruos -e LANG=C.UTF-8 -t toaruos/build-tools:1.8.x util/build-in-docker.sh + - name: Upload Branch Image + uses: actions/upload-artifact@v2 + with: + name: image + path: ./image.iso + - name: Draft Release notes + if: startsWith(github.ref, 'refs/tags/v') + run: bash util/generate-release-notes.sh > notes.md + - name: Create Release + if: startsWith(github.ref, 'refs/tags/v') + uses: actions/create-release@v1 + id: create_release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ToaruOS ${{ github.ref }} + body_path: ./notes.md + draft: true + - name: Upload Release Image + if: startsWith(github.ref, 'refs/tags/v') + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./image.iso + asset_name: image.iso + asset_content_type: application/x-iso9660-image diff --git a/.gitignore b/.gitignore index 85908872..38ef80e8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ /.make/* /base/bin/* +/base/lib/kuroko /base/src /_base /cdrom/extra @@ -33,7 +34,6 @@ /base/usr/bin/* /base/usr/lib/* /base/usr/share/python-demos -/base/usr/share/help # Generic /base/usr/share/aclocal @@ -72,3 +72,6 @@ # SDL /base/usr/include/SDL + +# Kuroko build files +/base/usr/share/kuroko diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..c8bf8f65 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "kuroko"] + path = kuroko + url = git@github.com:kuroko-lang/kuroko diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 10958c9b..00000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -language: c -script: - - docker run -v `pwd`:/root/toaruos -w /root/toaruos -e LANG=C.UTF-8 -t toaruos/build-tools:1.8.x util/build-travis.sh -sudo: required -dist: trusty -serivces: - - docker -before_install: - - docker pull toaruos/build-tools:1.8.x -notifications: - irc: - channels: - - secure: "YIH2do6BypI1ZiXONyldvW/xt8O2j/PDb3sPzGUwJql08pItH3t3MPN3JHtKEBU7+/yFB58HbA2C8B1gu/oAGs2tqMCFYiapVUYYv5CudvMt+XkBzxgKRFwPcvPtz+lAjVbIM0SXDdlrfjczDGKPnEvCYBIu+ZYlz8dgn5DEVX8=" - use_notice: false - skip_join: true -deploy: - provider: releases - api_key: - secure: Lcs5kHe9HAwcVLdm8c0FkAb77U9yzUQdI5PmTU1AnFyHtrPmW1+9d6HeYSPLHm+cPAUdWKqouJNKW7s76Sqhz+4IDcwro7VkbV/fd9NFQCOf9Jb5QScmCZndmYVQUiUmy/7wVqGdy+vatKEsnngctT7aVVhJ5SrWoCPmcttUez8= - file: image.iso - skip_cleanup: true - overwrite: true - on: - tags: true diff --git a/Makefile b/Makefile index 8c85e9ca..19230040 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ LC=base/lib/libc.so ## # APPS = C sources from apps/ # APPS_X = binaries -# APPS_Y = generated makefiles for binaries (except init) +# APPS_Y = generated makefiles for binaries # APPS_SH = shell scripts to copy to base/bin/ and mark executable # APPS_SH_X = destinations for shell scripts APPS=$(patsubst apps/%.c,%,$(wildcard apps/*.c)) @@ -46,6 +46,8 @@ APPS_X=$(foreach app,$(APPS),base/bin/$(app)) APPS_Y=$(foreach app,$(APPS),.make/$(app).mak) APPS_SH=$(patsubst apps/%.sh,%.sh,$(wildcard apps/*.sh)) APPS_SH_X=$(foreach app,$(APPS_SH),base/bin/$(app)) +APPS_KRK=$(patsubst apps/%.krk,%.krk,$(wildcard apps/*.krk)) +APPS_KRK_X=$(foreach app,$(APPS_KRK),base/bin/$(app)) ## # LIBS = C sources from lib/ @@ -58,12 +60,12 @@ LIBS_Y=$(foreach lib,$(LIBS),.make/$(lib).lmak) SOURCE_FILES = $(wildcard kernel/*.c kernel/*/*.c kernel/*/*/*.c modules/*.c) SOURCE_FILES += $(wildcard apps/*.c linker/*.c libc/*.c libc/*/*.c lib/*.c) -tags: $(SOURCE_FILES) - ctags -f tags $(SOURCE_FILES) +tags: $(SOURCE_FILES) $(wildcard kuroko/src/*.c kuroko/src/*.h) + ctags -f tags $(SOURCE_FILES) $(wildcard kuroko/src/*.c kuroko/src/*.h) ## # Files that must be present in the ramdisk (apps, libraries) -RAMDISK_FILES= ${APPS_X} ${APPS_SH_X} ${LIBS_X} base/lib/ld.so base/lib/libm.so +RAMDISK_FILES= ${APPS_X} ${APPS_SH_X} ${APPS_KRK_X} ${LIBS_X} base/lib/ld.so base/lib/libm.so ${KUROKO_FILES} # Kernel / module flags @@ -99,6 +101,7 @@ KERNEL_OBJS += $(patsubst %.c,%.o,$(wildcard kernel/*/*/*.c)) ## # Kernel objects from kernel/ assembly sources KERNEL_ASMOBJS = $(filter-out kernel/symbols.o,$(patsubst %.S,%.o,$(wildcard kernel/*.S))) +HEADERS = $(wildcard base/usr/include/kernel/*.h base/usr/include/kernel/*/*.h) # Kernel @@ -139,7 +142,6 @@ fatbase/mod: ## # Modules need to be installed on the boot image MODULES = $(patsubst modules/%.c,fatbase/mod/%.ko,$(wildcard modules/*.c)) -HEADERS = $(wildcard base/usr/include/kernel/*.h base/usr/include/kernel/*/*.h) fatbase/mod/%.ko: modules/%.c ${HEADERS} | fatbase/mod ${KCC} -nostdlib ${KCFLAGS} -c -o $@ $< @@ -162,13 +164,15 @@ base/cdrom: mkdir -p $@ base/var: mkdir -p $@ +base/lib/kuroko: + mkdir -p $@ fatbase/efi/boot: mkdir -p $@ cdrom: mkdir -p $@ .make: mkdir -p .make -dirs: base/dev base/tmp base/proc base/bin base/lib base/cdrom cdrom base/var fatbase/efi/boot .make +dirs: base/dev base/tmp base/proc base/bin base/lib base/cdrom base/lib/kuroko cdrom base/var fatbase/efi/boot .make # C Library @@ -192,6 +196,34 @@ base/lib/libc.so: ${LIBC_OBJS} | dirs crts base/lib/libm.so: util/lm.c | dirs crts $(CC) -nodefaultlibs -o $@ $(CFLAGS) -shared -fPIC $^ -lgcc +KUROKO_OBJS=$(patsubst %.c, %.o, $(filter-out kuroko/src/module_% kuroko/src/rline.c kuroko/src/kuroko.c, $(sort $(wildcard kuroko/src/*.c)))) +kuroko/%.o: kuroko/%.c + $(CC) $(CFLAGS) -DDEBUG -fPIC -c -o $@ $^ + +KUROKO_CMODS=$(patsubst kuroko/src/module_%.c,%,$(wildcard kuroko/src/module_*.c)) $(patsubst lib/kuroko/%.c,%,$(wildcard lib/kuroko/*.c)) +KUROKO_CMODS_X=$(foreach lib,$(KUROKO_CMODS),base/lib/kuroko/$(lib).so) +KUROKO_CMODS_Y=$(foreach lib,$(KUROKO_CMODS),.make/$(lib).kmak) +KUROKO_KRK_MODS=$(patsubst kuroko/modules/%.krk,base/lib/kuroko/%.krk,$(wildcard kuroko/modules/*.krk kuroko/modules/*/*.krk)) + +KUROKO_FILES=$(KUROKO_CMODS_X) $(KUROKO_KRK_MODS) base/lib/libkuroko.so + +base/lib/kuroko/%.krk: kuroko/modules/%.krk + @mkdir -p `dirname $@` + cp $< $@ + +.make/%.kmak: kuroko/src/module_%.c util/auto-dep.py | dirs + util/auto-dep.py --makekurokomod $< > $@ + +.make/%.kmak: lib/kuroko/%.c util/auto-dep.py | dirs + util/auto-dep.py --makekurokomod $< > $@ + +ifeq (,$(findstring clean,$(MAKECMDGOALS))) +-include ${KUROKO_CMODS_Y} +endif + +base/lib/libkuroko.so: $(KUROKO_OBJS) | dirs crts ${LC} + $(CC) $(CFLAGS) -DDEBUG -shared -fPIC -o $@ $^ -lgcc + # Userspace Linker/Loader base/lib/ld.so: linker/linker.c base/lib/libc.a | dirs @@ -222,6 +254,10 @@ base/bin/%.sh: apps/%.sh cp $< $@ chmod +x $@ +base/bin/%.krk: apps/%.krk + cp $< $@ + chmod +x $@ + # Ramdisk fatbase/ramdisk.img: ${RAMDISK_FILES} $(shell find base) Makefile util/createramdisk.py | dirs python3 util/createramdisk.py @@ -305,6 +341,8 @@ clean: rm -f base/lib/crt*.o rm -f ${MODULES} rm -f ${APPS_Y} ${LIBS_Y} ${EXT_LIBS_Y} + rm -f ${KUROKO_FILES} + rm -f kuroko/src/*.o ifneq (,$(findstring Microsoft,$(shell uname -r))) QEMU_ARGS=-serial mon:stdio -m 1G -rtc base=localtime -vnc :0 @@ -329,9 +367,11 @@ fast: image.iso .PHONY: headless headless: image.iso - @qemu-system-i386 -cdrom $< ${QEMU_ARGS} \ - -nographic -no-reboot \ - -fw_cfg name=opt/org.toaruos.bootmode,string=headless + @qemu-system-i386 -cdrom $< -m 1G ${KVM} -rtc base=localtime ${QEMU_EXTRA} \ + -serial null -serial mon:stdio \ + -nographic -no-reboot -audiodev none,id=id \ + -fw_cfg name=opt/org.toaruos.bootmode,string=headless \ + -fw_cfg name=opt/org.toaruos.gettyargs,string="-a local /dev/ttyS1" .PHONY: shell shell: image.iso diff --git a/apps/about.c b/apps/about.c index e26bd922..1ace51df 100644 --- a/apps/about.c +++ b/apps/about.c @@ -76,7 +76,7 @@ static void redraw(void) { static void init_default(void) { title_str = "About PonyOS"; - icon_path = "/usr/share/logo_small.bmp"; + icon_path = "/usr/share/logo_small.png"; { version_str = malloc(100); @@ -89,7 +89,7 @@ static void init_default(void) { sprintf(version_str, "PonyOS %s", u.release); } - copyright_str[0] = "(C) 2011-2020 K. Lange, et al."; + copyright_str[0] = "(C) 2011-2021 K. Lange, et al."; copyright_str[1] = "-"; copyright_str[2] = "PonyOS is free software released under the"; copyright_str[3] = "NCSA/University of Illinois license."; @@ -149,7 +149,6 @@ int main(int argc, char * argv[]) { ctx = init_graphics_yutani_double_buffer(window); load_sprite(&logo, icon_path); - logo.alpha = ALPHA_EMBEDDED; redraw(); diff --git a/apps/bim.c b/apps/bim.c index 1579f531..cf1515e1 100644 --- a/apps/bim.c +++ b/apps/bim.c @@ -1,12 +1,6 @@ -/** - * This is a baked, single-file version of bim. - * It was built Thu Feb 27 21:56:23 2020 - * It is based on git commit 83e6cc609584bd31e961c3873a9f3a5c7c2973ec - */ -#define GIT_TAG "83e6cc6-baked" /* Bim - A Text Editor * - * Copyright (C) 2012-2020 K. Lange + * Copyright (C) 2012-2021 K. Lange * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -20,595 +14,12 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - -/* Included from bim-core.h */ -#define _XOPEN_SOURCE 700 -#define _DARWIN_C_SOURCE -#define _DEFAULT_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __TIMESTAMP__ -# define BIM_BUILD_DATE " built " __TIMESTAMP__ -#else -# define BIM_BUILD_DATE DATE "" -#endif - -#ifdef GIT_TAG -# define TAG "-" GIT_TAG -#else -# define TAG "" -#endif - -#define BIM_VERSION "2.6.1" TAG -#define BIM_COPYRIGHT "Copyright 2012-2020 K. Lange <\033[3mklange@toaruos.org\033[23m>" - -#define BLOCK_SIZE 4096 -#define ENTER_KEY '\r' -#define LINE_FEED '\n' -#define BACKSPACE_KEY 0x08 -#define DELETE_KEY 0x7F - -enum Key { - KEY_TIMEOUT = -1, - KEY_CTRL_AT = 0, /* Base */ - KEY_CTRL_A, KEY_CTRL_B, KEY_CTRL_C, KEY_CTRL_D, KEY_CTRL_E, KEY_CTRL_F, KEY_CTRL_G, KEY_CTRL_H, - KEY_CTRL_I, KEY_CTRL_J, KEY_CTRL_K, KEY_CTRL_L, KEY_CTRL_M, KEY_CTRL_N, KEY_CTRL_O, KEY_CTRL_P, - KEY_CTRL_Q, KEY_CTRL_R, KEY_CTRL_S, KEY_CTRL_T, KEY_CTRL_U, KEY_CTRL_V, KEY_CTRL_W, KEY_CTRL_X, - KEY_CTRL_Y, KEY_CTRL_Z, /* Note we keep ctrl-z mapped in termios as suspend */ - KEY_CTRL_OPEN, KEY_CTRL_BACKSLASH, KEY_CTRL_CLOSE, KEY_CTRL_CARAT, KEY_CTRL_UNDERSCORE, - /* Space... */ - /* Some of these are equivalent to things above */ - KEY_BACKSPACE = 0x08, - KEY_LINEFEED = '\n', - KEY_ENTER = '\r', - KEY_TAB = '\t', - /* Basic printable characters go here. */ - /* Delete is special */ - KEY_DELETE = 0x7F, - /* Unicode codepoints go here */ - KEY_ESCAPE = 0x400000, /* Escape would normally be 27, but is special because reasons */ - KEY_F1, KEY_F2, KEY_F3, KEY_F4, - KEY_F5, KEY_F6, KEY_F7, KEY_F8, - KEY_F9, KEY_F10, KEY_F11, KEY_F12, - /* TODO ALT, SHIFT, etc., for F keys */ - KEY_MOUSE, /* Must be followed with a 3-byte mouse read */ - KEY_MOUSE_SGR, /* Followed by an SGR-style sequence of mouse data */ - KEY_HOME, KEY_END, KEY_PAGE_UP, KEY_PAGE_DOWN, - KEY_UP, KEY_DOWN, KEY_RIGHT, KEY_LEFT, - KEY_SHIFT_UP, KEY_SHIFT_DOWN, KEY_SHIFT_RIGHT, KEY_SHIFT_LEFT, - KEY_CTRL_UP, KEY_CTRL_DOWN, KEY_CTRL_RIGHT, KEY_CTRL_LEFT, - KEY_ALT_UP, KEY_ALT_DOWN, KEY_ALT_RIGHT, KEY_ALT_LEFT, - KEY_ALT_SHIFT_UP, KEY_ALT_SHIFT_DOWN, KEY_ALT_SHIFT_RIGHT, KEY_ALT_SHIFT_LEFT, - KEY_SHIFT_TAB, - /* Special signals for paste start, paste end */ - KEY_PASTE_BEGIN, KEY_PASTE_END, -}; - -struct key_name_map { - enum Key keycode; - char * name; -}; - -extern struct key_name_map KeyNames[]; - -/** - * Syntax highlighting flags. - */ -#define FLAG_NONE 0 -#define FLAG_KEYWORD 1 -#define FLAG_STRING 2 -#define FLAG_COMMENT 3 -#define FLAG_TYPE 4 -#define FLAG_PRAGMA 5 -#define FLAG_NUMERAL 6 -#define FLAG_ERROR 7 -#define FLAG_DIFFPLUS 8 -#define FLAG_DIFFMINUS 9 -#define FLAG_NOTICE 10 -#define FLAG_BOLD 11 -#define FLAG_LINK 12 -#define FLAG_ESCAPE 13 - -#define FLAG_SELECT (1 << 5) -#define FLAG_SEARCH (1 << 6) - -/** - * Line buffer definitions - * - * Lines are essentially resizable vectors of char_t structs, - * which represent single codepoints in the file. - */ -typedef struct { - uint32_t display_width:4; - uint32_t flags:7; - uint32_t codepoint:21; -} __attribute__((packed)) char_t; - -/** - * Lines have available and actual lengths, describing - * how much space was allocated vs. how much is being - * used at the moment. - */ -typedef struct { - int available; - int actual; - int istate; - int is_current; - int rev_status; - char_t text[]; -} line_t; - -/** - * Global configuration state - * - * At the moment, this is all in a global, but in the future - * this should be passed around to various functions. - */ -typedef struct { - /* Terminal size */ - int term_width, term_height; - int bottom_size; - - line_t ** yanks; - size_t yank_count; - int yank_is_full_lines; - - int tty_in; - - const char * bimrc_path; - const char * syntax_fallback; - uint32_t * search; - - int overlay_mode; - line_t * command_buffer; - - int command_offset, command_col_no; - struct syntax_definition * command_syn, * command_syn_back; - int history_point; - int search_direction; - int prev_line, prev_col, prev_coffset, prev_offset; - - unsigned int highlight_on_open:1; - unsigned int initial_file_is_read_only:1; - unsigned int go_to_line:1; - unsigned int break_from_selection:1; - unsigned int can_scroll:1; - unsigned int can_hideshow:1; - unsigned int can_altscreen:1; - unsigned int can_mouse:1; - unsigned int can_unicode:1; - unsigned int can_bright:1; - unsigned int can_title:1; - unsigned int can_bce:1; - unsigned int can_24bit:1; - unsigned int can_256color:1; - unsigned int can_italic:1; - unsigned int can_insert:1; - unsigned int can_bracketedpaste:1; - unsigned int history_enabled:1; - unsigned int highlight_parens:1; - unsigned int smart_case:1; - unsigned int highlight_current_line:1; - unsigned int shift_scrolling:1; - unsigned int check_git:1; - unsigned int color_gutter:1; - unsigned int relative_lines:1; - unsigned int numbers:1; - unsigned int horizontal_shift_scrolling:1; - unsigned int hide_statusbar:1; - unsigned int tabs_visible:1; - unsigned int autohide_tabs:1; - unsigned int smart_complete:1; - unsigned int has_terminal:1; - unsigned int use_sgr_mouse:1; - unsigned int search_wraps:1; - - int cursor_padding; - int split_percent; - int scroll_amount; - int tab_offset; - - char * tab_indicator; - char * space_indicator; - -} global_config_t; - -#define OVERLAY_MODE_NONE 0 -#define OVERLAY_MODE_READ_ONE 1 -#define OVERLAY_MODE_COMMAND 2 -#define OVERLAY_MODE_SEARCH 3 -#define OVERLAY_MODE_COMPLETE 4 - -#define HISTORY_SENTINEL 0 -#define HISTORY_INSERT 1 -#define HISTORY_DELETE 2 -#define HISTORY_REPLACE 3 -#define HISTORY_REMOVE_LINE 4 -#define HISTORY_ADD_LINE 5 -#define HISTORY_REPLACE_LINE 6 -#define HISTORY_MERGE_LINES 7 -#define HISTORY_SPLIT_LINE 8 - -#define HISTORY_BREAK 10 - -typedef struct history { - struct history * previous; - struct history * next; - int type; - int line; - int col; - union { - struct { - int lineno; - int offset; - int codepoint; - int old_codepoint; - } insert_delete_replace; - - struct { - int lineno; - line_t * contents; - line_t * old_contents; - } remove_replace_line; - - struct { - int lineno; - int split; - } add_merge_split_lines; - } contents; -} history_t; - -/** - * Buffer data - * - * A buffer describes a file, and stores - * its name as well as the editor state - * (cursor offsets, etc.) and the actual - * line buffers. - */ -typedef struct _env { - unsigned int loading:1; - unsigned int tabs:1; - unsigned int modified:1; - unsigned int readonly:1; - unsigned int indent:1; - unsigned int checkgitstatusonwrite:1; - unsigned int crnl:1; - unsigned int numbers:1; - unsigned int gutter:1; - - int highlighting_paren; - int maxcolumn; - - short mode; - short tabstop; - - char * file_name; - int offset; - int coffset; - int line_no; - int line_count; - int line_avail; - int col_no; - int preferred_column; - struct syntax_definition * syntax; - line_t ** lines; - - history_t * history; - history_t * last_save_history; - - int width; - int left; - - int start_line; - int sel_col; - int start_col; - int prev_line; -} buffer_t; - -struct theme_def { - const char * name; - void (*load)(const char * name); -}; - -extern struct theme_def * themes; - -extern void add_colorscheme(struct theme_def theme); - -struct syntax_state { - line_t * line; - int line_no; - int state; - int i; -}; - -struct completion_match { - char * string; - char * file; - char * search; -}; - -struct syntax_definition { - char * name; - char ** ext; - int (*calculate)(struct syntax_state *); - int prefers_spaces; - int (*completion_qualifier)(int c); - int (*completion_matcher)(uint32_t * comp, struct completion_match ** matches, int * matches_count, int complete_match, int * matches_len); -}; - -extern struct syntax_definition * syntaxes; - -/** - * Editor mode states - */ -#define MODE_NORMAL 0 -#define MODE_INSERT 1 -#define MODE_LINE_SELECTION 2 -#define MODE_REPLACE 3 -#define MODE_CHAR_SELECTION 4 -#define MODE_COL_SELECTION 5 -#define MODE_COL_INSERT 6 -#define MODE_DIRECTORY_BROWSE 7 - -extern global_config_t global_config; - -extern const char * COLOR_FG; -extern const char * COLOR_BG; -extern const char * COLOR_ALT_FG; -extern const char * COLOR_ALT_BG; -extern const char * COLOR_NUMBER_FG; -extern const char * COLOR_NUMBER_BG; -extern const char * COLOR_STATUS_FG; -extern const char * COLOR_STATUS_BG; -extern const char * COLOR_STATUS_ALT; -extern const char * COLOR_TABBAR_BG; -extern const char * COLOR_TAB_BG; -extern const char * COLOR_ERROR_FG; -extern const char * COLOR_ERROR_BG; -extern const char * COLOR_SEARCH_FG; -extern const char * COLOR_SEARCH_BG; -extern const char * COLOR_KEYWORD; -extern const char * COLOR_STRING; -extern const char * COLOR_COMMENT; -extern const char * COLOR_TYPE; -extern const char * COLOR_PRAGMA; -extern const char * COLOR_NUMERAL; -extern const char * COLOR_SELECTFG; -extern const char * COLOR_SELECTBG; -extern const char * COLOR_RED; -extern const char * COLOR_GREEN; -extern const char * COLOR_BOLD; -extern const char * COLOR_LINK; -extern const char * COLOR_ESCAPE; -extern const char * current_theme; - -struct action_def { - char * name; - void (*action)(); - int options; - const char * description; -}; - -extern struct action_def * mappable_actions; - -#define ARG_IS_INPUT 0x01 /* Takes the key that triggered it as the first argument */ -#define ARG_IS_CUSTOM 0x02 /* Takes a custom argument which is specific to the method */ -#define ARG_IS_PROMPT 0x04 /* Prompts for an argument. */ -#define ACTION_IS_RW 0x08 /* Needs to be able to write. */ - -#define BIM_ACTION(name, options, description) \ - void name (); /* Define the action with unknown arguments */ \ - void __attribute__((constructor)) _install_ ## name (void) { \ - add_action((struct action_def){#name, name, options, description}); \ - } \ - void name - -struct command_def { - char * name; - int (*command)(char *, int, char * arg[]); - const char * description; -}; - -#define BIM_COMMAND(cmd_name, cmd_str, description) \ - int bim_command_ ## cmd_name (char * cmd, int argc, char * argv[]); \ - void __attribute__((constructor)) _install_cmd_ ## cmd_name (void) { \ - add_command((struct command_def){cmd_str, bim_command_ ## cmd_name, description}); \ - } \ - int bim_command_ ## cmd_name (char * cmd __attribute__((unused)), int argc __attribute__((unused)), char * argv[] __attribute__((unused))) - -#define BIM_ALIAS(alias, alias_name, cmd_name) \ - void __attribute__((constructor)) _install_alias_ ## alias_name (void) { \ - add_command((struct command_def){alias, bim_command_ ## cmd_name, "Alias for " #cmd_name}); \ - } - -#define BIM_PREFIX_COMMAND(cmd_name, cmd_prefix, description) \ - int bim_command_ ## cmd_name (char * cmd, int argc, char * argv[]); \ - void __attribute__((constructor)) _install_cmd_ ## cmd_name (void) { \ - add_prefix_command((struct command_def){cmd_prefix, bim_command_ ## cmd_name, description}); \ - } \ - int bim_command_ ## cmd_name (char * cmd __attribute__((unused)), int argc __attribute__((unused)), char * argv[] __attribute__((unused))) - -extern buffer_t * env; -extern buffer_t * left_buffer; -extern buffer_t * right_buffer; -#define NAV_BUFFER_MAX 10 -extern char nav_buf[NAV_BUFFER_MAX+1]; -extern int nav_buffer; -extern int buffers_len; -extern int buffers_avail; -extern buffer_t ** buffers; - -extern const char * flag_to_color(int _flag); -extern void redraw_line(int x); -extern int git_examine(char * filename); -extern void search_next(void); -extern void set_preferred_column(void); -extern void quit(const char * message); -extern void close_buffer(void); -extern void set_syntax_by_name(const char * name); -extern void rehighlight_search(line_t * line); -extern void try_to_center(); -extern int read_one_character(char * message); -extern void bim_unget(int c); -#define bim_getch() bim_getch_timeout(200) -extern int bim_getch_timeout(int timeout); -extern buffer_t * buffer_new(void); -extern FILE * open_biminfo(void); -extern int fetch_from_biminfo(buffer_t * buf); -extern int update_biminfo(buffer_t * buf); -extern buffer_t * buffer_close(buffer_t * buf); -extern int to_eight(uint32_t codepoint, char * out); -extern char * name_from_key(enum Key keycode); -extern void add_action(struct action_def action); -extern void open_file(char * file); -extern void recalculate_selected_lines(void); -extern void add_command(struct command_def command); -extern void add_prefix_command(struct command_def command); -extern void render_command_input_buffer(void); -extern void unhighlight_matching_paren(void); - -extern void add_syntax(struct syntax_definition def); - -struct ColorName { - const char * name; - const char ** value; -}; - -extern struct ColorName color_names[]; - -struct bim_function { - char * command; - struct bim_function * next; -}; - -extern struct bim_function ** user_functions; -extern int run_function(char * name); -extern int has_function(char * name); -extern void find_matching_paren(int * out_line, int * out_col, int in_col); -extern void render_error(char * message, ...); -extern void pause_for_key(void); - -#define add_match(match_string, match_file, match_search) do { \ - if (*matches_count == *matches_len) { \ - (*matches_len) *= 2; \ - *matches = realloc(*matches, sizeof(struct completion_match) * (*matches_len)); \ - } \ - (*matches)[*matches_count].string = strdup(match_string); \ - (*matches)[*matches_count].file = strdup(match_file); \ - (*matches)[*matches_count].search = strdup(match_search); \ - (*matches_count)++; \ -} while (0) - -#define add_if_match(name,desc) do { \ - int i = 0; \ - while (comp[i] && comp[i] == (unsigned char)name[i]) i++; \ - if (comp[i] == '\0') { \ - add_match(name,desc,""); \ - } \ -} while (0) - -struct action_map { - int key; - void (*method)(); - int options; - int arg; -}; - -#define opt_rep 0x1 /* This action will be repeated */ -#define opt_arg 0x2 /* This action will take a specified argument */ -#define opt_char 0x4 /* This action will read a character to pass as an argument */ -#define opt_nav 0x8 /* This action will consume the nav buffer as its argument */ -#define opt_rw 0x10 /* Must not be read-only */ -#define opt_norm 0x20 /* Returns to normal mode */ -#define opt_byte 0x40 /* Same as opt_char but forces a byte */ - -struct mode_names { - const char * description; - const char * name; - struct action_map ** mode; -}; - -extern struct mode_names mode_names[]; - -/* End of bim-core.h */ - -/* Included from bim-syntax.h */ -#define BIM_SYNTAX(name, spaces) \ - __attribute__((constructor)) static void _load_ ## name (void) { \ - add_syntax((struct syntax_definition){#name, syn_ ## name ## _ext, syn_ ## name ## _calculate, spaces, NULL, NULL}); \ - } \ - -#define BIM_SYNTAX_EXT(name, spaces, matcher) \ - __attribute__((constructor)) static void _load_ ## name (void) { \ - add_syntax((struct syntax_definition){#name, syn_ ## name ## _ext, syn_ ## name ## _calculate, spaces, matcher, _match_completions_ ## name}); \ - } \ - -#define BIM_SYNTAX_COMPLETER(name) \ - static int _match_completions_ ## name ( \ - uint32_t * comp __attribute__((unused)), \ - struct completion_match **matches __attribute__((unused)), \ - int * matches_count __attribute__((unused)), \ - int complete_match __attribute__((unused)), \ - int *matches_len __attribute__((unused))) - -#define paint(length, flag) do { for (int i = 0; i < (length) && state->i < state->line->actual; i++, state->i++) { state->line->text[state->i].flags = (flag); } } while (0) -#define charat() (state->i < state->line->actual ? state->line->text[(state->i)].codepoint : -1) -#define nextchar() (state->i + 1 < state->line->actual ? state->line->text[(state->i+1)].codepoint : -1) -#define lastchar() (state->i - 1 >= 0 ? state->line->text[(state->i-1)].codepoint : -1) -#define skip() (state->i++) -#define charrel(x) (state->i + (x) < state->line->actual ? state->line->text[(state->i+(x))].codepoint : -1) - -extern int find_keywords(struct syntax_state * state, char ** keywords, int flag, int (*keyword_qualifier)(int c)); -extern int match_and_paint(struct syntax_state * state, const char * keyword, int flag, int (*keyword_qualifier)(int c)); -extern void paint_single_string(struct syntax_state * state); -extern void paint_simple_string(struct syntax_state * state); -extern int common_comment_buzzwords(struct syntax_state * state); -extern int paint_comment(struct syntax_state * state); -extern int match_forward(struct syntax_state * state, char * c); -extern struct syntax_definition * find_syntax_calculator(const char * name); - -#define nest(lang, low) \ - do { \ - state->state = (state->state < 1 ? 0 : state->state - low); \ - do { state->state = lang(state); } while (state->state == 0); \ - if (state->state == -1) return low; \ - return state->state + low; \ - } while (0) - -/* Some of the C stuff is widely used */ -extern int c_keyword_qualifier(int c); -extern int paint_c_numeral(struct syntax_state * state); -extern int paint_c_comment(struct syntax_state * state); -extern void paint_c_char(struct syntax_state * state); - -/* Hacky workaround for isdigit not really accepting Unicode stuff */ -static __attribute__((used)) int _isdigit(int c) { if (c > 128) return 0; return isdigit(c); } -static __attribute__((used)) int _isxdigit(int c) { if (c > 128) return 0; return isxdigit(c); } - -#undef isdigit -#undef isxdigit -#define isdigit(c) _isdigit(c) -#define isxdigit(c) _isxdigit(c) - - -/* End of bim-syntax.h */ +#define ENABLE_THREADING +#include "bim.h" +#include +#include +#include +#include global_config_t global_config = { /* State */ @@ -619,8 +30,8 @@ global_config_t global_config = { .yank_count = 0, .yank_is_full_lines = 0, .tty_in = STDIN_FILENO, - .bimrc_path = "~/.bimrc", - .syntax_fallback = NULL, /* syntax to fall back to if none other match applies */ + .bimrc_path = "~/.bim3rc", + .syntax_fallback = NULL, /* Syntax to fall back to if no other match applies */ .search = NULL, .overlay_mode = OVERLAY_MODE_NONE, .command_buffer = NULL, @@ -646,6 +57,7 @@ global_config_t global_config = { .can_italic = 1, /* can use italics (without inverting) */ .can_insert = 0, /* ^[[L */ .can_bracketedpaste = 0, /* puts escapes before and after pasted stuff */ + .can_sgrmouse = 0, /* Whether SGR mouse mode is availabe (large coordinates) */ /* Configuration options */ .history_enabled = 1, .highlight_parens = 1, /* highlight parens/braces when cursor moves */ @@ -662,13 +74,16 @@ global_config_t global_config = { .autohide_tabs = 0, .smart_complete = 0, .has_terminal = 0, - .use_sgr_mouse = 0, .search_wraps = 1, + .had_error = 0, + .use_biminfo = 1, /* Integer config values */ .cursor_padding = 4, .split_percent = 50, .scroll_amount = 5, .tab_offset = 0, + .background_task = NULL, + .tail_task = NULL, }; struct key_name_map KeyNames[] = { @@ -712,6 +127,55 @@ char * name_from_key(enum Key keycode) { return keyNameTmp; } +#define S(c) (krk_copyString(c,sizeof(c)-1)) + +static KrkClass * syntaxStateClass = NULL; + +struct SyntaxState { + KrkInstance inst; + struct syntax_state state; +}; + +static void schedule_complete_recalc(void); + +/** + * Theming data + * + * This default set is pretty simple "default foreground on default background" + * except for search and selections which are black-on-white specifically. + * + * The theme colors get set by separate configurable theme scripts. + */ +const char * COLOR_FG = "@9"; +const char * COLOR_BG = "@9"; +const char * COLOR_ALT_FG = "@9"; +const char * COLOR_ALT_BG = "@9"; +const char * COLOR_NUMBER_FG = "@9"; +const char * COLOR_NUMBER_BG = "@9"; +const char * COLOR_STATUS_FG = "@9"; +const char * COLOR_STATUS_BG = "@9"; +const char * COLOR_STATUS_ALT= "@9"; +const char * COLOR_TABBAR_BG = "@9"; +const char * COLOR_TAB_BG = "@9"; +const char * COLOR_ERROR_FG = "@9"; +const char * COLOR_ERROR_BG = "@9"; +const char * COLOR_SEARCH_FG = "@0"; +const char * COLOR_SEARCH_BG = "@17"; +const char * COLOR_KEYWORD = "@9"; +const char * COLOR_STRING = "@9"; +const char * COLOR_COMMENT = "@9"; +const char * COLOR_TYPE = "@9"; +const char * COLOR_PRAGMA = "@9"; +const char * COLOR_NUMERAL = "@9"; +const char * COLOR_SELECTFG = "@0"; +const char * COLOR_SELECTBG = "@17"; +const char * COLOR_RED = "@1"; +const char * COLOR_GREEN = "@2"; +const char * COLOR_BOLD = "@9"; +const char * COLOR_LINK = "@9"; +const char * COLOR_ESCAPE = "@9"; +const char * current_theme = "none"; + struct ColorName color_names[] = { {"text-fg", &COLOR_FG}, {"text-bg", &COLOR_BG}, @@ -766,7 +230,6 @@ FLEXIBLE_ARRAY(mappable_actions, add_action, struct action_def, ((struct action_ FLEXIBLE_ARRAY(regular_commands, add_command, struct command_def, ((struct command_def){NULL,NULL,NULL})) FLEXIBLE_ARRAY(prefix_commands, add_prefix_command, struct command_def, ((struct command_def){NULL,NULL,NULL})) FLEXIBLE_ARRAY(themes, add_colorscheme, struct theme_def, ((struct theme_def){NULL,NULL})) -FLEXIBLE_ARRAY(user_functions, add_user_function, struct bim_function *, NULL) /** * Special implementation of getch with a timeout @@ -777,6 +240,7 @@ void bim_unget(int c) { _bim_unget = c; } +void redraw_statusbar(void); int bim_getch_timeout(int timeout) { fflush(stdout); if (_bim_unget != -1) { @@ -793,6 +257,16 @@ int bim_getch_timeout(int timeout) { read(global_config.tty_in, buf, 1); return buf[0]; } else { + background_task_t * task = global_config.background_task; + if (task) { + global_config.background_task = task->next; + task->func(task); + free(task); + if (!global_config.background_task) { + global_config.tail_task = NULL; + redraw_statusbar(); + } + } return -1; } } @@ -867,7 +341,7 @@ int bim_getkey(int read_timeout) { uint32_t c; uint32_t istate = 0; - while ((cin = bim_getch_timeout(read_timeout))) { + while ((cin = bim_getch_timeout((timeout == 1) ? 50 : read_timeout))) { if (cin == -1) { if (timeout && this_buf[timeout-1] == '\033') return KEY_ESCAPE; @@ -900,10 +374,10 @@ int bim_getkey(int read_timeout) { } if (timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == 'O') { switch (c) { - case 'P': timeout = 0; return KEY_F1; - case 'Q': timeout = 0; return KEY_F2; - case 'R': timeout = 0; return KEY_F3; - case 'S': timeout = 0; return KEY_F4; + case 'P': return KEY_F1; + case 'Q': return KEY_F2; + case 'R': return KEY_F3; + case 'S': return KEY_F4; } timeout = 0; continue; @@ -927,25 +401,25 @@ int bim_getkey(int read_timeout) { } if (timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == '[') { switch (c) { - case 'M': timeout = 0; return KEY_MOUSE; - case '<': timeout = 0; return KEY_MOUSE_SGR; + case 'M': return KEY_MOUSE; + case '<': return KEY_MOUSE_SGR; case 'A': return shift_key(KEY_UP); case 'B': return shift_key(KEY_DOWN); case 'C': return shift_key(KEY_RIGHT); case 'D': return shift_key(KEY_LEFT); - case 'H': timeout = 0; return KEY_HOME; - case 'F': timeout = 0; return KEY_END; - case 'I': timeout = 0; return KEY_PAGE_UP; - case 'G': timeout = 0; return KEY_PAGE_DOWN; - case 'Z': timeout = 0; return KEY_SHIFT_TAB; + case 'H': return KEY_HOME; + case 'F': return KEY_END; + case 'I': return KEY_PAGE_UP; + case 'G': return KEY_PAGE_DOWN; + case 'Z': return KEY_SHIFT_TAB; case '~': if (timeout == 3) { switch (this_buf[2]) { - case '1': timeout = 0; return KEY_HOME; - case '3': timeout = 0; return KEY_DELETE; - case '4': timeout = 0; return KEY_END; - case '5': timeout = 0; return KEY_PAGE_UP; - case '6': timeout = 0; return KEY_PAGE_DOWN; + case '1': return KEY_HOME; + case '3': return KEY_DELETE; + case '4': return KEY_END; + case '5': return KEY_PAGE_UP; + case '6': return KEY_PAGE_DOWN; } } else if (timeout == 5) { if (this_buf[2] == '2' && this_buf[3] == '0' && this_buf[4] == '0') { @@ -996,7 +470,7 @@ enum Key key_from_name(char * name) { while (*name) { if (!decode(&state, &c, (unsigned char)*name)) { if (candidate == -1) candidate = c; - else return -1; /* Multiple characters */ + else return -1; /* Reject `name` if it is multiple codepoints */ } else if (state == UTF8_REJECT) { return -1; } @@ -1016,6 +490,7 @@ buffer_t * right_buffer = NULL; /** * A buffer for holding a number (line, repetition count) */ +#define NAV_BUFFER_MAX 10 char nav_buf[NAV_BUFFER_MAX+1]; int nav_buffer = 0; @@ -1036,7 +511,7 @@ buffer_t * buffer_new(void) { buffers = realloc(buffers, sizeof(buffer_t *) * buffers_avail); } - /* TODO: Support having split buffers with more than two buffers open */ + /* TODO: Clean up split support and support multiple splits... */ if (left_buffer) { left_buffer->left = 0; left_buffer->width = global_config.term_width; @@ -1064,10 +539,11 @@ buffer_t * buffer_new(void) { * Open the biminfo file. */ FILE * open_biminfo(void) { - /* TODO This should probably be configurable line bimrc */ + if (!global_config.use_biminfo) return NULL; + char * home = getenv("HOME"); if (!home) { - /* Since it's not, we need HOME */ + /* ... but since it's not, we need $HOME, so fail if it isn't set. */ return NULL; } @@ -1084,6 +560,58 @@ FILE * open_biminfo(void) { return biminfo; } +/** + * Check if a file is open by examining the biminfo file + */ +int file_is_open(char * file_name) { + /* Get the absolute path of the file to normalize for lookup */ + char tmp_path[PATH_MAX+2]; + if (!realpath(file_name, tmp_path)) { + return 0; /* Assume not */ + } + strcat(tmp_path," "); + + FILE * biminfo = open_biminfo(); + if (!biminfo) return 0; /* Assume not */ + + /* Scan */ + char line[PATH_MAX+64]; + + while (!feof(biminfo)) { + fpos_t start_of_line; + fgetpos(biminfo, &start_of_line); + fgets(line, PATH_MAX+63, biminfo); + if (line[0] != '%') { + continue; + } + + if (!strncmp(&line[1],tmp_path, strlen(tmp_path))) { + /* File is currently open */ + int pid = -1; + sscanf(line+1+strlen(tmp_path)+1,"%d",&pid); + if (pid != -1 && pid != getpid()) { + if (!kill(pid, 0)) { + int key = 0; + render_error("biminfo indicates another instance may already be editing this file"); + render_commandline_message("\n"); + render_commandline_message("file path = %s\n", tmp_path); + render_commandline_message("pid = %d (still running)\n", pid); + render_commandline_message("Open file anyway? (y/N)"); + while ((key = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT); + if (key != 'y') { + fclose(biminfo); + return 1; + } + } + } + fclose(biminfo); + return 0; + } + } + fclose(biminfo); + return 0; +} + /** * Fetch the cursor position from a biminfo file */ @@ -1091,7 +619,7 @@ int fetch_from_biminfo(buffer_t * buf) { /* Can't fetch if we don't have a filename */ if (!buf->file_name) return 1; - /* Get the absolute name of the file */ + /* Get the absolute path of the file to normalize for lookup */ char tmp_path[PATH_MAX+2]; if (!realpath(buf->file_name, tmp_path)) { return 1; @@ -1120,20 +648,23 @@ int fetch_from_biminfo(buffer_t * buf) { if (buf->line_no > buf->line_count) buf->line_no = buf->line_count; if (buf->col_no > buf->lines[buf->line_no-1]->actual) buf->col_no = buf->lines[buf->line_no-1]->actual; try_to_center(); + + fclose(biminfo); return 0; } } + fclose(biminfo); return 0; } /** * Write a file containing the last cursor position of a buffer. */ -int update_biminfo(buffer_t * buf) { +int update_biminfo(buffer_t * buf, int is_open) { if (!buf->file_name) return 1; - /* Get the absolute name of the file */ + /* Get the absolute path of the file to normalize for lookup */ char tmp_path[PATH_MAX+1]; if (!realpath(buf->file_name, tmp_path)) { return 1; @@ -1150,14 +681,15 @@ int update_biminfo(buffer_t * buf) { fpos_t start_of_line; fgetpos(biminfo, &start_of_line); fgets(line, PATH_MAX+63, biminfo); - if (line[0] != '>') { + if (line[0] != '>' && line[0] != '%') { continue; } if (!strncmp(&line[1],tmp_path, strlen(tmp_path))) { /* Update */ fsetpos(biminfo, &start_of_line); - fprintf(biminfo,">%s %20d %20d\n", tmp_path, buf->line_no, buf->col_no); + fprintf(biminfo,"%c%s %20d %20d\n", is_open ? '%' : '>', tmp_path, + is_open ? getpid() : buf->line_no, buf->col_no); goto _done; } } @@ -1169,32 +701,60 @@ int update_biminfo(buffer_t * buf) { fprintf(biminfo, "# Cursor positions and other state are stored here.\n"); } - /* Haven't found what we're looking for, should be at end of file */ - fprintf(biminfo, ">%s %20d %20d\n", tmp_path, buf->line_no, buf->col_no); + /* If we reach this point, we didn't find a record for this file + * and the write cursor should be at the end, so just add a new line */ + fprintf(biminfo,"%c%s %20d %20d\n", is_open ? '%' : '>', tmp_path, + is_open ? getpid() : buf->line_no, buf->col_no); _done: fclose(biminfo); return 0; } +void cancel_background_tasks(buffer_t * buf) { + background_task_t * t = global_config.background_task; + background_task_t * last = NULL; + while (t) { + if (t->env == buf) { + if (last) { + last->next = t->next; + } else { + global_config.background_task = t->next; + } + if (!t->next) { + global_config.tail_task = last; + } + background_task_t * tmp = t->next; + free(t); + t = tmp; + } else { + last = t; + t = t->next; + } + } +} + /** * Close a buffer */ buffer_t * buffer_close(buffer_t * buf) { int i; - /* Locate the buffer in the buffer pointer vector */ + /* Locate the buffer in the buffer list */ for (i = 0; i < buffers_len; i++) { if (buf == buffers[i]) break; } - /* Invalid buffer? */ + /* This buffer doesn't exist? */ if (i == buffers_len) { - return env; /* wtf */ + return NULL; } - update_biminfo(buf); + /* Cancel any background tasks for this env */ + cancel_background_tasks(buf); + + update_biminfo(buf, 0); /* Clean up lines used by old buffer */ for (int i = 0; i < buf->line_count; ++i) { @@ -1222,7 +782,7 @@ buffer_t * buffer_close(buffer_t * buf) { /* Remove the buffer from the vector, moving others up */ if (i != buffers_len - 1) { - memmove(&buffers[i], &buffers[i+1], sizeof(*buffers) * (buffers_len - i)); + memmove(&buffers[i], &buffers[i+1], sizeof(*buffers) * (buffers_len - i - 1)); } /* There is one less buffer */ @@ -1234,53 +794,15 @@ buffer_t * buffer_close(buffer_t * buf) { return NULL; } - /* If this was the last buffer, return the previous last buffer */ + /* If this was the last buffer in the list, return the previous last buffer */ if (i == buffers_len) { return buffers[buffers_len-1]; } - /* Otherwise return the new last buffer */ + /* Otherwise return the buffer in the same location */ return buffers[i]; } -/** - * Theming data - * - * This default set is pretty simple "default foreground on default background" - * except for search and selections which are black-on-white specifically. - * - * The theme colors get set by separate configurable themes. - */ -const char * COLOR_FG = "@9"; -const char * COLOR_BG = "@9"; -const char * COLOR_ALT_FG = "@9"; -const char * COLOR_ALT_BG = "@9"; -const char * COLOR_NUMBER_FG = "@9"; -const char * COLOR_NUMBER_BG = "@9"; -const char * COLOR_STATUS_FG = "@9"; -const char * COLOR_STATUS_BG = "@9"; -const char * COLOR_STATUS_ALT= "@9"; -const char * COLOR_TABBAR_BG = "@9"; -const char * COLOR_TAB_BG = "@9"; -const char * COLOR_ERROR_FG = "@9"; -const char * COLOR_ERROR_BG = "@9"; -const char * COLOR_SEARCH_FG = "@0"; -const char * COLOR_SEARCH_BG = "@17"; -const char * COLOR_KEYWORD = "@9"; -const char * COLOR_STRING = "@9"; -const char * COLOR_COMMENT = "@9"; -const char * COLOR_TYPE = "@9"; -const char * COLOR_PRAGMA = "@9"; -const char * COLOR_NUMERAL = "@9"; -const char * COLOR_SELECTFG = "@0"; -const char * COLOR_SELECTBG = "@17"; -const char * COLOR_RED = "@1"; -const char * COLOR_GREEN = "@2"; -const char * COLOR_BOLD = "@9"; -const char * COLOR_LINK = "@9"; -const char * COLOR_ESCAPE = "@9"; -const char * current_theme = "none"; - /** * Convert syntax highlighting flag to color code */ @@ -1316,26 +838,6 @@ const char * flag_to_color(int _flag) { } } - -/** - * Find keywords from a list and paint them, assuming they aren't in the middle of other words. - * Returns 1 if a keyword from the last was found, otherwise 0. - */ -int find_keywords(struct syntax_state * state, char ** keywords, int flag, int (*keyword_qualifier)(int c)) { - if (keyword_qualifier(lastchar())) return 0; - if (!keyword_qualifier(charat())) return 0; - for (char ** keyword = keywords; *keyword; ++keyword) { - int d = 0; - while (state->i + d < state->line->actual && state->line->text[state->i+d].codepoint == (*keyword)[d]) d++; - if ((*keyword)[d] == '\0' && (state->i + d >= state->line->actual || !keyword_qualifier(state->line->text[state->i+d].codepoint))) { - paint((int)strlen(*keyword), flag); - return 1; - } - } - - return 0; -} - /** * Match and paint a single keyword. Returns 1 if the keyword was matched and 0 otherwise, * so it can be used for prefix checking for things that need further special handling. @@ -1361,40 +863,6 @@ int match_and_paint(struct syntax_state * state, const char * keyword, int flag, return 0; } -void paint_single_string(struct syntax_state * state) { - paint(1, FLAG_STRING); - while (charat() != -1) { - if (charat() == '\\' && nextchar() == '\'') { - paint(2, FLAG_ESCAPE); - } else if (charat() == '\'') { - paint(1, FLAG_STRING); - return; - } else if (charat() == '\\') { - paint(2, FLAG_ESCAPE); - } else { - paint(1, FLAG_STRING); - } - } -} - - -void paint_simple_string(struct syntax_state * state) { - /* Assumes you came in from a check of charat() == '"' */ - paint(1, FLAG_STRING); - while (charat() != -1) { - if (charat() == '\\' && nextchar() == '"') { - paint(2, FLAG_ESCAPE); - } else if (charat() == '"') { - paint(1, FLAG_STRING); - return; - } else if (charat() == '\\') { - paint(2, FLAG_ESCAPE); - } else { - paint(1, FLAG_STRING); - } - } -} - /** * This is a basic character matcher for "keyword" characters. */ @@ -1404,7 +872,8 @@ int simple_keyword_qualifier(int c) { /** * These words can appear in comments and should be highlighted. - * Since there are a lot of comment highlighters, let's break them out. + * Since there are a lot of comment highlighters, this is provided + * as a common function that can be used by multiple highlighters. */ int common_comment_buzzwords(struct syntax_state * state) { if (match_and_paint(state, "TODO", FLAG_NOTICE, simple_keyword_qualifier)) { return 1; } @@ -1414,7 +883,7 @@ int common_comment_buzzwords(struct syntax_state * state) { } /** - * Paint a comment until end of line, assumes this comment can not continue. + * Paint a comment until end of line; assumes this comment can not continue. * (Some languages have comments that can continue with a \ - don't use this!) * Assumes you've already painted your comment start characters. */ @@ -1427,21 +896,7 @@ int paint_comment(struct syntax_state * state) { } /** - * Match a word forward and return whether it was matched. - */ -int match_forward(struct syntax_state * state, char * c) { - int i = 0; - while (1) { - if (charrel(i) == -1 && !*c) return 1; - if (charrel(i) != *c) return 0; - c++; - i++; - } - return 0; -} - -/** - * Find and return a highlighter by name, or NULL + * Find and return a highlighter by name, or return NULL if none was found. */ struct syntax_definition * find_syntax_calculator(const char * name) { for (struct syntax_definition * s = syntaxes; syntaxes && s->name; ++s) { @@ -1457,6 +912,14 @@ int syntax_space = 0; struct syntax_definition * syntaxes = NULL; void add_syntax(struct syntax_definition def) { + /* See if a name match already exists for this def. */ + for (struct syntax_definition * s = syntaxes; syntaxes && s->name; ++s) { + if (!strcmp(def.name,s->name)) { + *s = def; + return; + } + } + if (syntax_space == 0) { syntax_space = 4; syntaxes = calloc(sizeof(struct syntax_definition), syntax_space); @@ -1469,15 +932,25 @@ void add_syntax(struct syntax_definition def) { syntax_count++; } +void redraw_all(void); + /** - * Calculate syntax highlighting for the given line. + * Calculate syntax highlighting for the given line, and lines after + * if their initial syntax state has changed by this recalculation. + * + * If `line_no` is -1, this line is taken to be a special line and not + * part of a buffer; search highlighting will not be processed and syntax + * highlighting will halt after the line is finished. + * + * If `env->slowop` is currently enabled, recalculation is skipped. */ void recalculate_syntax(line_t * line, int line_no) { + if (env->slowop) return; /* Clear syntax for this line first */ int is_original = 1; while (1) { for (int i = 0; i < line->actual; ++i) { - line->text[i].flags = 0; + line->text[i].flags = line->text[i].flags & (3 << 5); } if (!env->syntax) { @@ -1486,25 +959,40 @@ void recalculate_syntax(line_t * line, int line_no) { } /* Start from the line's stored in initial state */ - struct syntax_state state; - state.line = line; - state.line_no = line_no; - state.state = line->istate; - state.i = 0; + struct SyntaxState * s = (void*)krk_newInstance(env->syntax->krkClass); + s->state.env = env; + s->state.line = line; + s->state.line_no = line_no; + s->state.state = line->istate; + s->state.i = 0; while (1) { - state.state = env->syntax->calculate(&state); - - if (state.state != 0) { + ptrdiff_t before = krk_currentThread.stackTop - krk_currentThread.stack; + krk_push(OBJECT_VAL(s)); + KrkValue result = krk_callSimple(OBJECT_VAL(env->syntax->krkFunc), 1, 0); + krk_currentThread.stackTop = krk_currentThread.stack + before; + if (IS_NONE(result) && (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) { + render_error("Exception occurred in plugin: %s", AS_INSTANCE(krk_currentThread.currentException)->_class->name->chars); + render_commandline_message("\n"); + krk_dumpTraceback(); + goto _syntaxError; + } else if (!IS_NONE(result) && !IS_INTEGER(result)) { + render_error("Instead of an integer, got %s", krk_typeName(result)); + render_commandline_message("\n"); + goto _syntaxError; + } + s->state.state = IS_NONE(result) ? -1 : AS_INTEGER(result); + + if (s->state.state != 0) { if (line_no == -1) return; rehighlight_search(line); if (!is_original) { redraw_line(line_no); } - if (line_no + 1 < env->line_count && env->lines[line_no+1]->istate != state.state) { + if (line_no + 1 < env->line_count && env->lines[line_no+1]->istate != s->state.state) { line_no++; line = env->lines[line_no]; - line->istate = state.state; + line->istate = s->state.state; if (env->loading) return; is_original = 0; goto _next; @@ -1515,6 +1003,14 @@ void recalculate_syntax(line_t * line, int line_no) { _next: (void)0; } + +_syntaxError: + krk_resetStack(); + fprintf(stderr,"This syntax highlighter will be disabled in this environment."); + env->syntax = NULL; + cancel_background_tasks(env); + pause_for_key(); + redraw_all(); } /** @@ -1533,40 +1029,66 @@ void recalculate_tabs(line_t * line) { } /** - * TODO: + * The next section contains the basic editing primitives. All other + * actions are built out of these primitives, and they are the basic + * instructions that get stored in history to be undone (or redone). * - * The line editing functions should probably take a buffer_t * - * so that they can act on buffers other than the active one. + * Primitives may recalculate syntax or redraw lines, if needed, but only + * when conditions for redrawing are met (such as not being in `slowop`, + * or loading the file; also replaying history, or when loading files). + * + * At the moment, primitives and most other functions do not take the current + * buffer (environment) as an argument and instead rely on a global variable; + * this should definitely be fixed at some point... */ -void recursive_history_free(history_t * root) { +/** + * When a new action that produces history happens and there is forward + * history that can be redone, we need to erase it as our tree has branched. + * If we wanted, we could actually story things in a tree structure so that + * the new branch and the old branch can both stick around, and that should + * probably be explored in the future... + */ +void history_free(history_t * root) { if (!root->next) return; + /* Find the last entry so we can free backwards */ history_t * n = root->next; - recursive_history_free(n); - - switch (n->type) { - case HISTORY_REPLACE_LINE: - free(n->contents.remove_replace_line.contents); - /* fall-through */ - case HISTORY_REMOVE_LINE: - free(n->contents.remove_replace_line.old_contents); - break; - default: - /* Nothing extra to free */ - break; + while (n->next) { + n = n->next; + } + + /* Free everything after the root, including stored content. */ + while (n != root) { + history_t * p = n->previous; + switch (n->type) { + case HISTORY_REPLACE_LINE: + free(n->contents.remove_replace_line.contents); + /* fall-through */ + case HISTORY_REMOVE_LINE: + free(n->contents.remove_replace_line.old_contents); + break; + default: + /* Nothing extra to free */ + break; + } + free(n); + n = p; } - free(n); root->next = NULL; } +/** + * This macro is called by primitives to insert history elements for each + * primitive action when performing in edit modes. + */ #define HIST_APPEND(e) do { \ e->col = env->col_no; \ e->line = env->line_no; \ if (env->history) { \ e->previous = env->history; \ - recursive_history_free(env->history); \ + history_free(env->history); \ env->history->next = e; \ e->next = NULL; \ } \ @@ -1575,10 +1097,15 @@ void recursive_history_free(history_t * root) { /** * Mark a point where a complete set of actions has ended. + * Individual history entries include things like "insert one character" + * but a user action that should be undone is "insert several characters"; + * breaks should be inserted after a series of primitives when there is + * a clear "end", such as when switching out of insert mode after typing. */ void set_history_break(void) { if (!global_config.history_enabled) return; + /* Do not produce duplicate breaks, or add breaks if we are at a sentinel. */ if (env->history->type != HISTORY_BREAK && env->history->type != HISTORY_SENTINEL) { history_t * e = malloc(sizeof(history_t)); e->type = HISTORY_BREAK; @@ -1587,7 +1114,15 @@ void set_history_break(void) { } /** - * Insert a character into an existing line. + * (Primitive) Insert a character into an existing line. + * + * This is the most basic primitive action: Take a line, and insert a codepoint + * into it at a given offset. If `lineno` is not -1, the line is assumed to + * be part of the active buffer. If inserting a character means the line needs + * to grow, then it will be reallocated, so the return value of the new line + * must ALWAYS be used. This primitive will NOT automatically update the + * buffer with the new pointer, so if you are calling insert on a buffer you + * MUST update env->lines[lineno-1] yourself. */ __attribute__((warn_unused_result)) line_t * line_insert(line_t * line, char_t c, int offset, int lineno) { @@ -1632,12 +1167,18 @@ __attribute__((warn_unused_result)) line_t * line_insert(line_t * line, char_t c } /** - * Delete a character from a line + * (Primitive) Delete a character from a line. + * + * Remove the character at the given offset. We never shrink lines, so this + * does not have a return value, and delete should never be called during + * a loading operation (though it may be called during a history replay). */ void line_delete(line_t * line, int offset, int lineno) { /* Can't delete character before start of line. */ if (offset == 0) return; + /* Can't delete past end of line either */ + if (offset > line->actual) return; if (!env->loading && global_config.history_enabled && lineno != -1) { history_t * e = malloc(sizeof(history_t)); @@ -1662,7 +1203,12 @@ void line_delete(line_t * line, int offset, int lineno) { } /** - * Replace a character in a line + * (Primitive) Replace a character in a line. + * + * Replaces the codepoint at the given offset with a new character. Since this + * does not involve any size changes, it does not have a return value. + * Since a replacement character may be a tab, we do still need to recalculate + * character widths for tabs as they may change. */ void line_replace(line_t * line, char_t _c, int offset, int lineno) { @@ -1686,7 +1232,14 @@ void line_replace(line_t * line, char_t _c, int offset, int lineno) { } /** - * Remove a line from the active buffer + * (Primitive) Remove a line from the active buffer + * + * This primitive is only valid for a buffer. Delete a line, or if this is the + * only line in the buffer, clear it but keep the line around with no + * characters. We use the `line_delete` primitive to clear that line, + * otherwise we are our own primitive and produce history entries. + * + * While we do not shrink the `lines` array, it is returned here anyway. */ line_t ** remove_line(line_t ** lines, int offset) { @@ -1698,6 +1251,8 @@ line_t ** remove_line(line_t ** lines, int offset) { return lines; } + /* When a line is removed, we need to keep its contents so we + * can un-remove it on redo... */ if (!env->loading && global_config.history_enabled) { history_t * e = malloc(sizeof(history_t)); e->type = HISTORY_REMOVE_LINE; @@ -1722,7 +1277,11 @@ line_t ** remove_line(line_t ** lines, int offset) { } /** - * Add a new line to the active buffer. + * (Primitive) Add a new line to the active buffer. + * + * Inserts a new line into a buffer at the given line offset. + * Since this grows the buffer, it will return the new line array + * after reallocation if needed. */ line_t ** add_line(line_t ** lines, int offset) { @@ -1767,7 +1326,10 @@ line_t ** add_line(line_t ** lines, int offset) { } /** - * Replace a line with data from another line (used by paste to paste yanked lines) + * (Primitive) Replace a line with data from another line. + * + * This is only called when pasting yanks after calling `add_line`, + * but it allows us to have simpler history for that action. */ void replace_line(line_t ** lines, int offset, line_t * replacement) { @@ -1796,8 +1358,14 @@ void replace_line(line_t ** lines, int offset, line_t * replacement) { } /** - * Merge two consecutive lines. - * lineb is the offset of the second line. + * (Primitive) Merge two consecutive lines. + * + * Take two lines in a buffer and turn them into one line. + * `lineb` is the offset of the second line... or the + * line number of the first line, depending on which indexing + * system you prefer to think about. This won't grow `lines`, + * but it will likely modify it and can reallocate individual + * lines as well. */ line_t ** merge_lines(line_t ** lines, int lineb) { @@ -1813,14 +1381,15 @@ line_t ** merge_lines(line_t ** lines, int lineb) { } /* If there isn't enough space in linea hold both... */ - while (lines[linea]->available < lines[linea]->actual + lines[lineb]->actual) { - /* ... allocate more space until it fits */ - if (lines[linea]->available == 0) { - lines[linea]->available = 8; - } else { - lines[linea]->available *= 2; + if (lines[linea]->available < lines[linea]->actual + lines[lineb]->actual) { + while (lines[linea]->available < lines[linea]->actual + lines[lineb]->actual) { + /* ... allocate more space until it fits */ + if (lines[linea]->available == 0) { + lines[linea]->available = 8; + } else { + lines[linea]->available *= 2; + } } - /* XXX why not just do this once after calculating appropriate size */ lines[linea] = realloc(lines[linea], sizeof(line_t) + sizeof(char_t) * lines[linea]->available); } @@ -1851,7 +1420,11 @@ line_t ** merge_lines(line_t ** lines, int lineb) { } /** - * Split a line into two lines at the given column + * (Primitive) Split a line into two lines at the given column. + * + * Takes one line and makes it two lines. There are some optimizations + * if you are trying to "split" at the first or last column, which + * are both just treated as add_line. */ line_t ** split_line(line_t ** lines, int line, int split) { @@ -1883,9 +1456,9 @@ line_t ** split_line(line_t ** lines, int line, int split) { memmove(&lines[line+2], &lines[line+1], sizeof(line_t *) * (env->line_count - line)); } - /* I have no idea what this is doing */ int remaining = lines[line]->actual - split; + /* This is some wacky math to get a good power-of-two */ int v = remaining; v--; v |= v >> 1; @@ -1920,6 +1493,19 @@ line_t ** split_line(line_t ** lines, int line, int split) { return lines; } +/** + * Primitives end here. Everything after this point that wants to + * perform modifications must be built on these primitives and + * should never directly modify env->lines or the contents thereof, + * outside of syntax highlighting flag changes. + */ + +/** + * The following section is where we implement "smart" automatic + * indentation. A lot of this is hacked-together nonsense and "smart" + * is a bit of an overstatement. + */ + /** * Understand spaces and comments and check if the previous line * ended with a brace or a colon. @@ -1937,6 +1523,13 @@ int line_ends_with_brace(line_t * line) { return (line->text[i].codepoint == '{' || line->text[i].codepoint == ':') ? i+1 : 0; } +/** + * Determine if a given line is a comment by checking its initial + * syntax highlighting state value. This is used by automatic indentation + * to continue block comments in languages like C. + * + * TODO: Why isn't this a syntax-> method? + */ int line_is_comment(line_t * line) { if (!env->syntax) return 0; @@ -1953,6 +1546,12 @@ int line_is_comment(line_t * line) { return 0; } +/** + * Figure out where the indentation for a brace belongs by finding + * where the start of the line is after whitespace. This is called + * by default when we insert } and tries to align it with the indentation + * of the matching opening {. + */ int find_brace_line_start(int line, int col) { int ncol = col - 1; while (ncol > 0) { @@ -1981,7 +1580,11 @@ int find_brace_line_start(int line, int col) { } /** - * Add indentation from the previous (temporally) line + * Add indentation from the previous (temporally) line. + * + * By "temporally", we mean not necessarily the line above, but + * potentially the line below if we are inserting a line above + * the cursor. */ void add_indent(int new_line, int old_line, int ignore_brace) { if (env->indent) { @@ -2045,7 +1648,7 @@ void add_indent(int new_line, int old_line, int ignore_brace) { } if (old_line < new_line && !ignore_brace && line_ends_with_brace(env->lines[old_line])) { if (env->tabs) { - char_t c; + char_t c = {0}; c.codepoint = '\t'; c.display_width = env->tabstop; env->lines[new_line] = line_insert(env->lines[new_line], c, env->col_no-1, new_line); @@ -2053,10 +1656,9 @@ void add_indent(int new_line, int old_line, int ignore_brace) { changed = 1; } else { for (int j = 0; j < env->tabstop; ++j) { - char_t c; + char_t c = {0}; c.codepoint = ' '; c.display_width = 1; - c.flags = FLAG_SELECT; env->lines[new_line] = line_insert(env->lines[new_line], c, env->col_no-1, new_line); env->col_no++; } @@ -2083,7 +1685,9 @@ void add_indent(int new_line, int old_line, int ignore_brace) { } /** - * Initialize a buffer with default values + * Initialize a buffer with default values. + * + * Should be called after creating a buffer. */ void setup_buffer(buffer_t * env) { /* If this buffer was already initialized, clear out its line data */ @@ -2198,7 +1802,7 @@ int codepoint_width(wchar_t codepoint) { return 1; } /* Skip wcwidth for anything under 256 */ - if (codepoint > 256) { + if (codepoint > 127) { if (global_config.can_unicode) { /* Higher codepoints may be wider (eg. Japanese) */ int out = wcwidth(codepoint); @@ -2210,6 +1814,13 @@ int codepoint_width(wchar_t codepoint) { return 1; } +/** + * The following section contains methods for crafting terminal escapes + * for rendering the display. We do not use curses or any similar + * library, so we have to generate these sequences ourself based on + * some assumptions about the target terminal. + */ + /** * Move the terminal cursor */ @@ -2217,6 +1828,11 @@ void place_cursor(int x, int y) { printf("\033[%d;%dH", y, x); } +/** + * Given two color codes from a theme, convert them to an escape + * sequence to set the foreground and background color. This allows + * us to support basic 16, xterm 256, and true color in themes. + */ char * color_string(const char * fg, const char * bg) { static char output[100]; char * t = output; @@ -2286,10 +1902,8 @@ void clear_to_end(void) { } /** - * For terminals without bce, - * prepaint the whole line, so we don't have to track - * where the cursor is for everything. Inefficient, - * but effective. + * For terminals without bce, prepaint the whole line, so we don't have to track + * where the cursor is for everything. Inefficient, but effective. */ void paint_line(const char * bg) { if (!global_config.can_bce) { @@ -2381,7 +1995,7 @@ void restore_cursor(void) { void mouse_enable(void) { if (global_config.can_mouse) { printf("\033[?1000h"); - if (global_config.use_sgr_mouse) { + if (global_config.can_sgrmouse) { printf("\033[?1006h"); } } @@ -2392,7 +2006,7 @@ void mouse_enable(void) { */ void mouse_disable(void) { if (global_config.can_mouse) { - if (global_config.use_sgr_mouse) { + if (global_config.can_sgrmouse) { printf("\033[?1006l"); } printf("\033[?1000l"); @@ -2462,6 +2076,10 @@ void unset_bracketed_paste(void) { /** * Get the name of just a file from a full path. * Returns a pointer within the original string. + * + * Called in a few different places where the name of a file + * is needed from its full path, such as drawing tab names or + * building HTML files. */ char * file_basename(char * file) { char * c = strrchr(file, '/'); @@ -2503,8 +2121,12 @@ int draw_tab_name(buffer_t * _env, char * out, int max_width, int * width) { } while (*t) { + /* File names can definitely by UTF-8, and we need to + * understand their display width... */ if (!decode(&state, &c, (unsigned char)*t)) { + /* But our displayed tab name is also just stored + * as UTF-8 again, so we essentially rebuild it... */ char tmp[7]; int size = to_eight(c, tmp); if (bytes + size > 62) break; @@ -2532,7 +2154,7 @@ int draw_tab_name(buffer_t * _env, char * out, int max_width, int * width) { } /** - * Redaw the tabbar, with a tab for each buffer. + * Redraw the tabbar, with a tab for each buffer. * * The active buffer is highlighted. */ @@ -2570,6 +2192,20 @@ void redraw_tabbar(void) { set_underline(); } + if (global_config.overlay_mode == OVERLAY_MODE_FILESEARCH) { + if (global_config.command_buffer->actual) { + char * f = _env->file_name ? file_basename(_env->file_name) : ""; + /* TODO: Support unicode input here; needs conversion */ + int i = 0; + for (; i < global_config.command_buffer->actual && + f[i] == global_config.command_buffer->text[i].codepoint; ++i); + if (global_config.command_buffer->actual == i) { + set_colors(COLOR_SEARCH_FG, COLOR_SEARCH_BG); + } + } + } + + char title[64]; int size = 0; int filled = draw_tab_name(_env, title, global_config.term_width - offset, &size); @@ -2599,7 +2235,8 @@ void redraw_tabbar(void) { } /** - * Braindead log10 implementation for the line numbers + * Braindead log10 implementation for figuring out the width of the + * line number column. */ int log_base_10(unsigned int v) { int r = (v >= 1000000000) ? 9 : (v >= 100000000) ? 8 : (v >= 10000000) ? 7 : @@ -2609,10 +2246,12 @@ int log_base_10(unsigned int v) { } /** - * Render a line of text + * Render a line of text. * * This handles rendering the actual text content. A full line of text - * also includes a line number and some padding. + * also includes a line number and some padding, but those elements + * are rendered elsewhere; this method can be used for lines that are + * not attached to a buffer such as command input lines. * * width: width of the text display region (term width - line number width) * offset: how many cells into the line to start rendering at @@ -2629,8 +2268,7 @@ void render_line(line_t * line, int width, int offset, int line_no) { /* * When we are rendering in the middle of a wide character, - * we render -'s to fill the remaining amount of the - * charater's width + * we render -'s to fill the remaining amount of the character's width. */ int remainder = 0; @@ -2803,13 +2441,20 @@ void render_line(line_t * line, int width, int offset, int line_no) { } } + /** + * Determine what color the rest of the line should be. + */ if (env->mode != MODE_LINE_SELECTION) { + /* If we are not selecting, then use the normal background or highlight + * the current line if that feature is enabled. */ if (line->is_current) { set_colors(COLOR_FG, COLOR_ALT_BG); } else { set_colors(COLOR_FG, COLOR_BG); } } else { + /* If this line was empty but was part of the selection, we didn't + * set the selection color already, so we need to do that here. */ if (!line->actual) { if (env->line_no == line_no || (env->start_line > env->line_no && @@ -2821,6 +2466,10 @@ void render_line(line_t * line, int width, int offset, int line_no) { } } + /** + * In column modes, we may need to draw a column select beyond the end + * of a given line, so we need to draw up to that point first. + */ if ((env->mode == MODE_COL_SELECTION || env->mode == MODE_COL_INSERT) && line_no >= ((env->start_line < env->line_no) ? env->start_line : env->line_no) && line_no <= ((env->start_line < env->line_no) ? env->line_no : env->start_line) && @@ -2837,6 +2486,10 @@ void render_line(line_t * line, int width, int offset, int line_no) { set_colors(COLOR_FG, COLOR_BG); } + /* + * `maxcolumn` renders the background outside of the requested line length + * in a different color, with a line at the border between the two. + */ if (env->maxcolumn && line_no > -1 /* ensures we don't do this for command line */) { /* Fill out the normal background */ @@ -2850,7 +2503,7 @@ void render_line(line_t * line, int width, int offset, int line_no) { j++; set_colors(COLOR_ALT_FG, COLOR_ALT_BG); if (global_config.can_unicode) { - printf("▏"); + printf("▏"); /* Should this be configurable? */ } else { printf("|"); } @@ -2860,7 +2513,12 @@ void render_line(line_t * line, int width, int offset, int line_no) { set_colors(COLOR_ALT_FG, COLOR_ALT_BG); } - if (env->left + env->width == global_config.term_width && global_config.can_bce) { + /** + * Clear out the rest of the line. If we are the only buffer or the right split, + * and our terminal supports `bce`, we can just bce; otherwise write spaces + * until we reach the right side of the screen. + */ + if (global_config.can_bce && (line_no == -1 || env->left + env->width == global_config.term_width)) { clear_to_end(); } else { /* Paint the rest of the line */ @@ -2993,12 +2651,18 @@ void redraw_line(int x) { draw_line_number(x); + int should_shift = x + 1 == env->line_no || global_config.horizontal_shift_scrolling || + ((env->mode == MODE_COL_SELECTION || env->mode == MODE_COL_INSERT) && + x + 1 >= ((env->start_line < env->line_no) ? env->start_line : env->line_no) && + x + 1 <= ((env->start_line < env->line_no) ? env->line_no : env->start_line)); + + /* * Draw the line text * If this is the active line, the current character cell offset should be used. * (Non-active lines are not shifted and always render from the start of the line) */ - render_line(env->lines[x], env->width - gutter_width() - num_width(), (x + 1 == env->line_no || global_config.horizontal_shift_scrolling) ? env->coffset : 0, x+1); + render_line(env->lines[x], env->width - gutter_width() - num_width(), should_shift ? env->coffset : 0, x+1); } @@ -3093,7 +2757,7 @@ void redraw_alt_buffer(buffer_t * buf) { * Basically wcswidth() but implemented internally using our * own utf-8 decoder to ensure it works properly. */ -int display_width_of_string(char * str) { +int display_width_of_string(const char * str) { uint8_t * s = (uint8_t *)str; int out = 0; @@ -3157,10 +2821,11 @@ int statusbar_build_right(char * right_hand) { * The status bar shows the name of the file, whether it has modifications, * and (in the future) what syntax highlighting mode is enabled. * - * The right side of the tatus bar shows the line number and column. + * The right side of the status bar shows the line number and column. */ void redraw_statusbar(void) { if (global_config.hide_statusbar) return; + if (!global_config.has_terminal) return; if (!env) return; /* Hide cursor while rendering */ hide_cursor(); @@ -3218,6 +2883,10 @@ void redraw_statusbar(void) { ADD("complete"); } + if (global_config.background_task) { + ADD("working"); + } + #undef ADD uint8_t * file_name = (uint8_t *)(env->file_name ? env->file_name : "[No Name]"); @@ -3258,6 +2927,7 @@ void redraw_statusbar(void) { * Redraw the navigation numbers on the right side of the command line */ void redraw_nav_buffer() { + if (!global_config.has_terminal) return; if (nav_buffer) { store_cursor(); place_cursor(global_config.term_width - nav_buffer - 2, global_config.term_height); @@ -3274,6 +2944,7 @@ void redraw_nav_buffer() { * or shows the INSERT (or VISUAL in the future) mode name. */ void redraw_commandline(void) { + if (!global_config.has_terminal) return; if (!env) return; /* Hide cursor while rendering */ @@ -3382,7 +3053,9 @@ BIM_ACTION(redraw_all, 0, } redraw_statusbar(); redraw_commandline(); - if (global_config.overlay_mode == OVERLAY_MODE_COMMAND || global_config.overlay_mode == OVERLAY_MODE_SEARCH) { + if (global_config.overlay_mode == OVERLAY_MODE_COMMAND || + global_config.overlay_mode == OVERLAY_MODE_SEARCH || + global_config.overlay_mode == OVERLAY_MODE_FILESEARCH) { render_command_input_buffer(); } } @@ -3484,7 +3157,7 @@ void render_status_message(char * message, ...) { } /** - * Draw an errormessage to the command line. + * Draw an error message to the command line. */ void render_error(char * message, ...) { /* varargs setup */ @@ -3508,6 +3181,7 @@ void render_error(char * message, ...) { /* Draw the message */ printf("%s", buf); + global_config.had_error = 1; } else { printf("bim: error during startup: %s\n", buf); } @@ -3573,10 +3247,9 @@ void highlight_matching_paren(void) { */ void unhighlight_matching_paren(void) { if (env->highlighting_paren > 0 && env->highlighting_paren <= env->line_count) { - for (int i = env->highlighting_paren - 1; i <= env->highlighting_paren + 1; ++i) { - if (i >= 1 && i <= env->line_count) { - recalculate_syntax(env->lines[i-1], i-1); - redraw_line(i-1); + for (int i = 0; i < env->line_count; i++) { + for (int j = 0; j < env->lines[i]->actual; ++j) { + env->lines[i]->text[j].flags &= ~(FLAG_SELECT); } } env->highlighting_paren = -1; @@ -3789,7 +3462,7 @@ BIM_ACTION(goto_line, ARG_IS_CUSTOM, /** - * Processs (part of) a file and add it to a buffer. + * Process (part of) a file and add it to a buffer. */ void add_buffer(uint8_t * buf, int size) { for (int i = 0; i < size; ++i) { @@ -3865,7 +3538,7 @@ void set_syntax_by_name(const char * name) { for (int i = 0; i < env->line_count; ++i) { env->lines[i]->istate = -1; for (int j = 0; j < env->lines[i]->actual; ++j) { - env->lines[i]->text[j].flags = 0; + env->lines[i]->text[j].flags &= (3 << 5); } } env->syntax = NULL; @@ -3878,9 +3551,7 @@ void set_syntax_by_name(const char * name) { for (int i = 0; i < env->line_count; ++i) { env->lines[i]->istate = -1; } - for (int i = 0; i < env->line_count; ++i) { - recalculate_syntax(env->lines[i],i); - } + schedule_complete_recalc(); redraw_all(); return; } @@ -3948,6 +3619,7 @@ void read_directory_into_buffer(char * file) { files[count].filename = strdup(ent->d_name); count++; ent = readdir(dirp); + free(tmp); } closedir(dirp); @@ -3967,9 +3639,7 @@ void read_directory_into_buffer(char * file) { env->file_name = strdup(file); env->syntax = find_syntax_calculator("dirent"); - for (int i = 0; i < env->line_count; ++i) { - recalculate_syntax(env->lines[i],i); - } + schedule_complete_recalc(); env->readonly = 1; env->loading = 0; env->mode = MODE_DIRECTORY_BROWSE; @@ -4026,16 +3696,69 @@ int line_matches(line_t * line, char * string) { } void run_onload(buffer_t * env) { - if (has_function("onload:*")) { - run_function("onload:*"); + /* TODO */ + KrkValue onLoad; + if (krk_tableGet_fast(&krk_currentThread.module->fields, S("onload"), &onLoad)) { + krk_push(onLoad); + krk_push(krk_dict_of(0,NULL,0)); + + if (env->file_name) { + krk_attachNamedObject(AS_DICT(krk_peek(0)), "filename", + (KrkObj*)krk_copyString(env->file_name,strlen(env->file_name))); + } + + if (env->syntax && env->syntax->krkClass) { + krk_attachNamedObject(AS_DICT(krk_peek(0)), "highlighter", + (KrkObj*)env->syntax->krkClass); + } + + + krk_callSimple(onLoad, 1, 1); + krk_resetStack(); } - if (env->syntax) { - char tmp[512]; - sprintf(tmp, "onload:%s", env->syntax->name); - if (has_function(tmp)) { - run_function(tmp); +} + +static void render_syntax_async(background_task_t * task) { + buffer_t * old_env = env; + env = task->env; + int line_no = task->_private_i; + + if (env->line_count && line_no < env->line_count) { + int tmp = env->loading; + env->loading = 1; + recalculate_syntax(env->lines[line_no], line_no); + env->loading = tmp; + if (env == old_env) { + redraw_line(line_no); + } + } + env = old_env; +} + +static void schedule_complete_recalc(void) { + if (env->line_count < 1000) { + for (int i = 0; i < env->line_count; ++i) { + recalculate_syntax(env->lines[i], i); + } + return; + } + + /* TODO see if there's already a redraw scheduled */ + for (int i = 0; i < env->line_count; ++i) { + background_task_t * task = malloc(sizeof(background_task_t)); + task->env = env; + task->_private_i = i; + task->func = render_syntax_async; + task->next = NULL; + if (global_config.tail_task) { + global_config.tail_task->next = task; + } + global_config.tail_task = task; + if (!global_config.background_task) { + global_config.background_task = task; } } + redraw_statusbar(); } /** @@ -4084,6 +3807,12 @@ void open_file(char * file) { } } + if (file_is_open(_file)) { + if (file != _file) free(_file); + close_buffer(); + return; + } + struct stat statbuf; if (!stat(_file, &statbuf) && S_ISDIR(statbuf.st_mode)) { read_directory_into_buffer(_file); @@ -4106,6 +3835,7 @@ void open_file(char * file) { if (env->syntax && env->syntax->prefers_spaces) { env->tabs = 0; } + update_biminfo(env, 1); run_onload(env); return; } @@ -4143,9 +3873,7 @@ void open_file(char * file) { if (!env->syntax && global_config.syntax_fallback) { set_syntax_by_name(global_config.syntax_fallback); } - for (int i = 0; i < env->line_count; ++i) { - recalculate_syntax(env->lines[i],i); - } + schedule_complete_recalc(); } /* Try to automatically figure out tabs vs. spaces */ @@ -4164,7 +3892,36 @@ void open_file(char * file) { env->tabs = env->syntax->prefers_spaces; } - /* TODO figure out tabstop for spaces? */ + if (spaces) { + int one = 0, two = 0, three = 0, four = 0; /* If you use more than that, I don't like you. */ + int lastCount = 0; + for (int i = 0; i < env->line_count; ++i) { + if (env->lines[i]->actual > 1 && !line_is_comment(env->lines[i])) { + /* Count spaces at beginning */ + int c = 0, diff = 0; + while (c < env->lines[i]->actual && env->lines[i]->text[c].codepoint == ' ') c++; + if (c > lastCount) { + diff = c - lastCount; + } else if (c < lastCount) { + diff = lastCount - c; + } + if (diff == 1) one++; + if (diff == 2) two++; + if (diff == 3) three++; + if (diff == 4) four++; + lastCount = c; + } + } + if (four > three && four > two && four > one) { + env->tabstop = 4; + } else if (three > two && three > one) { + env->tabstop = 3; + } else if (two > one) { + env->tabstop = 2; + } else { + env->tabstop = 1; + } + } env->loading = 0; @@ -4190,6 +3947,8 @@ void open_file(char * file) { } } + update_biminfo(env, 1); + fclose(f); run_onload(env); @@ -4206,6 +3965,7 @@ void quit(const char * message) { show_cursor(); unset_bracketed_paste(); unset_alternate_screen(); + krk_freeVM(); if (message) { fprintf(stdout, "%s\n", message); } @@ -4239,7 +3999,7 @@ void try_quit(void) { * Switch to the previous buffer */ BIM_ACTION(previous_tab, 0, - "Switch the previoius tab" + "Switch the previous tab" )(void) { buffer_t * last = NULL; for (int i = 0; i < buffers_len; i++) { @@ -4350,7 +4110,7 @@ int git_examine(char * filename) { } else if (from_count > 0 && to_count == 0) { /* * No +, all - means we have a deletion. We mark the next line such that it has a red bar at the top - * Note that to_line is one lower than the affacted line, so we don't need to mes with indexes. + * Note that to_line is one lower than the affected line, so we don't need to mess with indexes. */ if (to_line >= env->line_count) continue; env->lines[to_line]->rev_status = 4; /* Red */ @@ -4422,7 +4182,19 @@ void write_file(char * file) { return; } - FILE * f = fopen(file, "w+"); + char * _file = file; + + if (file[0] == '~') { + char * home = getenv("HOME"); + if (home) { + _file = malloc(strlen(file) + strlen(home) + 4); /* Paranoia */ + sprintf(_file, "%s%s", home, file+1); + } + } + + + FILE * f = fopen(_file, "w+"); + if (file != _file) free(_file); if (!f) { render_error("Failed to open file for writing."); @@ -4750,9 +4522,9 @@ BIM_ACTION(leave_insert, 0, if (env->col_no == 0) env->col_no = 1; set_preferred_column(); } - set_history_break(); - env->mode = MODE_NORMAL; - redraw_commandline(); + set_history_break(); + env->mode = MODE_NORMAL; + redraw_commandline(); } /** @@ -5158,9 +4930,7 @@ int convert_to_html(void) { recalculate_tabs(env->lines[i]); } env->syntax = match_syntax(".htm"); - for (int i = 0; i < env->line_count; ++i) { - recalculate_syntax(env->lines[i],i); - } + schedule_complete_recalc(); return 0; } @@ -5198,12 +4968,7 @@ int _prefix_command_run_script(char * cmd) { dup2(out[1], STDOUT_FILENO); dup2(in[0], STDIN_FILENO); dup2(fileno(dev_null), STDERR_FILENO); - if (*cmd == '!') { - system(&cmd[1]); /* Yes we can just do this */ - } else { - char * const args[] = {"python3","-c",&cmd[1],NULL}; - execvp("python3",args); - } + system(&cmd[1]); /* Yes we can just do this */ exit(1); } else if (child < 0) { render_error("Failed to fork"); @@ -5277,12 +5042,7 @@ int _prefix_command_run_script(char * cmd) { set_buffered(); /* Call the shell and wait for completion */ - if (*cmd == '!') { - system(&cmd[1]); - } else { - setenv("PYCMD",&cmd[1],1); - system("python3 -c \"$PYCMD\""); - } + system(&cmd[1]); /* Return to the editor, wait for user to press enter. */ set_unbuffered(); @@ -5298,16 +5058,6 @@ int _prefix_command_run_script(char * cmd) { return 0; } -BIM_PREFIX_COMMAND(bang,"!","Executes shell commands.") { - (void)argc, (void)argv; - return _prefix_command_run_script(cmd); -} - -BIM_PREFIX_COMMAND(tick,"`","Executes Python commands.") { - (void)argc, (void)argv; - return _prefix_command_run_script(cmd); -} - int replace_text(int range_top, int range_bot, char divider, char * needle) { char * c = needle; char * replacement = NULL; @@ -5443,6 +5193,7 @@ BIM_COMMAND(e,"e","Open a file") { SWAP(history_t *, env->history, new_env->history); buffer_close(new_env); /* Should probably also free, this needs editing. */ + schedule_complete_recalc(); redraw_all(); } return 0; @@ -5537,11 +5288,63 @@ BIM_COMMAND(tabn,"tabn","Next tab") { return 0; } +BIM_COMMAND(tabm,"tabm","Move the current tab to a new index") { + /* Figure out the current index */ + int i = 0; + for (; i < buffers_len; i++) { + if (buffers[i] == env) break; + } + + if (i == buffers_len) { + render_status_message("(invalid state?)"); + return 1; + } + + if (argc < 2) { + render_status_message("tab = %d", i); + return 1; + } + + int newIndex = atoi(argv[1]); + + if (newIndex == i) { + return 0; + } + + /* Okay, this is stupid, but, remove the buffer */ + memmove(&buffers[i], &buffers[i+1], sizeof(*buffers) * (buffers_len - i -1)); + /* Then make space at the destination */ + memmove(&buffers[newIndex+1], &buffers[newIndex], sizeof(*buffers) * (buffers_len - newIndex -1)); + + buffers[newIndex] = env; + + redraw_tabbar(); + update_title(); + return 0; +} + +BIM_COMMAND(tab,"tab", "Open a specific tab") { + if (argc < 2) return bim_command_tabm("tabm", argc, argv); + int i = atoi(argv[1]); + + if (i < 0 || i > buffers_len) { + render_error("Invalid tab index"); + return 1; + } + + env = buffers[i]; + if (left_buffer && (left_buffer != env && right_buffer != env)) unsplit(); + redraw_all(); + update_title(); + return 0; +} + BIM_COMMAND(tabindicator,"tabindicator","Set the tab indicator") { if (argc < 2) { render_status_message("tabindicator=%s", global_config.tab_indicator); return 0; } + if (!global_config.can_unicode && strlen(argv[1]) != 1) return 0; if (display_width_of_string(argv[1]) != 1) { render_error("Can't set '%s' as indicator, must be one cell wide.", argv[1]); return 1; @@ -5556,6 +5359,7 @@ BIM_COMMAND(spaceindicator,"spaceindicator","Set the space indicator") { render_status_message("spaceindicator=%s", global_config.space_indicator); return 0; } + if (!global_config.can_unicode && strlen(argv[1]) != 1) return 0; if (display_width_of_string(argv[1]) != 1) { render_error("Can't set '%s' as indicator, must be one cell wide.", argv[1]); return 1; @@ -5565,17 +5369,6 @@ BIM_COMMAND(spaceindicator,"spaceindicator","Set the space indicator") { return 0; } -BIM_COMMAND(global_sgr,"global.sgr_mouse","Enable SGR mouse escapes") { - if (argc < 2) { - render_status_message("global.sgr_mouse=%d", global_config.use_sgr_mouse); - } else { - if (global_config.has_terminal) mouse_disable(); - global_config.use_sgr_mouse = !!atoi(argv[1]); - if (global_config.has_terminal) mouse_enable(); - } - return 0; -} - BIM_COMMAND(global_git,"global.git","Show or change the default status of git integration") { if (argc < 2) { render_status_message("global.git=%d", global_config.check_git); @@ -5618,7 +5411,7 @@ BIM_COMMAND(indent,"indent","Enable smart indentation") { return 0; } -BIM_COMMAND(noindent,"noindent","Disable smrat indentation") { +BIM_COMMAND(noindent,"noindent","Disable smart indentation") { env->indent = 0; redraw_statusbar(); return 0; @@ -5645,7 +5438,9 @@ BIM_COMMAND(noh,"noh","Clear search term") { free(global_config.search); global_config.search = NULL; for (int i = 0; i < env->line_count; ++i) { - recalculate_syntax(env->lines[i],i); + for (int j = 0; j < env->lines[i]->actual; ++j) { + env->lines[i]->text[j].flags &= ~(FLAG_SEARCH); + } } redraw_text(); } @@ -5709,23 +5504,21 @@ BIM_COMMAND(version,"version","Show version information.") { return 0; } -void load_colorscheme_script(const char * name) { - static char name_copy[512]; - char tmp[1024]; - snprintf(tmp, 1023, "theme:%s", name); - if (!run_function(tmp)) { - sprintf(name_copy, "%s", name); - current_theme = name_copy; - } -} - BIM_COMMAND(theme,"theme","Set color theme") { if (argc < 2) { render_status_message("theme=%s", current_theme); } else { for (struct theme_def * d = themes; themes && d->name; ++d) { if (!strcmp(argv[1], d->name)) { - d->load(d->name); + ptrdiff_t before = krk_currentThread.stackTop - krk_currentThread.stack; + KrkValue result = krk_callSimple(OBJECT_VAL(d->callable), 0, 0); + krk_currentThread.stackTop = krk_currentThread.stack + before; + if (IS_NONE(result) && (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) { + render_error("Exception occurred in theme: %s", AS_INSTANCE(krk_currentThread.currentException)->_class->name->chars); + krk_dumpTraceback(); + int key = 0; + while ((key = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT); + } redraw_all(); return 0; } @@ -5804,36 +5597,17 @@ BIM_COMMAND(syntax,"syntax","Show or set the active syntax highlighter") { } BIM_COMMAND(recalc,"recalc","Recalculate syntax for the entire file.") { - for (int i = 0; i < env->line_count; ++i) { - env->lines[i]->istate = -1; - } - env->loading = 1; - for (int i = 0; i < env->line_count; ++i) { - recalculate_syntax(env->lines[i],i); - } - env->loading = 0; + schedule_complete_recalc(); redraw_all(); return 0; } -BIM_COMMAND(tabs,"tabs","Use tabs for indentation") { - env->tabs = 1; - redraw_statusbar(); - return 0; -} - -BIM_COMMAND(spaces,"spaces","Use spaces for indentation") { - env->tabs = 0; - redraw_statusbar(); - return 0; -} - BIM_COMMAND(tabstop,"tabstop","Show or set the tabstop (width of an indentation unit)") { if (argc < 2) { render_status_message("tabstop=%d", env->tabstop); } else { int t = atoi(argv[1]); - if (t > 0 && t < 32) { + if (t > 0 && t < 12) { env->tabstop = t; for (int i = 0; i < env->line_count; ++i) { recalculate_tabs(env->lines[i]); @@ -5846,6 +5620,24 @@ BIM_COMMAND(tabstop,"tabstop","Show or set the tabstop (width of an indentation return 0; } +BIM_COMMAND(spaces,"spaces","Use spaces for indentation") { + env->tabs = 0; + if (argc > 1) { + bim_command_tabstop("tabstop", argc, argv); + } + redraw_statusbar(); + return 0; +} + +BIM_COMMAND(tabs,"tabs","Use tabs for indentation") { + env->tabs = 1; + if (argc > 1) { + bim_command_tabstop("tabstop", argc, argv); + } + redraw_statusbar(); + return 0; +} + BIM_COMMAND(clearyear,"clearyank","Clear the yank buffer") { if (global_config.yanks) { for (unsigned int i = 0; i < global_config.yank_count; ++i) { @@ -5888,7 +5680,9 @@ BIM_COMMAND(hlparen,"hlparen","Show or set the configuration option to highlight global_config.highlight_parens = atoi(argv[1]); if (env) { for (int i = 0; i < env->line_count; ++i) { - recalculate_syntax(env->lines[i],i); + for (int j = 0; j < env->lines[i]->actual; ++j) { + env->lines[i]->text[j].flags &= (~FLAG_SELECT); + } } redraw_text(); place_cursor_actual(); @@ -6016,70 +5810,78 @@ BIM_COMMAND(buffers,"buffers","Show the open buffers") { BIM_COMMAND(keyname,"keyname","Press and key and get its name.") { int c; render_commandline_message("(press a key)"); - while ((c = bim_getkey(200)) == KEY_TIMEOUT); + while ((c = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT); render_commandline_message("%d = %s", c, name_from_key(c)); return 0; } +int isSubstitutionSymbol(int c) { + if (c >= '!' && c <= '/') return 1; + if (c >= ':' && c <= '@') return 1; + if (c >= '[' && c <= '`') return 1; + if (c >= '{' && c <= '~') return 1; + return 0; +} + +int alldigits(const char * c) { + while (*c) { + if (!isdigit(*c)) return 0; + c++; + } + return 1; +} + /** * Process a user command. */ +int process_krk_command(const char * cmd, KrkValue * outVal); int process_command(char * cmd) { - - if (*cmd == '#') return 0; - - /* First, check prefix commands */ - for (struct command_def * c = prefix_commands; prefix_commands && c->name; ++c) { - if (strstr(cmd, c->name) == cmd && - (!isalpha(cmd[strlen(c->name)]) || !isalpha(cmd[0]))) { - return c->command(cmd, 0, NULL); - } + if (cmd[0] == '-' && alldigits(&cmd[1])) { + goto_line(env->line_no-atoi(&cmd[1])); + return 0; + } else if (cmd[0] == '+' && alldigits(&cmd[1])) { + goto_line(env->line_no+atoi(&cmd[1])); + return 0; + } else if (alldigits(cmd)) { + goto_line(atoi(cmd)); + return 0; + } else if (cmd[0] == '!') { + return _prefix_command_run_script(cmd); + } else if (cmd[0] == 's' && isSubstitutionSymbol(cmd[1])) { + return bim_command_repsome(cmd, 0, NULL); + } else if (cmd[0] == '%' && cmd[1] == 's') { + return bim_command_repall(cmd, 0, NULL); } - char * argv[3] = {NULL, NULL, NULL}; - int argc = !!(*cmd); - char cmd_name[512] = {0}; - for (char * c = cmd; *c; ++c) { - if (c-cmd == 511) break; - if (*c == ' ') { - cmd_name[c-cmd] = '\0'; - argv[1] = c+1; - if (*argv[1]) argc++; - break; + /* See if it's a bim command in the classic format */ + { + char * argv[3] = {NULL, NULL, NULL}; + int argc = !!(*cmd); + char cmd_name[512] = {0}; + for (char * c = (char*)cmd; *c; ++c) { + if (c-cmd == 511) break; + if (*c == ' ') { + cmd_name[c-cmd] = '\0'; + argv[1] = c+1; + if (*argv[1]) argc++; + break; + } + cmd_name[c-cmd] = *c; } - cmd_name[c-cmd] = *c; - } - - argv[0] = cmd_name; - argv[argc] = NULL; - - if (argc < 1) { - /* no op */ - return 0; - } - /* Now check regular commands */ - for (struct command_def * c = regular_commands; regular_commands && c->name; ++c) { - if (!strcmp(argv[0], c->name)) { - return c->command(cmd, argc, argv); + argv[0] = cmd_name; + argv[argc] = NULL; + for (struct command_def * c = regular_commands; regular_commands && c->name; ++c) { + if (!strcmp(argv[0], c->name)) { + krk_resetStack(); + return c->command((char*)cmd, argc, argv); + } } } - global_config.break_from_selection = 1; + int retval = process_krk_command(cmd, NULL); - if (argv[0][0] == '-' && isdigit(argv[0][1])) { - goto_line(env->line_no-atoi(&argv[0][1])); - return 0; - } else if (argv[0][0] == '+' && isdigit(argv[0][1])) { - goto_line(env->line_no+atoi(&argv[0][1])); - return 0; - } else if (isdigit(*argv[0])) { - goto_line(atoi(argv[0])); - return 0; - } else { - render_error("Not an editor command: %s", argv[0]); - return 1; - } + return retval; } /** @@ -6189,13 +5991,6 @@ void command_tab_complete(char * buffer) { goto _accept_candidate; } - if (arg == 1 && (!strcmp(args[0], "call") || !strcmp(args[0], "trycall") || !strcmp(args[0], "showfunction"))) { - for (int i = 0; i < flex_user_functions_count; ++i) { - add_candidate(user_functions[i]->command); - } - goto _accept_candidate; - } - if (arg == 1 && (!strcmp(args[0], "mapkey"))) { for (int i = 0; args[arg][i]; ++i) { if (args[arg][i] == ' ') { @@ -6515,6 +6310,8 @@ void render_command_input_buffer(void) { set_colors(COLOR_FG, COLOR_BG); if (global_config.overlay_mode == OVERLAY_MODE_SEARCH) { printf(global_config.search_direction == 0 ? "?" : "/"); + } else if (global_config.overlay_mode == OVERLAY_MODE_FILESEARCH) { + printf("_"); } else { printf(":"); } @@ -6595,6 +6392,7 @@ BIM_ACTION(command_accept, 0, /* Run the converted command */ global_config.break_from_selection = 0; insert_command_history(tmp); + global_config.overlay_mode = OVERLAY_MODE_NONE; process_command(tmp); free(tmp); @@ -6606,7 +6404,6 @@ BIM_ACTION(command_accept, 0, } /* Leave command mode */ - global_config.overlay_mode = OVERLAY_MODE_NONE; } BIM_ACTION(command_word_delete, 0, @@ -6846,7 +6643,12 @@ void find_match_backwards(int from_line, int from_col, int * out_line, int * out void rehighlight_search(line_t * line) { if (!global_config.search) return; int j = 0; + while (j < line->actual) { + line->text[j].flags &= ~(FLAG_SEARCH); + j++; + } int ignorecase = smart_case(global_config.search); + j = 0; while (j < line->actual) { int matchlen = 0; if (subsearch_matches(line, j, global_config.search, ignorecase, &matchlen)) { @@ -6867,6 +6669,7 @@ void draw_search_match(uint32_t * buffer, int redraw_buffer) { env->lines[i]->text[j].flags &= (~FLAG_SEARCH); } } + int my_index = 0, match_count = 0; int line = -1, col = -1, _line = 1, _col = 1; do { int matchlen; @@ -6876,6 +6679,8 @@ void draw_search_match(uint32_t * buffer, int redraw_buffer) { for (int i = col; matchlen > 0; ++i, --matchlen) { l->text[i-1].flags |= FLAG_SEARCH; } + match_count += 1; + if (col == env->col_no && line == env->line_no) my_index = match_count; } _line = line; _col = col+1; @@ -6886,16 +6691,78 @@ void draw_search_match(uint32_t * buffer, int redraw_buffer) { place_cursor_actual(); redraw_statusbar(); redraw_commandline(); - if (redraw_buffer != -1) { - printf(redraw_buffer == 1 ? "/" : "?"); - uint32_t * c = buffer; - while (*c) { - char tmp[7] = {0}; /* Max six bytes, use 7 to ensure last is always nil */ - to_eight(*c, tmp); - printf("%s", tmp); - c++; + set_fg_color(COLOR_ALT_FG); + printf("[%d/%d] ", my_index, match_count); + set_fg_color(COLOR_KEYWORD); + printf(redraw_buffer == 1 ? "/" : "?"); + set_fg_color(COLOR_FG); + uint32_t * c = buffer; + while (*c) { + char tmp[7] = {0}; /* Max six bytes, use 7 to ensure last is always nil */ + to_eight(*c, tmp); + printf("%s", tmp); + c++; + } +} + +BIM_ACTION(start_file_search, 0, "Search for open files and switch tabs quickly.")(void) { + global_config.overlay_mode = OVERLAY_MODE_FILESEARCH; + + global_config.command_offset = 0; + global_config.command_col_no = 1; + + if (global_config.command_buffer) { + free(global_config.command_buffer); + } + + global_config.command_buffer = calloc(sizeof(line_t)+sizeof(char_t)*32,1); + global_config.command_buffer->available = 32; + + global_config.command_syn_back = env->syntax; + global_config.command_syn = NULL; /* uh, dunno for now */ + + render_command_input_buffer(); +} + +BIM_ACTION(file_search_accept, 0, "Open the requested tab")(void) { + if (!global_config.command_buffer->actual) { + goto _finish; + } + + /* See if the command buffer matches any open buffers */ + buffer_t * match = NULL; + for (int i = global_config.tab_offset; i < buffers_len; i++) { + buffer_t * _env = buffers[i]; + + if (global_config.overlay_mode == OVERLAY_MODE_FILESEARCH) { + if (global_config.command_buffer->actual) { + char * f = _env->file_name ? file_basename(_env->file_name) : ""; + /* TODO: Support unicode input here; needs conversion */ + int i = 0; + for (; i < global_config.command_buffer->actual && + f[i] == global_config.command_buffer->text[i].codepoint; ++i); + if (global_config.command_buffer->actual == i) { + match = _env; + break; + } + } } } + + if (match) { + env = match; + if (left_buffer && (left_buffer != env && right_buffer != env)) unsplit(); + } + +_finish: + /* Free the original editing buffer */ + free(global_config.command_buffer); + global_config.command_buffer = NULL; + + /* Leave command mode */ + global_config.overlay_mode = OVERLAY_MODE_NONE; + + redraw_all(); } BIM_ACTION(enter_search, ARG_IS_CUSTOM, @@ -6952,6 +6819,8 @@ BIM_ACTION(search_accept, 0, /* Leave command mode */ global_config.overlay_mode = OVERLAY_MODE_NONE; + + draw_search_match(global_config.search, global_config.search_direction); } /** @@ -6962,19 +6831,24 @@ BIM_ACTION(search_next, 0, )(void) { if (!global_config.search) return; if (env->coffset) env->coffset = 0; - int line = -1, col = -1; + int line = -1, col = -1, wrapped = 0; find_match(env->line_no, env->col_no+1, &line, &col, global_config.search, NULL); if (line == -1) { if (!global_config.search_wraps) return; find_match(1,1, &line, &col, global_config.search, NULL); if (line == -1) return; + wrapped = 1; } env->col_no = col; env->line_no = line; set_preferred_column(); - draw_search_match(global_config.search, -1); + draw_search_match(global_config.search, 1); + if (wrapped) { + set_fg_color(COLOR_ALT_FG); + printf(" (search wrapped to top)"); + } } /** @@ -6985,19 +6859,24 @@ BIM_ACTION(search_prev, 0, )(void) { if (!global_config.search) return; if (env->coffset) env->coffset = 0; - int line = -1, col = -1; + int line = -1, col = -1, wrapped = 0; find_match_backwards(env->line_no, env->col_no-1, &line, &col, global_config.search); if (line == -1) { if (!global_config.search_wraps) return; find_match_backwards(env->line_count, env->lines[env->line_count-1]->actual, &line, &col, global_config.search); if (line == -1) return; + wrapped = 1; } env->col_no = col; env->line_no = line; set_preferred_column(); - draw_search_match(global_config.search, -1); + draw_search_match(global_config.search, 0); + if (wrapped) { + set_fg_color(COLOR_ALT_FG); + printf(" (search wrapped to bottom)"); + } } /** @@ -7299,6 +7178,15 @@ BIM_ACTION(handle_mouse, 0, handle_common_mouse(buttons, x, y); } +BIM_ACTION(eat_mouse_sgr, 0, + "Receive, but do not process, mouse actions." +)(void) { + do { + int _c = bim_getch(); + if (_c == -1 || _c == 'm' || _c == 'M') break; + } while (1); +} + BIM_ACTION(handle_mouse_sgr, 0, "Process SGR-style mouse actions." )(void) { @@ -7490,9 +7378,7 @@ BIM_ACTION(undo_history, ACTION_IS_RW, env->lines[i]->istate = 0; recalculate_tabs(env->lines[i]); } - for (int i = 0; i < env->line_count; ++i) { - recalculate_syntax(env->lines[i],i); - } + schedule_complete_recalc(); place_cursor_actual(); update_title(); redraw_all(); @@ -7606,9 +7492,7 @@ BIM_ACTION(redo_history, ACTION_IS_RW, env->lines[i]->istate = 0; recalculate_tabs(env->lines[i]); } - for (int i = 0; i < env->line_count; ++i) { - recalculate_syntax(env->lines[i],i); - } + schedule_complete_recalc(); place_cursor_actual(); update_title(); redraw_all(); @@ -7859,6 +7743,7 @@ BIM_ACTION(insert_line_feed, ACTION_IS_RW, } else { env->lines = split_line(env->lines, env->line_no-1, env->col_no - 1); } + unhighlight_matching_paren(); env->coffset = 0; env->col_no = 1; env->line_no += 1; @@ -7911,6 +7796,14 @@ BIM_ACTION(yank_lines, 0, * Helper to yank part of a line into a new yank line. */ void yank_partial_line(int yank_no, int line_no, int start_off, int count) { + if (start_off + count > env->lines[line_no]->actual) { + if (start_off >= env->lines[line_no]->actual) { + start_off = env->lines[line_no]->actual; + count = 0; + } else { + count = env->lines[line_no]->actual - start_off; + } + } global_config.yanks[yank_no] = malloc(sizeof(line_t) + sizeof(char_t) * (count + 1)); global_config.yanks[yank_no]->available = count + 1; /* ensure extra space */ global_config.yanks[yank_no]->actual = count; @@ -7963,7 +7856,9 @@ BIM_ACTION(delete_at_column, ARG_IS_CUSTOM | ACTION_IS_RW, if (direction == -1 && env->sel_col <= 0) return; int prev_width = 0; - for (int i = env->line_no; i <= env->start_line; i++) { + int s = (env->line_no < env->start_line) ? env->line_no : env->start_line; + int e = (env->line_no < env->start_line) ? env->start_line : env->line_no; + for (int i = s; i <= e; i++) { line_t * line = env->lines[i - 1]; int _x = 0; @@ -7994,6 +7889,58 @@ BIM_ACTION(delete_at_column, ARG_IS_CUSTOM | ACTION_IS_RW, redraw_text(); } +void realign_column_cursor(void) { + line_t * line = env->lines[env->line_no - 1]; + int _x = 0, col = 1, j = 0; + for (; j < line->actual; ++j) { + char_t * c = &line->text[j]; + _x += c->display_width; + col = j + 1; + if (_x > env->sel_col) break; + } + env->col_no = col; +} + +BIM_ACTION(column_left, 0, "Move the column cursor left.")(void) { + if (env->sel_col > 0) { + env->sel_col -= 1; + env->preferred_column = env->sel_col; + /* Figure out where the cursor should be */ + realign_column_cursor(); + redraw_all(); + } +} + +BIM_ACTION(column_right, 0, "Move the column cursor right.")(void) { + env->sel_col += 1; + env->preferred_column = env->sel_col; + /* Figure out where the cursor should be */ + realign_column_cursor(); + redraw_all(); +} + +BIM_ACTION(column_up, 0, "Move the column cursor up.")(void) { + if (env->line_no > 1 && env->start_line > 1) { + env->line_no--; + env->start_line--; + /* Figure out where the cursor should be */ + realign_column_cursor(); + place_cursor_actual(); + redraw_all(); + } +} + +BIM_ACTION(column_down, 0, "Move the column cursor down.")(void) { + if (env->line_no < env->line_count && env->start_line < env->line_count) { + env->line_no++; + env->start_line++; + /* Figure out where the cursor should be */ + realign_column_cursor(); + place_cursor_actual(); + redraw_all(); + } +} + uint32_t * get_word_under_cursor(void) { /* Figure out size */ int c_before = 0; @@ -8134,7 +8081,9 @@ int point_in_range(int start_line, int end_line, int start_col, int end_col, int if ((env->line_no < env->start_line && ((line) < env->line_no || (line) > env->start_line)) || \ (env->line_no > env->start_line && ((line) > env->line_no || (line) < env->start_line)) || \ (env->line_no == env->start_line && (line) != env->start_line)) { \ - recalculate_syntax(env->lines[(line)-1],(line)-1); \ + for (int j = 0; j < env->lines[(line)-1]->actual; ++j) { \ + env->lines[(line)-1]->text[j].flags &= ~(FLAG_SELECT); \ + } \ } else { \ for (int j = 0; j < env->lines[(line)-1]->actual; ++j) { \ env->lines[(line)-1]->text[j].flags |= FLAG_SELECT; \ @@ -8156,10 +8105,14 @@ int point_in_range(int start_line, int end_line, int start_col, int end_col, int (env->line_no > env->start_line && ((line) > env->line_no || (line) < env->start_line)) || \ (env->line_no == env->start_line && (line) != env->start_line)) { \ /* Line is completely outside selection */ \ - recalculate_syntax(env->lines[(line)-1],(line)-1); \ + for (int j = 0; j < env->lines[(line)-1]->actual; ++j) { \ + env->lines[(line)-1]->text[j].flags &= ~(FLAG_SELECT); \ + } \ } else { \ if ((line) == env->start_line || (line) == env->line_no) { \ - recalculate_syntax(env->lines[(line)-1],(line)-1); \ + for (int j = 0; j < env->lines[(line)-1]->actual; ++j) { \ + env->lines[(line)-1]->text[j].flags &= ~(FLAG_SELECT); \ + } \ } \ for (int j = 0; j < env->lines[(line)-1]->actual; ++j) { \ if (point_in_range(env->start_line, env->line_no,env->start_col, env->col_no, (line), j+1)) { \ @@ -8203,9 +8156,14 @@ BIM_ACTION(adjust_indent, ARG_IS_CUSTOM | ACTION_IS_RW, _redraw_line(start_point+i+1,1); } } else { - for (int j = 0; j < env->tabstop; ++j) { - if (env->lines[start_point + i]->text[0].codepoint == ' ') { - line_delete(env->lines[start_point + i],1,start_point+i); + if (env->lines[start_point + i]->text[0].codepoint == '\t') { + line_delete(env->lines[start_point + i],1,start_point+i); + _redraw_line(start_point+i+1,1); + } else { + for (int j = 0; j < env->tabstop; ++j) { + if (env->lines[start_point + i]->text[0].codepoint == ' ') { + line_delete(env->lines[start_point + i],1,start_point+i); + } } } _redraw_line(start_point+i+1,1); @@ -8244,7 +8202,9 @@ void recalculate_selected_lines(void) { if (end < 1) end = 1; if (end > env->line_count) end = env->line_count; for (int i = (start > 1) ? (start-1) : (start); i <= end; ++i) { - recalculate_syntax(env->lines[i-1],i-1); + for (int j = 0; j < env->lines[i-1]->actual; j++) { + env->lines[i-1]->text[j].flags &= ~(FLAG_SELECT); + } } redraw_all(); } @@ -8368,7 +8328,9 @@ BIM_ACTION(insert_char_at_column, ARG_IS_INPUT | ACTION_IS_RW, int inserted_width = 0; /* For each line */ - for (int i = env->line_no; i <= env->start_line; i++) { + int s = (env->line_no < env->start_line) ? env->line_no : env->start_line; + int e = (env->line_no < env->start_line) ? env->start_line : env->line_no; + for (int i = s; i <= e; i++) { line_t * line = env->lines[i - 1]; int _x = 0; @@ -8397,21 +8359,17 @@ BIM_ACTION(insert_char_at_column, ARG_IS_INPUT | ACTION_IS_RW, } recalculate_tabs(line); inserted_width = line->text[col-1].display_width; + if (i == env->line_no) env->col_no = col + 1; } env->sel_col += inserted_width; - env->col_no++; + env->preferred_column = env->sel_col; + place_cursor_actual(); } BIM_ACTION(enter_col_insert, ACTION_IS_RW, "Enter column insert mode." )(void) { - if (env->start_line < env->line_no) { - /* swap */ - int tmp = env->line_no; - env->line_no = env->start_line; - env->start_line = tmp; - } env->mode = MODE_COL_INSERT; } @@ -8422,18 +8380,6 @@ BIM_ACTION(enter_col_insert_after, ACTION_IS_RW, enter_col_insert(); } -BIM_ACTION(delete_column, ACTION_IS_RW, - "(temporary) Delete the selected column." -)(void) { - /* TODO maybe a flag to do this so we can just call delete_at_column with arg = 1? */ - if (env->start_line < env->line_no) { - int tmp = env->line_no; - env->line_no = env->start_line; - env->start_line = tmp; - } - delete_at_column(1); -} - BIM_ACTION(enter_col_selection, 0, "Enter column selection mode." )(void) { @@ -8650,9 +8596,8 @@ int read_tags(uint32_t * comp, struct completion_match **matches, int * matches_ fclose(tags); } - /* TODO: Get these from syntax files with a dynamic callback */ if (env->syntax && env->syntax->completion_matcher) { - env->syntax->completion_matcher(comp,matches,matches_count,complete_match,matches_len); + env->syntax->completion_matcher(comp,matches,matches_count,complete_match,matches_len, env); } return 0; @@ -8730,6 +8675,9 @@ void draw_completion_matches(uint32_t * tmp, struct completion_match *matches, i * Autocomplete words (function/variable names, etc.) in input mode. */ int omni_complete(int quit_quietly_on_none) { + int retval = 0; + int index = 0; + int c; int (*qualifier)(int c) = simple_keyword_qualifier; @@ -8769,14 +8717,18 @@ int omni_complete(int quit_quietly_on_none) { int matches_count; /* TODO just reading ctags is rather mediocre; can we do something cool here? */ - if (read_tags(tmp, &matches, &matches_count, 0)) goto _completion_done; + if (read_tags(tmp, &matches, &matches_count, 0)) { + goto _completion_done; + } /* Draw box with matches at cursor-width(tmp) */ - if (quit_quietly_on_none && matches_count == 0) return 0; - draw_completion_matches(tmp, matches, matches_count, 0); + if (quit_quietly_on_none && matches_count == 0) { + free(tmp); + free(matches); + return 0; + } - int retval = 0; - int index = 0; + draw_completion_matches(tmp, matches, matches_count, 0); _completion_done: place_cursor_actual(); @@ -8933,7 +8885,7 @@ int read_one_character(char * message) { place_cursor_actual(); } int c; - while ((c = bim_getkey(200))) { + while ((c = bim_getkey(DEFAULT_KEY_WAIT))) { if (c == KEY_TIMEOUT) continue; if (c == KEY_CTRL_V) { if (!global_config.overlay_mode) { @@ -8997,6 +8949,7 @@ BIM_ACTION(append_and_insert, ACTION_IS_RW, "Insert a new line after the current line and enter insert mode." )(void) { set_history_break(); + unhighlight_matching_paren(); env->lines = add_line(env->lines, env->line_no); env->col_no = 1; env->line_no += 1; @@ -9048,6 +9001,7 @@ BIM_ACTION(paste, ARG_IS_CUSTOM | ACTION_IS_RW, "Paste yanked text before (`P`) or after (`p`) the cursor." )(int direction) { if (global_config.yanks) { + env->slowop = 1; if (!global_config.yank_is_full_lines) { /* Handle P for paste before, p for past after */ int target_column = (direction == -1 ? (env->col_no) : (env->col_no+1)); @@ -9084,13 +9038,9 @@ BIM_ACTION(paste, ARG_IS_CUSTOM | ACTION_IS_RW, replace_line(env->lines, env->line_no - (direction == -1 ? 1 : 0) + i, global_config.yanks[i]); } } + env->slowop = 0; + schedule_complete_recalc(); /* Recalculate whole document syntax */ - for (int i = 0; i < env->line_count; ++i) { - env->lines[i]->istate = 0; - } - for (int i = 0; i < env->line_count; ++i) { - recalculate_syntax(env->lines[i],i); - } if (direction == 1) { if (global_config.yank_is_full_lines) { env->line_no += 1; @@ -9182,13 +9132,13 @@ BIM_ACTION(expand_split_left, 0, } BIM_ACTION(go_page_up, 0, - "Jump up a screenful." + "Jump up a screenfull." )(void) { goto_line(env->line_no - (global_config.term_height - 6)); } BIM_ACTION(go_page_down, 0, - "Jump down a screenful." + "Jump down a screenfull." )(void) { goto_line(env->line_no + (global_config.term_height - 6)); } @@ -9280,7 +9230,7 @@ BIM_ACTION(smart_backspace, ACTION_IS_RW, } BIM_ACTION(perform_omni_completion, ACTION_IS_RW, - "(temporary) Perform smart symbol competion from ctags." + "(temporary) Perform smart symbol completion from ctags." )(void) { /* This should probably be a submode */ while (omni_complete(0) == 1); @@ -9365,7 +9315,7 @@ BIM_ACTION(enter_line_selection_and_cursor_down, 0, } BIM_ACTION(shift_horizontally, ARG_IS_CUSTOM, - "Shift the current line or screen view horiztonally, depending on settings." + "Shift the current line or screen view horizontally, depending on settings." )(int amount) { env->coffset += amount; if (env->coffset < 0) env->coffset = 0; @@ -9373,19 +9323,24 @@ BIM_ACTION(shift_horizontally, ARG_IS_CUSTOM, } static int state_before_paste = 0; +static int line_before_paste = 0; BIM_ACTION(paste_begin, 0, "Begin bracketed paste; disable indentation, completion, etc.")(void) { if (global_config.smart_complete) state_before_paste |= 0x01; if (env->indent) state_before_paste |= 0x02; global_config.smart_complete = 0; env->indent = 0; - /* TODO: We need env->loading == 1, but with history (manual breaks, though) */ + env->slowop = 1; + line_before_paste = env->line_no; } BIM_ACTION(paste_end, 0, "End bracketed paste; restore indentation, completion, etc.")(void) { if (state_before_paste & 0x01) global_config.smart_complete = 1; if (state_before_paste & 0x02) env->indent = 1; - redraw_statusbar(); + env->slowop = 0; + int line_to_recalculate = (line_before_paste > 1 ? line_before_paste - 1 : 0); + recalculate_syntax(env->lines[line_to_recalculate], line_to_recalculate); + redraw_all(); } struct action_map _NORMAL_MAP[] = { @@ -9412,6 +9367,7 @@ struct action_map _NORMAL_MAP[] = { {KEY_SHIFT_DOWN, enter_line_selection_and_cursor_down, 0, 0}, {KEY_ALT_UP, previous_tab, 0, 0}, {KEY_ALT_DOWN, next_tab, 0, 0}, + {KEY_CTRL_UNDERSCORE, start_file_search, 0, 0}, {-1, NULL, 0, 0}, }; @@ -9482,7 +9438,7 @@ struct action_map _COL_SELECTION_MAP[] = { {KEY_CTRL_V, leave_selection, 0, 0}, {'I', enter_col_insert, opt_rw, 0}, {'a', enter_col_insert_after, opt_rw, 0}, - {'d', delete_column, opt_norm | opt_rw, 0}, + {'d', delete_at_column, opt_arg | opt_rw, 1}, {-1, NULL, 0, 0}, }; @@ -9494,6 +9450,10 @@ struct action_map _COL_INSERT_MAP[] = { {KEY_ENTER, NULL, 0, 0}, {KEY_CTRL_W, NULL, 0, 0}, {KEY_CTRL_V, insert_char_at_column, opt_char, 0}, + {KEY_LEFT, column_left, 0, 0}, + {KEY_RIGHT, column_right, 0, 0}, + {KEY_UP, column_up, 0, 0}, + {KEY_DOWN, column_down, 0, 0}, {-1, NULL, 0, 0}, }; @@ -9580,6 +9540,15 @@ struct action_map _COMMAND_MAP[] = { {-1, NULL, 0, 0} }; +struct action_map _FILESEARCH_MAP[] = { + {KEY_ENTER, file_search_accept, 0, 0}, + + {KEY_UP, NULL, 0, 0}, + {KEY_DOWN, NULL, 0, 0}, + + {-1, NULL, 0, 0} +}; + struct action_map _SEARCH_MAP[] = { {KEY_ENTER, search_accept, 0, 0}, @@ -9596,6 +9565,7 @@ struct action_map _INPUT_BUFFER_MAP[] = { {KEY_BACKSPACE, command_backspace, 0, 0}, {KEY_CTRL_W, command_word_delete, 0, 0}, {KEY_MOUSE, eat_mouse, 0, 0}, + {KEY_MOUSE_SGR, eat_mouse_sgr, 0, 0}, {KEY_LEFT, command_cursor_left, 0, 0}, {KEY_CTRL_LEFT, command_word_left, 0, 0}, {KEY_RIGHT, command_cursor_right, 0, 0}, @@ -9624,6 +9594,7 @@ struct action_map * NAVIGATION_MAP = NULL; struct action_map * ESCAPE_MAP = NULL; struct action_map * COMMAND_MAP = NULL; struct action_map * SEARCH_MAP = NULL; +struct action_map * FILESEARCH_MAP = NULL; struct action_map * INPUT_BUFFER_MAP = NULL; struct mode_names mode_names[] = { @@ -9726,7 +9697,7 @@ void normal_mode(void) { render_command_input_buffer(); refresh = 0; } - int key = bim_getkey(200); + int key = bim_getkey(DEFAULT_KEY_WAIT); if (key != KEY_TIMEOUT) { refresh = 1; if (!handle_action(COMMAND_MAP, key)) @@ -9734,12 +9705,26 @@ void normal_mode(void) { if (key < KEY_ESCAPE) command_insert_char(key); } continue; + } else if (global_config.overlay_mode == OVERLAY_MODE_FILESEARCH) { + if (refresh) { + render_command_input_buffer(); + redraw_tabbar(); + refresh = 0; + } + int key = bim_getkey(DEFAULT_KEY_WAIT); + if (key != KEY_TIMEOUT) { + refresh = 1; + if (!handle_action(FILESEARCH_MAP, key)) + if (!handle_action(INPUT_BUFFER_MAP, key)) + if (key < KEY_ESCAPE) command_insert_char(key); + } + continue; } else if (global_config.overlay_mode == OVERLAY_MODE_SEARCH) { if (refresh) { render_command_input_buffer(); refresh = 0; } - int key = bim_getkey(200); + int key = bim_getkey(DEFAULT_KEY_WAIT); if (key != KEY_TIMEOUT) { refresh = 1; if (!handle_action(SEARCH_MAP, key)) { @@ -9796,7 +9781,10 @@ void normal_mode(void) { if (env->mode == MODE_NORMAL) { place_cursor_actual(); - int key = bim_getkey(200); + int key = 0; + do { + key = bim_getkey(DEFAULT_KEY_WAIT); + } while (key == KEY_TIMEOUT); if (handle_nav_buffer(key)) { if (!handle_action(NORMAL_MAP, key)) if (!handle_action(NAVIGATION_MAP, key)) @@ -9804,9 +9792,10 @@ void normal_mode(void) { } reset_nav_buffer(key); } else if (env->mode == MODE_INSERT) { - place_cursor_actual(); - int key = bim_getkey(refresh ? 10 : 200); + if (!refresh) place_cursor_actual(); + int key = bim_getkey(refresh ? 10 : DEFAULT_KEY_WAIT); if (key == KEY_TIMEOUT) { + place_cursor_actual(); if (refresh > 1) { redraw_text(); } else if (refresh) { @@ -9827,7 +9816,7 @@ void normal_mode(void) { } } else if (env->mode == MODE_REPLACE) { place_cursor_actual(); - int key = bim_getkey(200); + int key = bim_getkey(DEFAULT_KEY_WAIT); if (key != KEY_TIMEOUT) { if (handle_action(REPLACE_MAP, key)) { redraw_text(); @@ -9847,7 +9836,7 @@ void normal_mode(void) { } } else if (env->mode == MODE_LINE_SELECTION) { place_cursor_actual(); - int key = bim_getkey(200); + int key = bim_getkey(DEFAULT_KEY_WAIT); if (key == KEY_TIMEOUT) continue; if (handle_nav_buffer(key)) { @@ -9879,7 +9868,7 @@ void normal_mode(void) { } } else if (env->mode == MODE_CHAR_SELECTION) { place_cursor_actual(); - int key = bim_getkey(200); + int key = bim_getkey(DEFAULT_KEY_WAIT); if (key == KEY_TIMEOUT) continue; if (handle_nav_buffer(key)) { @@ -9909,7 +9898,7 @@ void normal_mode(void) { } } else if (env->mode == MODE_COL_SELECTION) { place_cursor_actual(); - int key = bim_getkey(200); + int key = bim_getkey(DEFAULT_KEY_WAIT); if (key == KEY_TIMEOUT) continue; if (handle_nav_buffer(key)) { @@ -9938,7 +9927,7 @@ void normal_mode(void) { redraw_commandline(); } } else if (env->mode == MODE_COL_INSERT) { - int key = bim_getkey(refresh ? 10 : 200); + int key = bim_getkey(refresh ? 10 : DEFAULT_KEY_WAIT); if (key == KEY_TIMEOUT) { if (refresh) { redraw_commandline(); @@ -9953,7 +9942,7 @@ void normal_mode(void) { } } if (env->mode == MODE_DIRECTORY_BROWSE) { place_cursor_actual(); - int key = bim_getkey(200); + int key = bim_getkey(DEFAULT_KEY_WAIT); if (handle_nav_buffer(key)) { if (!handle_action(DIRECTORY_BROWSE_MAP, key)) if (!handle_action(NAVIGATION_MAP, key)) @@ -9965,6 +9954,79 @@ void normal_mode(void) { } +KrkClass * CommandDef; +struct CommandDef { + KrkInstance inst; + struct command_def * command; +}; + +int process_krk_command(const char * cmd, KrkValue * outVal) { + place_cursor(global_config.term_width, global_config.term_height); + fprintf(stdout, "\n"); + /* By resetting, we're at 0 frames. */ + krk_resetStack(); + /* Push something so we're not at the bottom of the stack when an + * exception happens, or we'll get the normal interpreter behavior + * and won't be able to examine the exception ourselves. */ + krk_push(NONE_VAL()); + /* If we don't set outSlots for the top frame a syntax error will + * get printed by the interpreter and we can't catch it here. */ + krk_currentThread.frames[0].outSlots = 1; + /* Call the interpreter */ + KrkValue out = krk_interpret(cmd,""); + /* If the user typed just a command name, try to execute it. */ + if (krk_isInstanceOf(out,CommandDef)) { + krk_push(out); + out = krk_callSimple(krk_peek(0),0,1); + } + if (outVal) *outVal = out; + int retval = (IS_INTEGER(out)) ? AS_INTEGER(out) : 0; + int hadOutput = 0; + /* If we got an exception during execution, print it now */ + if (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) { + if (krk_isInstanceOf(krk_currentThread.currentException, vm.exceptions->syntaxError)) { + } + set_fg_color(COLOR_RED); + fflush(stdout); + krk_dumpTraceback(); + set_fg_color(COLOR_FG); + fflush(stdout); + hadOutput = 1; + krk_resetStack(); + } + /* Otherwise, we can look at the result here. */ + if (!IS_NONE(out) && !(IS_INTEGER(out) && AS_INTEGER(out) == 0)) { + krk_push(out); + KrkValue repr = krk_callSimple(OBJECT_VAL(krk_getType(out)->_reprer), 1, 0); + if (IS_STRING(repr)) { + fprintf(stdout, " => %s\n", AS_CSTRING(repr)); + clear_to_end(); + } + krk_resetStack(); + hadOutput = 1; + } + /* If we had either an exception or a non-zero, non-None result, + * we want to wait for a key press before continuing, and avoid + * clearing the screen if the user is going to enter another command. */ + if (hadOutput) { + int c; + while ((c = bim_getch())== -1); + if (c != ':') { + bim_unget(c); + } else { + enter_command(); + global_config.command_offset = 0; + global_config.command_col_no = 1; + render_command_input_buffer(); + return retval; + } + } + global_config.break_from_selection = 1; + if (!global_config.had_error) redraw_all(); + global_config.had_error = 0; + return retval; +} + /** * Show help text for -? */ @@ -9978,16 +10040,13 @@ static void show_usage(char * argv[]) { " %s [options] -- -\n" "\n" " -R " _s "open initial buffer read-only" _e - " -O " _s "set various options:" _e - " noscroll " _s "disable terminal scrolling" _e + " -O " _s "set various options; examples:" _e " noaltscreen " _s "disable alternate screen buffer" _e - " nomouse " _s "disable mouse support" _e " nounicode " _s "disable unicode display" _e - " nobright " _s "disable bright next" _e - " nohideshow " _s "disable togglging cursor visibility" _e " nosyntax " _s "disable syntax highlighting on load" _e - " notitle " _s "disable title-setting escapes" _e - " history " _s "enable experimental undo/redo" _e + " nohistory " _s "disable undo/redo" _e + " nomouse " _s "disable mouse support" _e + " cansgrmouse " _s "enable SGR mouse escape sequences" _e " -c,-C " _s "print file to stdout with syntax highlighting" _e " " _s "-C includes line numbers, -c does not" _e " -u " _s "override bimrc file" _e @@ -10005,377 +10064,631 @@ static void show_usage(char * argv[]) { #undef _s } -void free_function(struct bim_function * func) { - do { - struct bim_function * next = func->next; - free(func->command); - free(func); - func = next; - } while (func); -} - -int run_function(char * name) { - for (int i = 0; i < flex_user_functions_count; ++i) { - if (user_functions[i] && !strcmp(user_functions[i]->command, name)) { - /* Execute function */ - struct bim_function * this = user_functions[i]->next; - while (this) { - char * tmp = strdup(this->command); - int result = process_command(tmp); - free(tmp); - if (result != 0) { - return result; - } - this = this->next; - } - return 0; - } - } - return -1; -} +BIM_COMMAND(runkrk,"runkrk", "Run a kuroko script") { + if (argc < 2) return 1; + + /* In case we're running in a weird context? */ + int previousExitFrame = krk_currentThread.exitOnFrame; + krk_currentThread.exitOnFrame = krk_currentThread.frameCount; + krk_runfile(argv[1],argv[1]); + krk_currentThread.exitOnFrame = previousExitFrame; + + redraw_all(); -int has_function(char * name) { - for (int i = 0; i < flex_user_functions_count; ++i) { - if (user_functions[i] && !strcmp(user_functions[i]->command, name)) { - return 1; - } - } return 0; } -BIM_COMMAND(call,"call","Call a function") { - if (argc < 2) { - render_error("Expected function name"); - return 1; +/** + * Enable or disable terminal features/quirks and other options. + * Used by -O and by the `quirks` bimrc command. + */ +int set_capability(char * arg) { + char * argname; + int value; + if (strstr(arg,"no") == arg) { + argname = &arg[2]; + value = 0; + } else if (strstr(arg,"can") == arg) { + argname = &arg[3]; + value = 1; + } else { + render_error("Capabilities must by 'no{CAP}' or 'can{CAP}': %s", arg); + return 2; } - int result = run_function(argv[1]); - if (result == -1) { - render_error("Undefined function: %s", argv[1]); + + /* Terminal features / quirks */ + if (!strcmp(argname, "24bit")) global_config.can_24bit = value; + else if (!strcmp(argname, "256color")) global_config.can_256color = value; + else if (!strcmp(argname, "altscreen")) global_config.can_altscreen = value; + else if (!strcmp(argname, "bce")) global_config.can_bce = value; + else if (!strcmp(argname, "bright")) global_config.can_bright = value; + else if (!strcmp(argname, "hideshow")) global_config.can_hideshow = value; + else if (!strcmp(argname, "italic")) global_config.can_italic = value; + else if (!strcmp(argname, "mouse")) global_config.can_mouse = value; + else if (!strcmp(argname, "scroll")) global_config.can_scroll = value; + else if (!strcmp(argname, "title")) global_config.can_title = value; + else if (!strcmp(argname, "unicode")) global_config.can_unicode = value; + else if (!strcmp(argname, "insert")) global_config.can_insert = value; + else if (!strcmp(argname, "paste")) global_config.can_bracketedpaste = value; + else if (!strcmp(argname, "sgrmouse")) global_config.can_sgrmouse = value; + /* Startup options */ + else if (!strcmp(argname, "syntax")) global_config.highlight_on_open = value; + else if (!strcmp(argname, "history")) global_config.history_enabled = value; + else { + render_error("Unknown capability: %s", argname); return 1; } - return result; + return 0; } -BIM_COMMAND(try_call,"trycall","Call a function but return quietly if it fails") { - if (argc < 2) return 0; - run_function(argv[1]); +BIM_COMMAND(setcap, "setcap", "Enable or disable quirks/features.") { + for (int i = 1; i < argc; ++i) { + if (set_capability(argv[i])) return 1; + } return 0; } -BIM_COMMAND(list_functions,"listfunctions","List functions") { - render_commandline_message(""); - for (int i = 0; i < flex_user_functions_count; ++i) { - render_commandline_message("%s\n", user_functions[i]->command); +BIM_COMMAND(quirk,"quirk","Handle quirks based on environment variables") { + if (argc < 3) goto _quirk_arg_error; + char * varname = argv[1]; + char * teststr = argv[2]; + char * value = getenv(varname); + if (!value) return 0; + + if (strstr(value, teststr) == value) { + /* Process quirk strings */ + for (int i = 3; i < argc; ++i) { + set_capability(argv[i]); + } } - pause_for_key(); + return 0; +_quirk_arg_error: + render_error("Usage: quirk ENVVAR value no... can..."); + return 1; } -BIM_COMMAND(show_function,"showfunction","Show the commands in a function") { - if (argc < 2) return 1; - struct bim_function * this = NULL; - for (int i = 0; i < flex_user_functions_count; ++i) { - if (user_functions[i] && !strcmp(user_functions[i]->command, argv[1])) { - this = user_functions[i]; - break; +/** + * Load bimrc configuration file. + * + * At the moment, this a simple key=value list. + */ +void load_bimrc(void) { + if (!global_config.bimrc_path) return; + + /* Default is ~/.bimrc */ + char * tmp = strdup(global_config.bimrc_path); + + if (!*tmp) { + free(tmp); + return; + } + + /* Parse ~ at the front of the path. */ + if (*tmp == '~') { + char path[1024] = {0}; + char * home = getenv("HOME"); + if (!home) { + /* $HOME is unset? */ + free(tmp); + return; } + + /* New path is $HOME/.bimrc */ + snprintf(path, 1024, "%s%s", home, tmp+1); + free(tmp); + tmp = strdup(path); } - if (!this) { - render_error("Not a function: %s", argv[1]); - return 1; + + struct stat statbuf; + if (stat(tmp, &statbuf)) { + free(tmp); + return; + } + + char * args[] = {"runkrk", tmp, NULL}; + if (bim_command_runkrk("runkrk", 2, args)) { + /* Wait */ + render_error("Errors were encountered when loading bimrc. Press ENTER to continue."); + int c; + while ((c = bim_getch(), c != ENTER_KEY && c != LINE_FEED)); } + free(tmp); +} - /* We really should rewrite this so syntax highlighting takes a highlighter */ - struct syntax_definition * old_syntax = env->syntax; - env->syntax = find_syntax_calculator("bimcmd"); +static int checkClass(KrkClass * _class, KrkClass * base) { + while (_class) { + if (_class == base) return 1; + _class = _class->base; + } + return 0; +} - int i = 0; +static KrkValue krk_bim_syntax_dict; +static KrkValue krk_bim_register_syntax(int argc, KrkValue argv[], int hasKw) { + if (argc < 1 || !IS_CLASS(argv[0]) || !checkClass(AS_CLASS(argv[0]), syntaxStateClass)) + return krk_runtimeError(vm.exceptions->typeError, "Can not register '%s' as a syntax highlighter, expected subclass of SyntaxState.", krk_typeName(argv[0])); - while (this) { - /* Turn command into line */ - line_t * tmp = calloc(sizeof(line_t) + sizeof(char_t) * strlen(this->command),1); - tmp->available = strlen(this->command); + KrkValue name = krk_valueGetAttribute_default(argv[0], "name", NONE_VAL()); + KrkValue extensions = krk_valueGetAttribute_default(argv[0], "extensions", NONE_VAL()); + KrkValue spaces = krk_valueGetAttribute_default(argv[0], "spaces", BOOLEAN_VAL(0)); + KrkValue calculate = krk_valueGetAttribute_default(argv[0], "calculate", NONE_VAL()); - unsigned char * t = (unsigned char *)this->command; - uint32_t state = 0; - uint32_t c = 0; - int col = 1; - while (*t) { - if (!decode(&state, &c, *t)) { - char_t _c = {codepoint_width(c), 0, c}; - tmp = line_insert(tmp, _c, col - 1, -1); - col++; - } - t++; - } + if (!IS_STRING(name)) + return krk_runtimeError(vm.exceptions->typeError, "%s.name must be str", AS_CLASS(argv[0])->name->chars); + if (!IS_TUPLE(extensions)) + return krk_runtimeError(vm.exceptions->typeError, "%s.extensions must be tuple", AS_CLASS(argv[0])->name->chars); + if (!IS_BOOLEAN(spaces)) + return krk_runtimeError(vm.exceptions->typeError, "%s.spaces must be bool", AS_CLASS(argv[0])->name->chars); + if (!IS_CLOSURE(calculate)) + return krk_runtimeError(vm.exceptions->typeError, "%s.calculate must be method, not '%s'", AS_CLASS(argv[0])->name->chars, krk_typeName(calculate)); - render_commandline_message(""); - render_line(tmp, global_config.term_width - 1, 0, -1); - printf("\n"); - this = this->next; - i++; - if (this && i == global_config.term_height - 3) { - printf("(function continues)"); - while (bim_getkey(200) == KEY_TIMEOUT); + /* Convert tuple of strings */ + char ** ext = malloc(sizeof(char *) * (AS_TUPLE(extensions)->values.count + 1)); /* +1 for NULL */ + ext[AS_TUPLE(extensions)->values.count] = NULL; + for (size_t i = 0; i < AS_TUPLE(extensions)->values.count; ++i) { + if (!IS_STRING(AS_TUPLE(extensions)->values.values[i])) { + free(ext); + return krk_runtimeError(vm.exceptions->typeError, "%s.extensions must by tuple", AS_CLASS(argv[0])->name->chars); } + ext[i] = AS_CSTRING(AS_TUPLE(extensions)->values.values[i]); } - /* Restore previous syntax */ - env->syntax = old_syntax; + add_syntax((struct syntax_definition) { + AS_CSTRING(name), /* name */ + ext, /* NULL-terminated array of extensions */ + NULL, /* calculate function */ + AS_BOOLEAN(spaces), /* spaces */ + NULL, /* qualifier */ + NULL, /* matcher */ + AS_OBJECT(calculate), /* krkFunc */ + AS_OBJECT(argv[0]), + }); - pause_for_key(); - return 0; + /* And save it in the module stuff. */ + krk_tableSet(AS_DICT(krk_bim_syntax_dict), name, argv[0]); + + return NONE_VAL(); } -BIM_COMMAND(runscript,"runscript","Run a script file") { - if (argc < 2) { - render_error("Expected a script to run"); - return 1; - } +static KrkValue krk_bim_theme_dict; +static KrkValue krk_bim_define_theme(int argc, KrkValue argv[], int hasKw) { + if (argc < 1 || !IS_CLOSURE(argv[0])) + return krk_runtimeError(vm.exceptions->typeError, "themes must be functions, not '%s'", krk_typeName(argv[0])); - /* Run commands */ - FILE * f; - char * home; - if (argv[1][0] == '~' && (home = getenv("HOME"))) { - char * tmp = malloc(strlen(argv[1]) + strlen(home) + 4); - sprintf(tmp,"%s%s", home, argv[1]+1); - f = fopen(tmp,"r"); - free(tmp); + KrkValue name = OBJECT_VAL(AS_CLOSURE(argv[0])->function->name); + + add_colorscheme((struct theme_def) { + AS_CSTRING(name), + AS_OBJECT(argv[0]), + }); + + krk_tableSet(AS_DICT(krk_bim_theme_dict), name, argv[0]); + return argv[0]; +} + +int c_keyword_qualifier(int c) { + return isalnum(c) || (c == '_'); +} + +#define BIM_STATE() \ + if (unlikely(argc < 1 || !krk_isInstanceOf(argv[0],syntaxStateClass))) return krk_runtimeError(vm.exceptions->typeError, "expected state"); \ + KrkInstance * _self = AS_INSTANCE(argv[0]); \ + struct SyntaxState * self = (struct SyntaxState*)_self; \ + struct syntax_state * state = &self->state; + +static KrkTuple * _bim_state_chars = NULL; + +static KrkValue bim_krk_state_getstate(int argc, KrkValue argv[], int hasKw) { + BIM_STATE(); + if (argc > 1 && IS_INTEGER(argv[1])) { + state->state = AS_INTEGER(argv[1]); + } else if (argc > 1 && IS_NONE(argv[1])) { + state->state = -1; + } + return INTEGER_VAL(state->state); +} +static KrkValue bim_krk_state_setstate(int argc, KrkValue argv[], int hasKw) { + BIM_STATE(); + if (argc > 1 && IS_INTEGER(argv[1])) { + state->state = AS_INTEGER(argv[1]); + } else if (argc > 1 && IS_NONE(argv[1])) { + state->state = -1; + } else { + return krk_runtimeError(vm.exceptions->typeError, "expected int"); + } + return NONE_VAL(); +} +static KrkValue bim_krk_state_index(int argc, KrkValue argv[], int hasKw) { + BIM_STATE(); + return INTEGER_VAL(state->i); +} +static KrkValue bim_krk_state_lineno(int argc, KrkValue argv[], int hasKw) { + BIM_STATE(); + return INTEGER_VAL(state->line_no); +} +static KrkValue bim_krk_state_get(int argc, KrkValue argv[], int hasKw) { + BIM_STATE(); + long arg = AS_INTEGER(argv[1]); + int charRel = charrel(arg); + if (charRel == -1) return NONE_VAL(); + if (charRel >= 32 && charRel <= 126) return _bim_state_chars->values.values[charRel - 32]; + char tmp[8] = {0}; + size_t len = to_eight(charRel, tmp); + return OBJECT_VAL(krk_copyString(tmp,len)); +} +static KrkValue bim_krk_state_isdigit(int argc, KrkValue argv[], int hasKw) { + if (IS_NONE(argv[1])) return BOOLEAN_VAL(0); + if (!IS_STRING(argv[1])) { + krk_runtimeError(vm.exceptions->typeError, "not a string: %s", krk_typeName(argv[1])); + return BOOLEAN_VAL(0); + } + if (AS_STRING(argv[1])->codesLength > 1) { + krk_runtimeError(vm.exceptions->typeError, "arg must be str of len 1"); + return BOOLEAN_VAL(0); + } + unsigned int c = krk_unicodeCodepoint(AS_STRING(argv[1]), 0); + return BOOLEAN_VAL(!!isdigit(c)); +} +static KrkValue bim_krk_state_isxdigit(int argc, KrkValue argv[], int hasKw) { + if (!IS_STRING(argv[1])) return BOOLEAN_VAL(0); + if (AS_STRING(argv[1])->length > 1) return BOOLEAN_VAL(0); + int c = AS_CSTRING(argv[1])[0]; + return BOOLEAN_VAL(!!isxdigit(c)); +} +static KrkValue bim_krk_state_paint(int argc, KrkValue argv[], int hasKw) { + BIM_STATE(); + long howMuch = AS_INTEGER(argv[1]); + if (howMuch == -1) howMuch = state->line->actual; + long whatFlag = AS_INTEGER(argv[2]); + paint(howMuch, whatFlag); + return NONE_VAL(); +} +static KrkValue bim_krk_state_paintComment(int argc, KrkValue argv[], int hasKw) { + BIM_STATE(); + paint_comment(state); + return NONE_VAL(); +} +static KrkValue bim_krk_state_skip(int argc, KrkValue argv[], int hasKw) { + BIM_STATE(); + skip(); + return NONE_VAL(); +} +static KrkValue bim_krk_state_cKeywordQualifier(int argc, KrkValue argv[], int hasKw) { + if (IS_INTEGER(argv[0])) return BOOLEAN_VAL(!!c_keyword_qualifier(AS_INTEGER(argv[0]))); + if (!IS_STRING(argv[0])) return BOOLEAN_VAL(0); + if (AS_STRING(argv[0])->length > 1) return BOOLEAN_VAL(0); + return BOOLEAN_VAL(!!c_keyword_qualifier(AS_CSTRING(argv[0])[0])); +} + +static int callQualifier(KrkValue qualifier, int codepoint) { + if (IS_NATIVE(qualifier) && AS_NATIVE(qualifier)->function == bim_krk_state_cKeywordQualifier) return AS_BOOLEAN(!!c_keyword_qualifier(codepoint)); + krk_push(qualifier); + krk_push(INTEGER_VAL(codepoint)); + KrkValue result = krk_callSimple(krk_peek(1), 1, 1); + if (IS_BOOLEAN(result)) return AS_BOOLEAN(result); + return 0; +} + +#define KRK_STRING_FAST(string,offset) (uint32_t)\ + (string->type <= 1 ? ((uint8_t*)string->codes)[offset] : \ + (string->type == 2 ? ((uint16_t*)string->codes)[offset] : \ + ((uint32_t*)string->codes)[offset])) + +static KrkValue bim_krk_state_findKeywords(int argc, KrkValue argv[], int hasKw) { + BIM_STATE(); + if (unlikely(argc < 4 || !(IS_INSTANCE(argv[1]) && AS_INSTANCE(argv[1])->_class == vm.baseClasses->listClass) || !IS_INTEGER(argv[2]))) + return krk_runtimeError(vm.exceptions->typeError, "invalid arguments to SyntaxState.findKeywords"); + + KrkValue qualifier = argv[3]; + int flag = AS_INTEGER(argv[2]); + + if (callQualifier(qualifier, lastchar())) return BOOLEAN_VAL(0); + if (!callQualifier(qualifier, charat())) return BOOLEAN_VAL(0); + + for (size_t keyword = 0; keyword < AS_LIST(argv[1])->count; ++keyword) { + if (!IS_STRING(AS_LIST(argv[1])->values[keyword])) + return krk_runtimeError(vm.exceptions->typeError, "expected list of strings, found '%s'", krk_typeName(AS_LIST(argv[1])->values[keyword])); + + KrkString * me = AS_STRING(AS_LIST(argv[1])->values[keyword]); + size_t d = 0; + if (me->type == KRK_STRING_ASCII) { + while (state->i + (int)d < state->line->actual && + d < me->codesLength && + state->line->text[state->i+d].codepoint == me->chars[d]) d++; + } else { + krk_unicodeString(me); + while (state->i + (int)d < state->line->actual && + d < me->codesLength && + state->line->text[state->i+d].codepoint == KRK_STRING_FAST(me,d)) d++; + } + if (d == me->codesLength && (state->i + (int)d >= state->line->actual || + !callQualifier(qualifier,state->line->text[state->i+d].codepoint))) { + paint((int)me->codesLength, flag); + return BOOLEAN_VAL(1); + } + } + return BOOLEAN_VAL(0); +} +static KrkValue bim_krk_state_matchAndPaint(int argc, KrkValue argv[], int hasKw) { + BIM_STATE(); + if (argc < 4 || !IS_STRING(argv[1]) || !IS_INTEGER(argv[2])) + return krk_runtimeError(vm.exceptions->typeError, "invalid arguments to SyntaxState.matchAndPaint"); + KrkValue qualifier = argv[3]; + int flag = AS_INTEGER(argv[2]); + KrkString * me = AS_STRING(argv[1]); + size_t d = 0; + if (me->type == KRK_STRING_ASCII) { + while (state->i + (int)d < state->line->actual && + d < me->codesLength && + state->line->text[state->i+d].codepoint == me->chars[d]) d++; } else { - f = fopen(argv[1],"r"); + krk_unicodeString(me); + while (state->i + (int)d < state->line->actual && + d < me->codesLength && + state->line->text[state->i+d].codepoint == KRK_STRING_FAST(me,d)) d++; } - if (!f) { - render_error("Failed to open script"); - return 1; + if (d == me->codesLength && (state->i + (int)d >= state->line->actual || + !callQualifier(qualifier,state->line->text[state->i+d].codepoint))) { + paint((int)me->codesLength, flag); + return BOOLEAN_VAL(1); + } + return BOOLEAN_VAL(0); +} +static KrkValue bim_krk_state_rewind(int argc, KrkValue argv[], int hasKw) { + BIM_STATE(); + state->i -= AS_INTEGER(argv[1]); + return NONE_VAL(); +} +static KrkValue bim_krk_state_commentBuzzwords(int argc, KrkValue argv[], int hasKw) { + BIM_STATE(); + return BOOLEAN_VAL(common_comment_buzzwords(state)); +} +static KrkValue bim_krk_state_init(int argc, KrkValue argv[], int hasKw) { + BIM_STATE(); + if (argc < 2 || !krk_isInstanceOf(argv[1], syntaxStateClass)) { + return krk_runtimeError(vm.exceptions->typeError, "Can only initialize subhighlighter from an existing highlighter."); } - int retval = 0; - char linebuf[4096]; - int line = 1; - int was_collecting_function = 0; - char * function_name = NULL; - struct bim_function * new_function = NULL; - struct bim_function * last_function = NULL; + *state = ((struct SyntaxState*)AS_INSTANCE(argv[1]))->state; - while (!feof(f)) { - memset(linebuf, 0, 4096); - fgets(linebuf, 4095, f); - /* Remove linefeed */ - char * s = strstr(linebuf, "\n"); - if (s) *s = '\0'; - - /* See if this is a special syntax element */ - if (!strncmp(linebuf, "function ", 9)) { - /* Confirm we have a function name */ - if (was_collecting_function) { - free_function(new_function); - render_error("Syntax error on line %d: attempt nest function while already defining function '%s'", line, function_name); - retval = 1; - break; - } - if (!strlen(linebuf+9)) { - render_error("Syntax error on line %d: function needs a name", line); - retval = 1; - break; - } - function_name = strdup(linebuf+9); - was_collecting_function = 1; - new_function = malloc(sizeof(struct bim_function)); - new_function->command = strdup(function_name); - new_function->next = NULL; - last_function = new_function; - /* Set up function */ - } else if (!strcmp(linebuf,"end")) { - if (!was_collecting_function) { - render_error("Syntax error on line %d: unexpected 'end'", line); - retval = 1; - break; - } - was_collecting_function = 0; - /* See if a function with this name is already defined */ - int this = -1; - for (int i = 0; i < flex_user_functions_count; ++i) { - if (user_functions[i] && !strcmp(user_functions[i]->command, function_name)) { - this = i; - break; - } - } - if (this > -1) { - free_function(user_functions[this]); - user_functions[this] = new_function; - } else { - add_user_function(new_function); - if (strstr(function_name,"theme:") == function_name) { - add_colorscheme((struct theme_def){strdup(function_name+6), load_colorscheme_script}); - } - } - free(function_name); - new_function = NULL; - last_function = NULL; - function_name = NULL; - } else if (was_collecting_function) { - /* Collect function */ - last_function->next = malloc(sizeof(struct bim_function)); - last_function = last_function->next; - char * s = linebuf; - while (*s == ' ') s++; - last_function->command = strdup(s); - last_function->next = NULL; + return argv[0]; +} + +static KrkValue krk_bim_get_commands(int argc, KrkValue argv[], int hasKw) { + KrkValue myList = krk_list_of(0, NULL,0); + krk_push(myList); + for (struct command_def * c = regular_commands; regular_commands && c->name; ++c) { + krk_writeValueArray(AS_LIST(myList), OBJECT_VAL(krk_copyString(c->name,strlen(c->name)))); + } + for (struct command_def * c = prefix_commands; prefix_commands && c->name; ++c) { + krk_writeValueArray(AS_LIST(myList), OBJECT_VAL(krk_copyString(c->name,strlen(c->name)))); + } + return krk_pop(); +} + +KrkClass * ActionDef; +struct ActionDef { + KrkInstance inst; + struct action_def * action; +}; + +static KrkValue bim_krk_action_call(int argc, KrkValue argv[], int hasKw) { + struct ActionDef * self = (void*)AS_OBJECT(argv[0]); + + /* Figure out arguments */ + int args = 0; + if (self->action->options & ARG_IS_CUSTOM) args++; + if (self->action->options & ARG_IS_INPUT) args++; + if (self->action->options & ARG_IS_PROMPT) args++; + + int argsAsInts[3] = { 0, 0, 0 }; + for (int i = 0; i < args; i++) { + if (argc < i + 2) + return krk_runtimeError(vm.exceptions->argumentError, "%s() takes %d argument%s", + self->action->name, args, args == 1 ? "" : "s"); + if (IS_INTEGER(argv[i+1])) { + argsAsInts[i] = AS_INTEGER(argv[i+1]); + } else if (IS_STRING(argv[i+1]) && AS_STRING(argv[i+1])->codesLength == 1) { + argsAsInts[i] = krk_unicodeCodepoint(AS_STRING(argv[i+1]), 0); + } else if (IS_BOOLEAN(argv[i+1])) { + argsAsInts[i] = AS_BOOLEAN(argv[i+1]); } else { - int result = process_command(linebuf); - if (result != 0) { - retval = result; - break; - } + return krk_runtimeError(vm.exceptions->typeError, + "argument to %s() must be int, bool, or str of len 1", + self->action->name); } + } + + self->action->action(argsAsInts[0], argsAsInts[1], argsAsInts[2]); + + return NONE_VAL(); +} - line++; +static KrkValue bim_krk_command_call(int argc, KrkValue argv[], int hasKw) { + struct CommandDef * self = (void*)AS_OBJECT(argv[0]); + + char ** args = malloc(sizeof(char*)*argc); + args[0] = strdup(self->command->name); + + for (int i = 1; i < argc; ++i) { + if (IS_STRING(argv[i])) { + args[i] = strdup(AS_CSTRING(argv[i])); + } else { + krk_push(argv[i]); + KrkValue asString = krk_callSimple(OBJECT_VAL(krk_getType(argv[i])->_tostr), 1, 0); + args[i] = strdup(AS_CSTRING(asString)); + } } - if (was_collecting_function) { - free_function(new_function); - render_error("Syntax error on line %d: unexpected end of file while defining function '%s'", line, function_name); - retval = 1; + int result = self->command->command(args[0], argc, args); + + for (int i = 0; i < argc; ++i) { + free(args[i]); } + free(args); - if (function_name) free(function_name); - fclose(f); - return retval; + return INTEGER_VAL(result); } -BIM_COMMAND(rundir,"rundir","Run scripts from a directory, in unspecified order") { - if (argc < 2) return 1; - char * file = argv[1]; - DIR * dirp = NULL; - if (file[0] == '~') { - char * home = getenv("HOME"); - if (home) { - char * _file = malloc(strlen(file) + strlen(home) + 4); /* Paranoia */ - sprintf(_file, "%s%s", home, file+1); - dirp = opendir(_file); - free(_file); - } +static void makeClass(KrkInstance * module, KrkClass ** _class, const char * name, KrkClass * base) { + KrkString * str_Name = krk_copyString(name,strlen(name)); + krk_push(OBJECT_VAL(str_Name)); + *_class = krk_newClass(str_Name, base); + krk_push(OBJECT_VAL(*_class)); + /* Bind it */ + krk_attachNamedObject(&module->fields,name,(KrkObj*)*_class); + krk_pop(); + krk_pop(); +} + +void import_directory(char * dirName) { + const char * extra = ""; + char * dirpath = NULL; + char file[4096]; + if (vm.binpath) { + char * tmp = strdup(vm.binpath); + dirpath = strdup(dirname(tmp)); + extra = "/"; + free(tmp); + sprintf(file, "%s/%s", dirpath, dirName); } else { + sprintf(file, "%s", dirName); + } + + DIR * dirp = opendir(file); + if (!dirp && dirpath) { + /* Try ../share/bim/dirName */ + sprintf(file, "%s/../share/bim/%s", dirpath, dirName); + extra = "/../share/bim/"; dirp = opendir(file); } if (!dirp) { - render_error("Directory is not accessible: %s", argv[1]); - return 1; + /* Try one last fallback */ + if (dirpath) free(dirpath); + dirpath = strdup("/usr/share/bim"); + sprintf(file, "%s/%s", dirpath, dirName); + extra = "/"; + dirp = opendir(file); + } + if (!dirp) { + fprintf(stderr, "Could not find startup files: %s\n", dirName); + exit(1); + } + + if (dirpath) { + /* This is a dumb hack. */ + char tmp[4096]; + sprintf(tmp, + "import kuroko\n" + "if '%s%s' not in kuroko.module_paths:\n" + " kuroko.module_paths.insert(0,'%s%s')\n", + dirpath, extra, dirpath, extra); + krk_interpret(tmp,""); } + + if (dirpath) free(dirpath); struct dirent * ent = readdir(dirp); while (ent) { - if (str_ends_with(ent->d_name,".bimscript")) { - char * tmp = malloc(strlen(file) + 1 + strlen(ent->d_name) + 1); - snprintf(tmp, strlen(file) + 1 + strlen(ent->d_name) + 1, "%s/%s", file, ent->d_name); - char * args[] = {"runscript",tmp,NULL}; - bim_command_runscript("runscript", 2, args); + if (str_ends_with(ent->d_name,".krk") && !str_ends_with(ent->d_name,"__init__.krk")) { + char * tmp = malloc(strlen(dirName) + 1 + strlen(ent->d_name) + 1 + 7); + snprintf(tmp, strlen(dirName) + 1 + strlen(ent->d_name) + 1 + 7, "import %s.%s", dirName, ent->d_name); + tmp[strlen(tmp)-4] = '\0'; + krk_interpret(tmp,ent->d_name); free(tmp); } ent = readdir(dirp); } - return 0; + closedir(dirp); } -/** - * Load bimrc configuration file. - * - * At the moment, this a simple key=value list. - */ -void load_bimrc(void) { - if (!global_config.bimrc_path) return; - - /* Default is ~/.bimrc */ - char * tmp = strdup(global_config.bimrc_path); - - if (!*tmp) { - free(tmp); - return; - } - - /* Parse ~ at the front of the path. */ - if (*tmp == '~') { - char path[1024] = {0}; - char * home = getenv("HOME"); - if (!home) { - /* $HOME is unset? */ - free(tmp); - return; +static void findBim(char * argv[]) { + /* Try asking /proc */ + char * binpath = realpath("/proc/self/exe", NULL); + if (!binpath) { + if (strchr(argv[0], '/')) { + binpath = realpath(argv[0], NULL); + } else { + /* Search PATH for argv[0] */ + char * _path = strdup(getenv("PATH")); + char * path = _path; + while (path) { + char * next = strchr(path,':'); + if (next) *next++ = '\0'; + + char tmp[4096]; + sprintf(tmp, "%s/%s", path, argv[0]); + if (access(tmp, X_OK)) { + binpath = strdup(tmp); + break; + } + path = next; + } + free(_path); } - - /* New path is $HOME/.bimrc */ - snprintf(path, 1024, "%s%s", home, tmp+1); - free(tmp); - tmp = strdup(path); } + if (binpath) { + vm.binpath = binpath; + } /* Else, give up at this point and just don't attach it at all. */ +} - struct stat statbuf; - if (stat(tmp, &statbuf)) return; +BIM_COMMAND(reload,"reload","Reloads all the Kuroko stuff.") { + /* Unload everything syntax-y */ + KrkValue result = krk_interpret( + "if True:\n" + " import kuroko\n" + " for mod in kuroko.modules():\n" + " if mod.startswith('syntax.') or mod.startswith('themes.'):\n" + " kuroko.unload(mod)\n", ""); - char * args[] = {"runscript", tmp, NULL}; - if (bim_command_runscript("runscript", 2, args)) { - /* Wait */ - render_error("Errors were encountered when loading bimrc. Press ENTER to continue."); - int c; - while ((c = bim_getch(), c != ENTER_KEY && c != LINE_FEED)); + if (IS_NONE(result) && (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) { + krk_dumpTraceback(); + return 1; } + + /* Reload everything */ + krk_resetStack(); + krk_startModule(""); + import_directory("syntax"); + krk_startModule(""); + import_directory("themes"); + krk_startModule(""); + /* Re-run the RC file */ + load_bimrc(); + krk_resetStack(); + return 0; } -/** - * Set some default values when certain terminals are detected. - */ -void detect_weird_terminals(void) { +static KrkValue krk_bim_getDocumentText(int argc, KrkValue argv[], int hasKw) { + struct StringBuilder sb = {0}; - char * term = getenv("TERM"); - if (term && !strcmp(term,"linux")) { - /* Linux VTs can't scroll. */ - global_config.can_scroll = 0; - } - if (term && !strcmp(term,"cons25")) { - /* Dragonfly BSD console */ - global_config.can_hideshow = 0; - global_config.can_altscreen = 0; - global_config.can_mouse = 0; - global_config.can_unicode = 0; - global_config.can_bright = 0; - } - if (term && !strcmp(term,"sortix")) { - /* sortix will spew title escapes to the screen, no good */ - global_config.can_title = 0; - } - if (term && strstr(term,"tmux") == term) { - global_config.can_scroll = 0; - global_config.can_bce = 0; - } - if (term && strstr(term,"screen") == term) { - /* unfortunately */ - global_config.can_24bit = 0; - global_config.can_italic = 0; - } - if (term && strstr(term,"toaru-vga") == term) { - global_config.can_24bit = 0; /* Also not strictly true */ - global_config.can_256color = 0; /* Not strictly true */ - } - if (term && strstr(term,"xterm-256color") == term) { - global_config.can_insert = 1; - global_config.can_bracketedpaste = 1; - char * term_emu = getenv("TERMINAL_EMULATOR"); - if (term_emu && strstr(term_emu,"JetBrains")) { - global_config.can_bce = 0; + int i, j; + for (i = 0; i < env->line_count; ++i) { + line_t * line = env->lines[i]; + for (j = 0; j < line->actual; j++) { + char_t c = line->text[j]; + if (c.codepoint == 0) { + pushStringBuilder(&sb, 0); + } else { + char tmp[8] = {0}; + int len = to_eight(c.codepoint, tmp); + pushStringBuilderStr(&sb, tmp, len); + } } - } - if (term && strstr(term,"toaru") == term) { - global_config.can_insert = 1; - global_config.can_bracketedpaste = 1; + pushStringBuilder(&sb, '\n'); } - if (!global_config.can_unicode) { - global_config.tab_indicator = strdup(">"); - global_config.space_indicator = strdup("-"); - } else { - global_config.tab_indicator = strdup("»"); - global_config.space_indicator = strdup("·"); - } + return finishStringBuilder(&sb); +} +static KrkValue krk_bim_renderError(int argc, KrkValue argv[], int hasKw) { + static const char * _method_name = "renderError"; + if (argc != 1 || !IS_STRING(argv[0])) return TYPE_ERROR(str,argv[0]); + if (AS_STRING(argv[0])->length == 0) + redraw_commandline(); + else + render_error(AS_CSTRING(argv[0])); + return NONE_VAL(); } /** @@ -10407,15 +10720,124 @@ void initialize(void) { CLONE_MAP(ESCAPE_MAP); CLONE_MAP(COMMAND_MAP); CLONE_MAP(SEARCH_MAP); + CLONE_MAP(FILESEARCH_MAP); CLONE_MAP(INPUT_BUFFER_MAP); #undef CLONE_MAP - /* Detect terminal quirks */ - detect_weird_terminals(); + /* Simple ASCII defaults, but you could use " " as a config option */ + global_config.tab_indicator = strdup(">"); + global_config.space_indicator = strdup("-"); + +#if 0 + krk_initVM(KRK_GLOBAL_CALLGRIND); /* no debug flags */ + vm.callgrindFile = fopen("callgrind.out","w"); +#else + krk_initVM(0); /* no debug flags */ +#endif + + KrkInstance * bimModule = krk_newInstance(vm.baseClasses->moduleClass); + krk_attachNamedObject(&vm.modules, "bim", (KrkObj*)bimModule); + krk_attachNamedObject(&bimModule->fields, "__name__", (KrkObj*)S("bim")); + krk_defineNative(&bimModule->fields, "bindHighlighter", krk_bim_register_syntax); + krk_defineNative(&bimModule->fields, "getCommands", krk_bim_get_commands); + krk_bim_theme_dict = krk_dict_of(0,NULL,0); + krk_attachNamedValue(&bimModule->fields, "themes", krk_bim_theme_dict); + krk_defineNative(&bimModule->fields, "defineTheme", krk_bim_define_theme); + krk_bim_syntax_dict = krk_dict_of(0,NULL,0); + krk_attachNamedValue(&bimModule->fields, "highlighters", krk_bim_syntax_dict); + + krk_defineNative(&bimModule->fields, "getDocumentText", krk_bim_getDocumentText); + krk_defineNative(&bimModule->fields, "renderError", krk_bim_renderError); + + makeClass(bimModule, &ActionDef, "Action", vm.baseClasses->objectClass); + ActionDef->allocSize = sizeof(struct ActionDef); + krk_defineNative(&ActionDef->methods, "__call__", bim_krk_action_call); + krk_finalizeClass(ActionDef); + + for (struct action_def * a = mappable_actions; mappable_actions && a->name; ++a) { + struct ActionDef * actionObj = (void*)krk_newInstance(ActionDef); + actionObj->action = a; + krk_attachNamedObject(&vm.builtins->fields, a->name, (KrkObj*)actionObj); + } + + makeClass(bimModule, &CommandDef, "Command", vm.baseClasses->objectClass); + CommandDef->allocSize = sizeof(struct CommandDef); + krk_defineNative(&CommandDef->methods, "__call__", bim_krk_command_call); + krk_finalizeClass(CommandDef); + + KrkInstance * global = krk_newInstance(vm.baseClasses->objectClass); + krk_attachNamedObject(&vm.builtins->fields, "global", (KrkObj*)global); + + for (struct command_def * c = regular_commands; regular_commands && c->name; ++c) { + struct CommandDef * commandObj = (void*)krk_newInstance(CommandDef); + commandObj->command = c; + if (strstr(c->name,"global.") == c->name) { + krk_attachNamedObject(&global->fields, c->name + 7, (KrkObj*)commandObj); + } else { + krk_attachNamedObject(&vm.builtins->fields, c->name, (KrkObj*)commandObj); + } + } + + makeClass(bimModule, &syntaxStateClass, "SyntaxState", vm.baseClasses->objectClass); + syntaxStateClass->allocSize = sizeof(struct SyntaxState); + krk_defineNative(&syntaxStateClass->methods, "set_state", bim_krk_state_setstate); /* TODO property? */ + krk_defineNative(&syntaxStateClass->methods, "state", bim_krk_state_getstate)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY; + krk_defineNative(&syntaxStateClass->methods, "i", bim_krk_state_index)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY; + krk_defineNative(&syntaxStateClass->methods, "lineno", bim_krk_state_lineno)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY; + krk_defineNative(&syntaxStateClass->methods, "__init__", bim_krk_state_init); + /* These ones take argumens so they're methods instead of dynamic fields */ + krk_defineNative(&syntaxStateClass->methods, "findKeywords", bim_krk_state_findKeywords); + krk_defineNative(&syntaxStateClass->methods, "cKeywordQualifier", bim_krk_state_cKeywordQualifier)->flags |= KRK_NATIVE_FLAGS_IS_STATIC_METHOD; + krk_defineNative(&syntaxStateClass->methods, "isdigit", bim_krk_state_isdigit); + krk_defineNative(&syntaxStateClass->methods, "isxdigit", bim_krk_state_isxdigit); + krk_defineNative(&syntaxStateClass->methods, "paint", bim_krk_state_paint); + krk_defineNative(&syntaxStateClass->methods, "paintComment", bim_krk_state_paintComment); + krk_defineNative(&syntaxStateClass->methods, "skip", bim_krk_state_skip); + krk_defineNative(&syntaxStateClass->methods, "matchAndPaint", bim_krk_state_matchAndPaint); + krk_defineNative(&syntaxStateClass->methods, "commentBuzzwords", bim_krk_state_commentBuzzwords); + krk_defineNative(&syntaxStateClass->methods, "rewind", bim_krk_state_rewind); + krk_defineNative(&syntaxStateClass->methods, "__getitem__", bim_krk_state_get); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_NONE", INTEGER_VAL(FLAG_NONE)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_KEYWORD", INTEGER_VAL(FLAG_KEYWORD)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_STRING", INTEGER_VAL(FLAG_STRING)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_COMMENT", INTEGER_VAL(FLAG_COMMENT)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_TYPE", INTEGER_VAL(FLAG_TYPE)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_PRAGMA", INTEGER_VAL(FLAG_PRAGMA)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_NUMERAL", INTEGER_VAL(FLAG_NUMERAL)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_ERROR", INTEGER_VAL(FLAG_ERROR)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_DIFFPLUS", INTEGER_VAL(FLAG_DIFFPLUS)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_DIFFMINUS", INTEGER_VAL(FLAG_DIFFMINUS)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_NOTICE", INTEGER_VAL(FLAG_NOTICE)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_BOLD", INTEGER_VAL(FLAG_BOLD)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_LINK", INTEGER_VAL(FLAG_LINK)); + krk_attachNamedValue(&syntaxStateClass->methods, "FLAG_ESCAPE", INTEGER_VAL(FLAG_ESCAPE)); + + _bim_state_chars = krk_newTuple(95); + krk_attachNamedObject(&syntaxStateClass->methods, "__chars__", (KrkObj*)_bim_state_chars); + for (int c = 0; c < 95; ++c) { + char tmp = c + 32; + _bim_state_chars->values.values[_bim_state_chars->values.count++] = OBJECT_VAL(krk_copyString(&tmp,1)); + } + + krk_finalizeClass(syntaxStateClass); + + krk_resetStack(); + + krk_startModule(""); + import_directory("syntax"); + krk_startModule(""); + import_directory("themes"); + + /* Start context for command line */ + krk_startModule(""); /* Load bimrc */ load_bimrc(); + krk_resetStack(); + + /* Disable default traceback printing */ + vm.globalFlags |= KRK_GLOBAL_CLEAN_OUTPUT; /* Initialize space for buffers */ buffers_avail = 4; @@ -10467,22 +10889,41 @@ void dump_mapping(const char * description, struct action_map * map) { printf("\n"); } +int sort_regular_commands(const void * a, const void * b) { + return strcmp(regular_commands[*(int *)a].name, regular_commands[*(int *)b].name); +} + +int sort_prefix_commands(const void * a, const void * b) { + return strcmp(prefix_commands[*(int *)a].name, prefix_commands[*(int *)b].name); +} + void dump_commands(void) { printf("## Regular Commands\n"); printf("\n"); printf("| **Command** | **Description** |\n"); printf("|-------------|-----------------|\n"); - for (struct command_def * c = regular_commands; regular_commands && c->name; ++c) { - printf("| `:%s` | %s |\n", c->name, c->description); + int * offsets = malloc(sizeof(int) * flex_regular_commands_count); + for (int i = 0; i < flex_regular_commands_count; ++i) { + offsets[i] = i; } + qsort(offsets, flex_regular_commands_count, sizeof(int), sort_regular_commands); + for (int i = 0; i < flex_regular_commands_count; ++i) { + printf("| `:%s` | %s |\n", regular_commands[offsets[i]].name, regular_commands[offsets[i]].description); + } + free(offsets); printf("\n"); printf("## Prefix Commands\n"); printf("\n"); printf("| **Command** | **Description** |\n"); printf("|-------------|-----------------|\n"); - for (struct command_def * c = prefix_commands; prefix_commands && c->name; ++c) { - printf("| `:%s...` | %s |\n", !strcmp(c->name, "`") ? "`(backtick)`" : c->name, c->description); - } + offsets = malloc(sizeof(int) * flex_prefix_commands_count); + for (int i = 0; i < flex_prefix_commands_count; ++i) offsets[i] = i; + qsort(offsets, flex_prefix_commands_count, sizeof(int), sort_regular_commands); + for (int i = 0; i < flex_prefix_commands_count; ++i) { + printf("| `:%s...` | %s |\n", !strcmp(prefix_commands[offsets[i]].name, "`") ? "`(backtick)`" : + prefix_commands[offsets[i]].name, prefix_commands[offsets[i]].description); + } + free(offsets); printf("\n"); } @@ -10491,7 +10932,7 @@ BIM_COMMAND(whatis,"whatis","Describe actions bound to a key in different modes. if (argc < 2) { render_commandline_message("(press a key)"); - while ((key = bim_getkey(200)) == KEY_TIMEOUT); + while ((key = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT); } else if (strlen(argv[1]) > 1 && argv[1][0] == '^') { /* See if it's a valid ctrl key */ if (argv[1][2] != '\0') { @@ -10578,8 +11019,7 @@ BIM_COMMAND(setcolor, "setcolor", "Set colorscheme colors") { pause_for_key(); } else { char * colorname = argv[1]; - char * space = strstr(colorname, " "); - if (!space) { + if (argc == 2) { struct ColorName * c = color_names; while (c->name) { if (!strcmp(c->name, colorname)) { @@ -10591,8 +11031,7 @@ BIM_COMMAND(setcolor, "setcolor", "Set colorscheme colors") { render_error(":setcolor "); return 1; } - char * colorvalue = space + 1; - *space = '\0'; + char * colorvalue = argv[2]; struct ColorName * c = color_names; while (c->name) { if (!strcmp(c->name, colorname)) { @@ -10636,21 +11075,9 @@ BIM_COMMAND(action,"action","Execute a bim action") { /* Split argument on spaces */ char * action = argv[1]; char * arg1 = NULL, * arg2 = NULL, * arg3 = NULL; - arg1 = strstr(argv[1]," "); - if (arg1) { - *arg1 = '\0'; - arg1++; - arg2 = strstr(arg1," "); - if (arg2) { - *arg2 = '\0'; - arg2++; - arg3 = strstr(arg1," "); - if (arg3) { - *arg3 = '\0'; - arg3++; - } - } - } + if (argc > 2) arg1 = argv[2]; + if (argc > 3) arg2 = argv[3]; + if (argc > 4) arg3 = argv[4]; /* Find the action */ for (int i = 0; i < flex_mappable_actions_count; ++i) { @@ -10849,6 +11276,7 @@ BIM_COMMAND(mapkey,"mapkey","Map a key to an action.") { } int main(int argc, char * argv[]) { + findBim(argv); int opt; while ((opt = getopt(argc, argv, "?c:C:u:RS:O:-:")) != -1) { switch (opt) { @@ -10859,9 +11287,11 @@ int main(int argc, char * argv[]) { case 'C': /* Print file to stdout using our syntax highlighting and color theme */ initialize(); + global_config.use_biminfo = 0; global_config.go_to_line = 0; open_file(optarg); for (int i = 0; i < env->line_count; ++i) { + recalculate_syntax(env->lines[i], i); if (opt == 'C') { draw_line_number(i); } @@ -10878,17 +11308,7 @@ int main(int argc, char * argv[]) { break; case 'O': /* Set various display options */ - if (!strcmp(optarg,"noaltscreen")) global_config.can_altscreen = 0; - else if (!strcmp(optarg,"noscroll")) global_config.can_scroll = 0; - else if (!strcmp(optarg,"nomouse")) global_config.can_mouse = 0; - else if (!strcmp(optarg,"nounicode")) global_config.can_unicode = 0; - else if (!strcmp(optarg,"nobright")) global_config.can_bright = 0; - else if (!strcmp(optarg,"nohideshow")) global_config.can_hideshow = 0; - else if (!strcmp(optarg,"nosyntax")) global_config.highlight_on_open = 0; - else if (!strcmp(optarg,"nohistory")) global_config.history_enabled = 0; - else if (!strcmp(optarg,"notitle")) global_config.can_title = 0; - else if (!strcmp(optarg,"nobce")) global_config.can_bce = 0; - else { + if (set_capability(optarg)) { fprintf(stderr, "%s: unrecognized -O option: %s\n", argv[0], optarg); return 1; } @@ -10896,17 +11316,36 @@ int main(int argc, char * argv[]) { case '-': if (!strcmp(optarg,"version")) { initialize(); /* Need to load bimrc to get themes */ - fprintf(stderr, "bim %s%s - %s\n", BIM_VERSION, BIM_BUILD_DATE, BIM_COPYRIGHT); - fprintf(stderr, " Available syntax highlighters:"); + update_screen_size(); /* Get terminal size if possible */ + fprintf(stderr, "\033[1mbim\033[0m %s%s\n%s\n\n", BIM_VERSION, BIM_BUILD_DATE, BIM_COPYRIGHT); + #define SECTION(title) do { \ + int x, width; \ + width = 2 + display_width_of_string(title); \ + fprintf(stderr, " \033[1m%s\033[0m:", title); \ + x = width; + #define ENDSECTION() fprintf(stderr, "\n\n"); } while (0) + #define ITEM(str) do { \ + int my_width = display_width_of_string(str); \ + if (x + my_width + 1 >= global_config.term_width) { \ + fprintf(stderr, "\n"); \ + for (x = 0; x <= width; ++x) fprintf(stderr, " "); \ + fprintf(stderr, "%s", str); \ + x += width; \ + } else { \ + fprintf(stderr, " %s", str); \ + x += my_width + 1; \ + } } while(0) + + SECTION("Syntax"); for (struct syntax_definition * s = syntaxes; syntaxes && s->name; ++s) { - fprintf(stderr, " %s", s->name); + ITEM(s->name); } - fprintf(stderr, "\n"); - fprintf(stderr, " Available color themes:"); + ENDSECTION(); + SECTION("Themes"); for (struct theme_def * d = themes; themes && d->name; ++d) { - fprintf(stderr, " %s", d->name); + ITEM(d->name); } - fprintf(stderr, "\n"); + ENDSECTION(); return 0; } else if (!strcmp(optarg,"help")) { show_usage(argv); @@ -10929,6 +11368,9 @@ int main(int argc, char * argv[]) { initialize(); global_config.go_to_line = 0; open_file(argv[optind]); + for (int i = 0; i < env->line_count; ++i) { + recalculate_syntax(env->lines[i], i); + } convert_to_html(); /* write to stdout */ output_file(env, stdout); @@ -10981,2472 +11423,3 @@ int main(int argc, char * argv[]) { return 0; } -char * syn_bash_keywords[] = { - /* Actual bash keywords */ - "if","then","else","elif","fi","case","esac","for","coproc", - "select","while","until","do","done","in","function","time", - /* Other keywords */ - "exit","return","source","function","export","alias","complete","shopt","local","eval", - /* Common Unix utilities */ - "echo","cd","pushd","popd","printf","sed","rm","mv", - NULL -}; - -int bash_pop_state(int state) { - int new_state = state / 100; - return new_state * 10; -} - -int bash_push_state(int state, int new) { - return state * 10 + new; -} - -int bash_paint_tick(struct syntax_state * state, int out_state) { - int last = -1; - while (charat() != -1) { - if (last != '\\' && charat() == '\'') { - paint(1, FLAG_STRING); - return bash_pop_state(out_state); - } else if (last == '\\') { - paint(1, FLAG_STRING); - last = -1; - } else if (charat() != -1) { - last = charat(); - paint(1, FLAG_STRING); - } - } - return out_state; -} - -int bash_paint_braced_variable(struct syntax_state * state) { - while (charat() != -1) { - if (charat() == '}') { - paint(1, FLAG_NUMERAL); - return 0; - } - paint(1, FLAG_NUMERAL); - } - return 0; -} - -int bash_special_variable(int c) { - return (c == '@' || c == '?'); -} - -int bash_paint_string(struct syntax_state * state, char terminator, int out_state, int color) { - int last = -1; - state->state = out_state; - while (charat() != -1) { - if (last != '\\' && charat() == terminator) { - paint(1, color); - return bash_pop_state(state->state); - } else if (last == '\\') { - paint(1, color); - last = -1; - } else if (terminator != '`' && charat() == '`') { - paint(1, FLAG_ESCAPE); - state->state = bash_paint_string(state,'`',bash_push_state(out_state, 20),FLAG_ESCAPE); - } else if (terminator != ')' && charat() == '$' && nextchar() == '(') { - paint(2, FLAG_TYPE); - state->state = bash_paint_string(state,')',bash_push_state(out_state, 30),FLAG_TYPE); - } else if (charat() == '$' && nextchar() == '{') { - paint(2, FLAG_NUMERAL); - bash_paint_braced_variable(state); - } else if (charat() == '$') { - paint(1, FLAG_NUMERAL); - if (bash_special_variable(charat())) { paint(1, FLAG_NUMERAL); continue; } - while (c_keyword_qualifier(charat())) paint(1, FLAG_NUMERAL); - } else if (terminator != '"' && charat() == '"') { - paint(1, FLAG_STRING); - state->state = bash_paint_string(state,'"',bash_push_state(out_state, 40),FLAG_STRING); - } else if (terminator != '"' && charat() == '\'') { /* No single quotes in regular quotes */ - paint(1, FLAG_STRING); - state->state = bash_paint_tick(state, out_state); - } else if (charat() != -1) { - last = charat(); - paint(1, color); - } - } - return state->state; -} - -int syn_bash_calculate(struct syntax_state * state) { - if (state->state < 1) { - if (charat() == '#') { - while (charat() != -1) { - if (common_comment_buzzwords(state)) continue; - else paint(1, FLAG_COMMENT); - } - return -1; - } else if (charat() == '\'') { - paint(1, FLAG_STRING); - return bash_paint_tick(state, 10); - } else if (charat() == '`') { - paint(1, FLAG_ESCAPE); - return bash_paint_string(state,'`',20,FLAG_ESCAPE); - } else if (charat() == '$' && nextchar() == '(') { - paint(2, FLAG_TYPE); - return bash_paint_string(state,')',30,FLAG_TYPE); - } else if (charat() == '"') { - paint(1, FLAG_STRING); - return bash_paint_string(state,'"',40,FLAG_STRING); - } else if (charat() == '$' && nextchar() == '{') { - paint(2, FLAG_NUMERAL); - bash_paint_braced_variable(state); - return 0; - } else if (charat() == '$') { - paint(1, FLAG_NUMERAL); - if (bash_special_variable(charat())) { paint(1, FLAG_NUMERAL); return 0; } - while (c_keyword_qualifier(charat())) paint(1, FLAG_NUMERAL); - return 0; - } else if (find_keywords(state, syn_bash_keywords, FLAG_KEYWORD, c_keyword_qualifier)) { - return 0; - } else if (charat() == ';') { - paint(1, FLAG_KEYWORD); - return 0; - } else if (c_keyword_qualifier(charat())) { - for (int i = 0; charrel(i) != -1; ++i) { - if (charrel(i) == ' ') break; - if (charrel(i) == '=') { - for (int j = 0; j < i; ++j) { - paint(1, FLAG_TYPE); - } - skip(); /* equals sign */ - return 0; - } - } - for (int i = 0; charrel(i) != -1; ++i) { - if (charrel(i) == '(') { - for (int j = 0; j < i; ++j) { - paint(1, FLAG_TYPE); - } - return 0; - } - if (!c_keyword_qualifier(charrel(i)) && charrel(i) != '-' && charrel(i) != ' ') break; - } - skip(); - return 0; - } else if (charat() != -1) { - skip(); - return 0; - } - } else if (state->state < 10) { - /* - * TODO: I have an idea of how to do up to `n` (here... 8?) heredocs - * by storing them in a static table and using the index into that table - * for the state, but it's iffy. It would work well in situations where - * someoen used the same heredoc repeatedly throughout their document. - */ - } else if (state->state >= 10) { - /* Nested string states */ - while (charat() != -1) { - int s = (state->state / 10) % 10; - if (s == 1) { - state->state = bash_paint_string(state,'\'',state->state,FLAG_STRING); - } else if (s == 2) { - state->state = bash_paint_string(state,'`',state->state,FLAG_ESCAPE); - } else if (s == 3) { - state->state = bash_paint_string(state,')',state->state,FLAG_TYPE); - } else if (s == 4) { - state->state = bash_paint_string(state,'"',state->state,FLAG_STRING); - } else if (!s) { - return -1; - } - } - return state->state; - } - return -1; -} - -char * syn_bash_ext[] = { -#ifndef __toaru__ - ".sh", -#endif - ".bash",".bashrc", - NULL -}; - -BIM_SYNTAX_COMPLETER(bash) { - for (char ** keyword = syn_bash_keywords; *keyword; ++keyword) { - add_if_match((*keyword),"(sh keyword)"); - } - - return 0; -} - -BIM_SYNTAX_EXT(bash, 0, c_keyword_qualifier) -int cmd_qualifier(int c) { return c != -1 && c != ' '; } - -extern int syn_bash_calculate(struct syntax_state * state); -extern int syn_py_calculate(struct syntax_state * state); - -static int bimcmd_paint_replacement(struct syntax_state * state) { - paint(1, FLAG_KEYWORD); - char special = charat(); - paint(1, FLAG_TYPE); - while (charat() != -1 && charat() != special) { - paint(1, FLAG_DIFFMINUS); - } - if (charat() == special) paint(1, FLAG_TYPE); - while (charat() != -1 && charat() != special) { - paint(1, FLAG_DIFFPLUS); - } - if (charat() == special) paint(1, FLAG_TYPE); - while (charat() != -1) paint(1, FLAG_NUMERAL); - return -1; -} - -extern struct command_def * regular_commands; -extern struct command_def * prefix_commands; - -static int bimcmd_find_commands(struct syntax_state * state) { - for (struct command_def * c = regular_commands; regular_commands && c->name; ++c) { - if (match_and_paint(state, c->name, FLAG_KEYWORD, cmd_qualifier)) return 1; - } - for (struct command_def * c = prefix_commands; prefix_commands && c->name; ++c) { - if (match_and_paint(state, c->name, FLAG_KEYWORD, cmd_qualifier)) return 1; - } - return 0; -} - -static char * bimscript_comments[] = { - "@author","@version","@url","@description", - NULL -}; - -static int bcmd_at_keyword_qualifier(int c) { - return isalnum(c) || (c == '_') || (c == '@'); -} - -int syn_bimcmd_calculate(struct syntax_state * state) { - if (state->i == 0) { - while (charat() == ' ') skip(); - if (charat() == '#') { - while (charat() != -1) { - if (charat() == '@') { - if (!find_keywords(state, bimscript_comments, FLAG_ESCAPE, bcmd_at_keyword_qualifier)) { - paint(1, FLAG_COMMENT); - } - } else { - paint(1, FLAG_COMMENT); - } - } - return -1; - } else if (match_and_paint(state, "function", FLAG_PRAGMA, cmd_qualifier)) { - while (charat() == ' ') skip(); - while (charat() != -1 && charat() != ' ') paint(1, FLAG_TYPE); - while (charat() != -1) paint(1, FLAG_ERROR); - return -1; - } else if (match_and_paint(state, "end", FLAG_PRAGMA, cmd_qualifier)) { - while (charat() != -1) paint(1, FLAG_ERROR); - return -1; - } else if (match_and_paint(state, "return", FLAG_PRAGMA, cmd_qualifier)) { - while (charat() == ' ') skip(); - while (charat() != -1 && charat() != ' ') paint(1, FLAG_NUMERAL); - return -1; - } else if (match_and_paint(state, "call", FLAG_KEYWORD, cmd_qualifier) || - match_and_paint(state, "trycall", FLAG_KEYWORD, cmd_qualifier) || - match_and_paint(state, "showfunction", FLAG_KEYWORD, cmd_qualifier)) { - while (charat() == ' ') skip(); - for (struct bim_function ** f = user_functions; user_functions && *f; ++f) { - if (match_and_paint(state, (*f)->command, FLAG_TYPE, cmd_qualifier)) break; - } - } else if (match_and_paint(state, "theme", FLAG_KEYWORD, cmd_qualifier) || - match_and_paint(state, "colorscheme", FLAG_KEYWORD, cmd_qualifier)) { - while (charat() == ' ') skip(); - for (struct theme_def * s = themes; themes && s->name; ++s) { - if (match_and_paint(state, s->name, FLAG_TYPE, cmd_qualifier)) break; - } - } else if (match_and_paint(state, "syntax", FLAG_KEYWORD, cmd_qualifier)) { - while (charat() == ' ') skip(); - for (struct syntax_definition * s = syntaxes; syntaxes && s->name; ++s) { - if (match_and_paint(state, s->name, FLAG_TYPE, cmd_qualifier)) return -1; - } - if (match_and_paint(state, "none", FLAG_TYPE, cmd_qualifier)) return -1; - } else if (match_and_paint(state, "setcolor", FLAG_KEYWORD, cmd_qualifier)) { - while (charat() == ' ') skip(); - for (struct ColorName * c = color_names; c->name; ++c) { - if (match_and_paint(state, c->name, FLAG_TYPE, cmd_qualifier)) { - while (charat() != -1) paint(1, FLAG_STRING); - return -1; - } - } - return -1; - } else if (match_and_paint(state, "mapkey", FLAG_KEYWORD, cmd_qualifier)) { - if (charat() == ' ') skip(); else { paint(1, FLAG_ERROR); return -1; } - for (struct mode_names * m = mode_names; m->name; ++m) { - if (match_and_paint(state, m->name, FLAG_TYPE, cmd_qualifier)) break; - } - if (charat() == ' ') skip(); else { paint(1, FLAG_ERROR); return -1; } - while (charat() != ' ' && charat() != -1) skip(); /* key name */ - if (charat() == ' ') skip(); else { paint(1, FLAG_ERROR); return -1; } - for (struct action_def * a = mappable_actions; a->name; ++a) { - if (match_and_paint(state, a->name, FLAG_TYPE, cmd_qualifier)) break; - } - match_and_paint(state, "none", FLAG_TYPE, cmd_qualifier); - if (charat() == -1) return -1; - if (charat() == ' ' && charat() != -1) skip(); else { paint(1, FLAG_ERROR); return -1; } - while (charat() != -1 && charat() != ' ') { - if (!strchr("racnwmb",charat())) { - paint(1, FLAG_ERROR); - } else { - skip(); - } - } - return -1; - } else if (match_and_paint(state, "action", FLAG_KEYWORD, cmd_qualifier)) { - while (charat() == ' ') skip(); - for (struct action_def * a = mappable_actions; a->name; ++a) { - if (match_and_paint(state, a->name, FLAG_TYPE, cmd_qualifier)) return -1; - } - } else if (charat() == '%' && nextchar() == 's') { - paint(1, FLAG_KEYWORD); - return bimcmd_paint_replacement(state); - } else if (charat() == 's' && !isalpha(nextchar())) { - return bimcmd_paint_replacement(state); - } else if (bimcmd_find_commands(state)) { - return -1; - } else if (charat() == '!') { - paint(1, FLAG_NUMERAL); - nest(syn_bash_calculate, 1); - } else if (charat() == '`') { - paint(1, FLAG_NUMERAL); - nest(syn_py_calculate, 1); - } else if (isdigit(charat()) || charat() == '-' || charat() == '+') { - paint(1, FLAG_NUMERAL); - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - return -1; - } - } - return -1; -} - -char * syn_bimcmd_ext[] = {".bimscript",".bimrc",NULL}; /* no files */ - -BIM_SYNTAX_COMPLETER(bimcmd) { - for (struct command_def * c = regular_commands; regular_commands && c->name; ++c) { - add_if_match(c->name,c->description); - } - add_if_match("function","Define a function"); - add_if_match("end","End a function definition"); - return 0; -} - -BIM_SYNTAX_EXT(bimcmd, 1, cmd_qualifier) -int syn_biminfo_calculate(struct syntax_state * state) { - if (state->i == 0) { - if (charat() == '#') { - while (charat() != -1) paint(1, FLAG_COMMENT); - } else if (charat() == '>') { - paint(1, FLAG_KEYWORD); - while (charat() != ' ') paint(1, FLAG_TYPE); - skip(); - while (charat() != -1) paint(1, FLAG_NUMERAL); - } else { - while (charat() != -1) paint(1, FLAG_ERROR); - } - } - return -1; -} - -char * syn_biminfo_ext[] = {".biminfo",NULL}; - -BIM_SYNTAX(biminfo, 0) -/** - * Syntax definition for C - */ -char * syn_c_keywords[] = { - "while","if","for","continue","return","break","switch","case","sizeof", - "struct","union","typedef","do","default","else","goto", - "alignas","alignof","offsetof","asm","__asm__", - /* C++ stuff */ - "public","private","class","using","namespace","virtual","override","protected", - "template","typename","static_cast","throw", - NULL -}; - -char * syn_c_types[] = { - "static","int","char","short","float","double","void","unsigned","volatile","const", - "register","long","inline","restrict","enum","auto","extern","bool","complex", - "uint8_t","uint16_t","uint32_t","uint64_t", - "int8_t","int16_t","int32_t","int64_t","FILE", - "ssize_t","size_t","uintptr_t","intptr_t","__volatile__", - "constexpr", - NULL -}; - -char * syn_c_special[] = { - "NULL", - "stdin","stdout","stderr", - "STDIN_FILENO","STDOUT_FILENO","STDERR_FILENO", - NULL -}; - -int c_keyword_qualifier(int c) { - return isalnum(c) || (c == '_'); -} - -/** - * Paints a basic C-style quoted string. - */ -void paint_c_string(struct syntax_state * state) { - /* Assumes you came in from a check of charat() == '"' */ - paint(1, FLAG_STRING); - int last = -1; - while (charat() != -1) { - if (last != '\\' && charat() == '"') { - paint(1, FLAG_STRING); - return; - } else if (charat() == '\\' && (nextchar() == '\\' || nextchar() == 'n' || nextchar() == 'r')) { - paint(2, FLAG_ESCAPE); - last = -1; - } else if (charat() == '\\' && nextchar() >= '0' && nextchar() <= '7') { - paint(2, FLAG_ESCAPE); - if (charat() >= '0' && charat() <= '7') { - paint(1, FLAG_ESCAPE); - if (charat() >= '0' && charat() <= '7') { - paint(1, FLAG_ESCAPE); - } - } - last = -1; - } else if (charat() == '%') { - paint(1, FLAG_ESCAPE); - if (charat() == '%') { - paint(1, FLAG_ESCAPE); - } else { - while (charat() == '-' || charat() == '#' || charat() == '*' || charat() == '0' || charat() == '+') paint(1, FLAG_ESCAPE); - while (isdigit(charat())) paint(1, FLAG_ESCAPE); - if (charat() == '.') { - paint(1, FLAG_ESCAPE); - if (charat() == '*') paint(1, FLAG_ESCAPE); - else while (isdigit(charat())) paint(1, FLAG_ESCAPE); - } - while (charat() == 'l' || charat() == 'z') paint(1, FLAG_ESCAPE); - if (charat() == '\\' || charat() == '"') continue; - paint(1, FLAG_ESCAPE); - } - } else if (charat() == '\\' && nextchar() == 'x') { - paint(2, FLAG_ESCAPE); - while (isxdigit(charat())) paint(1, FLAG_ESCAPE); - } else { - last = charat(); - paint(1, FLAG_STRING); - } - } -} - -/** - * Paint a C character numeral. Can be arbitrarily large, so - * it supports multibyte chars for things like defining weird - * ASCII multibyte integer constants. - */ -void paint_c_char(struct syntax_state * state) { - /* Assumes you came in from a check of charat() == '\'' */ - paint(1, FLAG_NUMERAL); - int last = -1; - while (charat() != -1) { - if (last != '\\' && charat() == '\'') { - paint(1, FLAG_NUMERAL); - return; - } else if (last == '\\' && charat() == '\\') { - paint(1, FLAG_NUMERAL); - last = -1; - } else { - last = charat(); - paint(1, FLAG_NUMERAL); - } - } -} - -/** - * Paint a classic C comment which continues until terminated. - * Assumes you've already painted the starting / and *. - */ -int paint_c_comment(struct syntax_state * state) { - int last = -1; - while (charat() != -1) { - if (common_comment_buzzwords(state)) continue; - else if (last == '*' && charat() == '/') { - paint(1, FLAG_COMMENT); - return 0; - } else { - last = charat(); - paint(1, FLAG_COMMENT); - } - } - return 1; -} - -/** - * Paint a generic C pragma, eg. a #define statement. - */ -int paint_c_pragma(struct syntax_state * state) { - while (state->i < state->line->actual) { - if (charat() == '"') { - /* Paint C string */ - paint_c_string(state); - } else if (charat() == '\'') { - paint_c_char(state); - } else if (charat() == '\\' && state->i == state->line->actual - 1) { - paint(1, FLAG_PRAGMA); - return 2; - } else if (find_keywords(state, syn_c_keywords, FLAG_KEYWORD, c_keyword_qualifier)) { - continue; - } else if (find_keywords(state, syn_c_types, FLAG_TYPE, c_keyword_qualifier)) { - continue; - } else if (charat() == '/' && nextchar() == '/') { - /* C++-style comments */ - paint_comment(state); - return -1; - } else if (charat() == '/' && nextchar() == '*') { - /* C-style comments */ - if (paint_c_comment(state) == 1) return 3; - continue; - } else { - paint(1, FLAG_PRAGMA); - } - } - return 0; -} - -/** - * Paint integers and floating point values with some handling for suffixes. - */ -int paint_c_numeral(struct syntax_state * state) { - if (charat() == '0' && (nextchar() == 'x' || nextchar() == 'X')) { - paint(2, FLAG_NUMERAL); - while (isxdigit(charat())) paint(1, FLAG_NUMERAL); - } else if (charat() == '0' && nextchar() == '.') { - paint(2, FLAG_NUMERAL); - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - if (charat() == 'f') paint(1, FLAG_NUMERAL); - return 0; - } else if (charat() == '0') { - paint(1, FLAG_NUMERAL); - while (charat() >= '0' && charat() <= '7') paint(1, FLAG_NUMERAL); - } else { - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - if (charat() == '.') { - paint(1, FLAG_NUMERAL); - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - if (charat() == 'f') paint(1, FLAG_NUMERAL); - return 0; - } - } - while (charat() == 'u' || charat() == 'U' || charat() == 'l' || charat() == 'L') paint(1, FLAG_NUMERAL); - return 0; -} - -int syn_c_calculate(struct syntax_state * state) { - switch (state->state) { - case -1: - case 0: - if (charat() == '#') { - /* Must be first thing on line, but can have spaces before */ - for (int i = 0; i < state->i; ++i) { - if (state->line->text[i].codepoint != ' ' && state->line->text[i].codepoint != '\t') { - skip(); - return 0; - } - } - /* Handle preprocessor functions */ - paint(1, FLAG_PRAGMA); - while (charat() == ' ') paint(1, FLAG_PRAGMA); - if (match_and_paint(state, "include", FLAG_PRAGMA, c_keyword_qualifier)) { - /* Put quotes around */ - while (charat() == ' ') paint(1, FLAG_PRAGMA); - if (charat() == '<') { - paint(1, FLAG_STRING); - while (charat() != '>' && state->i < state->line->actual) { - paint(1, FLAG_STRING); - } - if (charat() != -1) { - paint(1, FLAG_STRING); - } - } - /* (for "includes", normal pragma highlighting covers that. */ - } else if (match_and_paint(state, "if", FLAG_PRAGMA, c_keyword_qualifier)) { - /* These are to prevent #if and #else from being highlighted as keywords */ - if (charat() == ' ' && nextchar() == '0' && charrel(2) == -1) { - state->i -= 4; - while (charat() != -1) paint(1, FLAG_COMMENT); - return 4; - } - } else if (match_and_paint(state, "else", FLAG_PRAGMA, c_keyword_qualifier)) { - /* ... */ - } - return paint_c_pragma(state); - } else if (charat() == '/' && nextchar() == '/') { - /* C++-style comments */ - paint_comment(state); - } else if (charat() == '/' && nextchar() == '*') { - /* C-style comments */ - if (paint_c_comment(state) == 1) return 1; - return 0; - } else if (find_keywords(state, syn_c_keywords, FLAG_KEYWORD, c_keyword_qualifier)) { - return 0; - } else if (find_keywords(state, syn_c_types, FLAG_TYPE, c_keyword_qualifier)) { - return 0; - } else if (find_keywords(state, syn_c_special, FLAG_NUMERAL, c_keyword_qualifier)) { - return 0; - } else if (charat() == '\"') { - paint_c_string(state); - return 0; - } else if (charat() == '\'') { - paint_c_char(state); - return 0; - } else if (!c_keyword_qualifier(lastchar()) && isdigit(charat())) { - paint_c_numeral(state); - return 0; - } else if (charat() != -1) { - skip(); - return 0; - } - break; - case 1: - /* In a block comment */ - if (paint_c_comment(state) == 1) return 1; - return 0; - case 2: - /* In an unclosed preprocessor statement */ - return paint_c_pragma(state); - case 3: - /* In a block comment within an unclosed preprocessor statement */ - if (paint_c_comment(state) == 1) return 3; - return paint_c_pragma(state); - default: - while (charat() == ' ' || charat() == '\t') paint(1, FLAG_COMMENT); - if (charat() == '#') { - paint(1, FLAG_COMMENT); - while (charat() == ' ' || charat() == '\t') paint(1, FLAG_COMMENT); - if (match_and_paint(state,"if",FLAG_COMMENT, c_keyword_qualifier)) { - while (charat() != -1) paint(1, FLAG_COMMENT); - return state->state + 1; - } else if (match_and_paint(state, "else", FLAG_COMMENT, c_keyword_qualifier) || match_and_paint(state, "elif", FLAG_COMMENT, c_keyword_qualifier)) { - while (charat() != -1) paint(1, FLAG_COMMENT); - return (state->state == 4) ? 0 : (state->state); - } else if (match_and_paint(state, "endif", FLAG_COMMENT, c_keyword_qualifier)) { - while (charat() != -1) paint(1, FLAG_COMMENT); - return (state->state == 4) ? 0 : (state->state - 1); - } else { - while (charat() != -1) paint(1, FLAG_COMMENT); - return (state->state); - } - } else { - while (charat() != -1) paint(1, FLAG_COMMENT); - return state->state; - } - break; - } - return -1; -} - -char * syn_c_ext[] = {".c",".h",".cpp",".hpp",".c++",".h++",".cc",".hh",NULL}; - -BIM_SYNTAX_COMPLETER(c) { - for (char ** keyword = syn_c_keywords; *keyword; ++keyword) { - add_if_match((*keyword),"(c keyword)"); - } - for (char ** keyword = syn_c_types; *keyword; ++keyword) { - add_if_match((*keyword),"(c type)"); - } - return 0; -} - -BIM_SYNTAX_EXT(c, 0, c_keyword_qualifier) -int syn_conf_calculate(struct syntax_state * state) { - if (state->i == 0) { - if (charat() == ';') { - while (charat() != -1) { - if (common_comment_buzzwords(state)) continue; - else paint(1, FLAG_COMMENT); - } - } else if (charat() == '#') { - while (charat() != -1) { - if (common_comment_buzzwords(state)) continue; - else paint(1, FLAG_COMMENT); - } - } else if (charat() == '[') { - paint(1, FLAG_KEYWORD); - while (charat() != ']' && charat() != -1) paint(1, FLAG_KEYWORD); - if (charat() == ']') paint(1, FLAG_KEYWORD); - } else { - while (charat() != '=' && charat() != -1) paint(1, FLAG_TYPE); - } - } - - return -1; -} - -char * syn_conf_ext[] = {".conf",".ini",".git/config",".cfg",".properties",NULL}; - -BIM_SYNTAX(conf, 1) -static char * html_elements[] = { - "a","abbr","address","area","article","aside","audio", - "b","base","bdi","bdo","blockquote","body","br","button", - "canvas","cite","code","col","colgroup","data","datalist", - "dd","del","details","dfn","dialog","div","dl","dt","em", - "embed","fieldset","figcaption","figure","footer","form", - "h1","h2","h3","h4","h5","h6","head","header","hr","html", - "i","iframe","img","input","ins","kbd","label","legend", - "li","link","main","map","mark","meta","meter","nav", - "noscript","object","ol","optgroup","option","output", - "p","param","picture","pre","progress","q","rp","rt", - "ruby","s","samp","script","section","select","small", - "source","span","strong","style","sub","summary","sup", - "svg","table","tbody","td","template","textarea","tfoot", - "th","thead","time","title","tr","track","u","ul","var", - "video","wbr","hgroup","*", - NULL -}; - -static char * css_properties[] = { - "align-content","align-items","align-self","all","animation", - "animation-delay","animation-direction","animation-duration", - "animation-fill-mode","animation-iteration-count","animation-name", - "animation-play-state","animation-timing-function","backface-visibility", - "background","background-attachment","background-blend-mode","background-clip", - "background-color","background-image","background-origin","background-position", - "background-repeat","background-size","border","border-bottom","border-bottom-color", - "border-bottom-left-radius","border-bottom-right-radius","border-bottom-style", - "border-bottom-width","border-collapse","border-color","border-image","border-image-outset", - "border-image-repeat","border-image-slice","border-image-source","border-image-width", - "border-left","border-left-color","border-left-style","border-left-width", - "border-radius","border-right","border-right-color","border-right-style","border-right-width", - "border-spacing","border-style","border-top","border-top-color","border-top-left-radius", - "border-top-right-radius","border-top-style","border-top-width","border-width", - "bottom","box-decoration-break","box-shadow","box-sizing","break-after", - "break-before","break-inside","caption-side","caret-color","@charset", - "clear","clip","color","column-count","column-fill","column-gap","column-rule","column-rule-color", - "column-rule-style","column-rule-width","column-span","column-width","columns","content", - "counter-increment","counter-reset","cursor","direction","display","empty-cells", - "filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink", - "flex-wrap","float","font","@font-face","font-family","font-feature-settings","@font-feature-values", - "font-kerning","font-language-override","font-size","font-size-adjust","font-stretch","font-style", - "font-synthesis","font-variant","font-variant-alternates","font-variant-caps","font-variant-east-asian", - "font-variant-ligatures","font-variant-numeric","font-variant-position","font-weight", - "grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column", - "grid-column-end","grid-column-gap","grid-column-start","grid-gap","grid-row","grid-row-end", - "grid-row-gap","grid-row-start","grid-template","grid-template-areas","grid-template-columns", - "grid-template-rows","hanging-punctuation","height","hyphens","image-rendering","@import", - "isolation","justify-content","@keyframes","left","letter-spacing","line-break","line-height", - "list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom", - "margin-left","margin-right","margin-top","max-height","max-width","@media","min-height", - "min-width","mix-blend-mode","object-fit","object-position","opacity","order","orphans", - "outline","outline-color","outline-offset","outline-style","outline-width","overflow", - "overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right", - "padding-top","page-break-after","page-break-before","page-break-inside","perspective", - "perspective-origin","pointer-events","position","quotes","resize","right","scroll-behavior", - "tab-size","table-layout","text-align","text-align-last","text-combine-upright","text-decoration", - "text-decoration-color","text-decoration-line","text-decoration-style","text-indent","text-justify", - "text-orientation","text-overflow","text-shadow","text-transform","text-underline-position", - "top","transform","transform-origin","transform-style","transition","transition-delay", - "transition-duration","transition-property","transition-timing-function","unicode-bidi", - "user-select","vertical-align","visibility","white-space","widows","width","word-break", - "word-spacing","word-wrap","writing-mode", - NULL -}; - -static char * css_values[] = { - "inline","block","inline-block","none", - "transparent","thin","dotted","sans-serif", - "rgb","rgba","bold","italic","underline","context-box", - "monospace","serif","sans-serif","pre-wrap", - "relative","baseline","hidden","solid","inherit","normal", - "button","pointer","border-box","default","textfield", - "collapse","top","bottom","avoid","table-header-group", - "middle","absolute","rect","left","center","right", - "ellipsis","nowrap","table","both","uppercase","lowercase","help", - "static","table-cell","table-column","scroll","touch","auto", - "not-allowed","inset","url","fixed","translate","alpha","fixed","device-width", - "table-row", - NULL -}; - -static char * css_states[] = { - "focus","active","hover","link","visited","before","after", - "left","right","root","empty","target","enabled","disabled","checked","invalid", - "first-child","nth-child","not","last-child", - NULL -}; - -int css_property_qualifier(int c) { - return isalnum(c) || c == '-' || c == '@' || c == '*' || c == '!'; -} - -int match_prefix(struct syntax_state * state, char * prefix) { - int i = 0; - while (1) { - if (prefix[i] == '\0') return 1; - if (prefix[i] != charrel(i)) return 0; - if (charrel(i) == -1) return 0; - i++; - } -} - -int syn_css_calculate(struct syntax_state * state) { - if (state->state < 1) { - if (charat() == '/' && nextchar() == '*') { - if (paint_c_comment(state) == 1) return 1; - } else if (charat() == '"') { - paint_simple_string(state); - return 0; - } else if (lastchar() != '.' && find_keywords(state,html_elements,FLAG_KEYWORD,css_property_qualifier)) { - return 0; - } else if (lastchar() != '.' && find_keywords(state,css_properties,FLAG_TYPE,css_property_qualifier)) { - return 0; - } else if (match_prefix(state,"-moz-")) { - paint(5,FLAG_ESCAPE); - while (charat() != -1 && css_property_qualifier(charat())) paint(1, FLAG_TYPE); - } else if (match_prefix(state,"-webkit-")) { - paint(8,FLAG_ESCAPE); - while (charat() != -1 && css_property_qualifier(charat())) paint(1, FLAG_TYPE); - } else if (match_prefix(state,"-ms-")) { - paint(4,FLAG_ESCAPE); - while (charat() != -1 && css_property_qualifier(charat())) paint(1, FLAG_TYPE); - } else if (match_prefix(state,"-o-")) { - paint(3,FLAG_ESCAPE); - while (charat() != -1 && css_property_qualifier(charat())) paint(1, FLAG_TYPE); - } else if (charat() == ':') { - skip(); - if (find_keywords(state, css_states, FLAG_PRAGMA, css_property_qualifier)) return 0; - while (charat() != -1 && charat() != ';') { - if (find_keywords(state, css_values, FLAG_NUMERAL, css_property_qualifier)) { - continue; - } else if (charat() == '"') { - paint_simple_string(state); - continue; - } else if (charat() == '{') { - skip(); - return 0; - } else if (charat() == '#') { - paint(1, FLAG_NUMERAL); - while (isxdigit(charat())) paint(1, FLAG_NUMERAL); - } else if (isdigit(charat())) { - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - if (charat() == '.') { - paint(1, FLAG_NUMERAL); - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - } - if (charat() == '%') paint(1, FLAG_NUMERAL); - else if (charat() == 'p' && (nextchar() == 't' || nextchar() == 'x' || nextchar() == 'c')) paint(2, FLAG_NUMERAL); - else if ((charat() == 'e' || charat() == 'c' || charat() == 'm') && nextchar() == 'm') paint(2, FLAG_NUMERAL); - else if (charat() == 'e' && nextchar() == 'x') paint(2, FLAG_NUMERAL); - else if (charat() == 'i' && nextchar() == 'n') paint(2, FLAG_NUMERAL); - else if (charat() == 'v' && (nextchar() == 'w' || nextchar() == 'h')) paint(2, FLAG_NUMERAL); - else if (charat() == 'c' && nextchar() == 'h') paint(2, FLAG_NUMERAL); - else if (charat() == 'r' && nextchar() == 'e' && charrel(2) == 'm') paint(3, FLAG_NUMERAL); - else if (charat() == 'v' && nextchar() == 'm' && ((charrel(2) == 'i' && charrel(3) == 'n') || (charrel(2) == 'a' && charrel(3) == 'x'))) paint(4, FLAG_NUMERAL); - else if (charat() == 's') paint(1, FLAG_NUMERAL); - } else if (match_and_paint(state,"!important",FLAG_PRAGMA,css_property_qualifier)) { - continue; - } else if (charat() != -1) { - skip(); - } - } - return 0; - } else if (charat() == -1) { - return -1; - } else { - skip(); - } - return 0; - } else if (state->state == 1) { - if (paint_c_comment(state) == 1) return 1; - return 0; - } - return -1; -} - -char * syn_css_ext[] = {".css",NULL}; - -BIM_SYNTAX(css, 1) -int syn_ctags_calculate(struct syntax_state * state) { - if (state->i == 0) { - if (charat() == '!') { - paint_comment(state); - return -1; - } else { - while (charat() != -1 && charat() != '\t') paint(1, FLAG_TYPE); - if (charat() == '\t') skip(); - while (charat() != -1 && charat() != '\t') paint(1, FLAG_NUMERAL); - if (charat() == '\t') skip(); - while (charat() != -1 && !(charat() == ';' && nextchar() == '"')) paint(1, FLAG_KEYWORD); - return -1; - } - } - return -1; -} - -char * syn_ctags_ext[] = { "tags", NULL }; - -BIM_SYNTAX(ctags, 0) -int syn_diff_calculate(struct syntax_state * state) { - /* No states to worry about */ - if (state->i == 0) { - int flag = 0; - if (charat() == '+') { - flag = FLAG_DIFFPLUS; - } else if (charat() == '-') { - flag = FLAG_DIFFMINUS; - } else if (charat() == '@') { - flag = FLAG_TYPE; - } else if (charat() != ' ') { - flag = FLAG_KEYWORD; - } else { - return -1; - } - while (charat() != -1) paint(1, flag); - } - return -1; -} - -char * syn_diff_ext[] = {".patch",".diff",NULL}; - -BIM_SYNTAX(diff, 1) -int syn_dirent_calculate(struct syntax_state * state) { - if (state->i == 0) { - if (charat() == '#') { - while (charat() != -1) paint(1, FLAG_COMMENT); - } else if (charat() == 'd') { - paint(1, FLAG_COMMENT); - while (charat() != -1) paint(1, FLAG_KEYWORD); - } else if (charat() == 'f') { - paint(1, FLAG_COMMENT); - return -1; - } - } - return -1; -} - -char * syn_dirent_ext[] = {NULL}; - -BIM_SYNTAX(dirent, 1) -int esh_variable_qualifier(int c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '_'); -} - -int paint_esh_variable(struct syntax_state * state) { - if (charat() == '{') { - paint(1, FLAG_TYPE); - while (charat() != '}' && charat() != -1) paint(1, FLAG_TYPE); - if (charat() == '}') paint(1, FLAG_TYPE); - } else { - if (charat() == '?' || charat() == '$' || charat() == '#') { - paint(1, FLAG_TYPE); - } else { - while (esh_variable_qualifier(charat())) paint(1, FLAG_TYPE); - } - } - return 0; -} - -int paint_esh_string(struct syntax_state * state) { - int last = -1; - while (charat() != -1) { - if (last != '\\' && charat() == '"') { - paint(1, FLAG_STRING); - return 0; - } else if (charat() == '$') { - paint(1, FLAG_TYPE); - paint_esh_variable(state); - last = -1; - } else if (charat() != -1) { - last = charat(); - paint(1, FLAG_STRING); - } - } - return 2; -} - -int paint_esh_single_string(struct syntax_state * state) { - int last = -1; - while (charat() != -1) { - if (last != '\\' && charat() == '\'') { - paint(1, FLAG_STRING); - return 0; - } else if (charat() != -1) { - last = charat(); - paint(1, FLAG_STRING); - } - } - return 1; -} - -int esh_keyword_qualifier(int c) { - return (isalnum(c) || c == '?' || c == '_' || c == '-'); /* technically anything that isn't a space should qualify... */ -} - -char * esh_keywords[] = { - "cd","exit","export","help","history","if","empty?", - "equals?","return","export-cmd","source","exec","not","while", - "then","else","echo", - NULL -}; - -int syn_esh_calculate(struct syntax_state * state) { - if (state->state == 1) { - return paint_esh_single_string(state); - } else if (state->state == 2) { - return paint_esh_string(state); - } - if (charat() == '#') { - while (charat() != -1) { - if (common_comment_buzzwords(state)) continue; - else paint(1, FLAG_COMMENT); - } - return -1; - } else if (charat() == '$') { - paint(1, FLAG_TYPE); - paint_esh_variable(state); - return 0; - } else if (charat() == '\'') { - paint(1, FLAG_STRING); - return paint_esh_single_string(state); - } else if (charat() == '"') { - paint(1, FLAG_STRING); - return paint_esh_string(state); - } else if (match_and_paint(state, "export", FLAG_KEYWORD, esh_keyword_qualifier)) { - while (charat() == ' ') skip(); - while (esh_keyword_qualifier(charat())) paint(1, FLAG_TYPE); - return 0; - } else if (match_and_paint(state, "export-cmd", FLAG_KEYWORD, esh_keyword_qualifier)) { - while (charat() == ' ') skip(); - while (esh_keyword_qualifier(charat())) paint(1, FLAG_TYPE); - return 0; - } else if (find_keywords(state, esh_keywords, FLAG_KEYWORD, esh_keyword_qualifier)) { - return 0; - } else if (isdigit(charat())) { - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - return 0; - } else if (charat() != -1) { - skip(); - return 0; - } - return -1; -} - -/* Only enable esh highlighting by default on ToaruOS */ -char * syn_esh_ext[] = { -#ifdef __toaru__ - ".sh", -#endif - ".eshrc",".yutanirc", - NULL -}; - -BIM_SYNTAX(esh, 0) -int syn_gitcommit_calculate(struct syntax_state * state) { - if (state->i == 0 && charat() == '#') { - while (charat() != -1) paint(1, FLAG_COMMENT); - } else if (state->line_no == 0) { - /* First line is special */ - while (charat() != -1 && state->i < 50) paint(1, FLAG_KEYWORD); - while (charat() != -1) paint(1, FLAG_DIFFMINUS); - } else if (state->line_no == 1) { - /* No text on second line */ - while (charat() != -1) paint(1, FLAG_DIFFMINUS); - } else if (charat() != -1) { - skip(); - return 0; - } - return -1; -} - -char * syn_gitcommit_ext[] = {"COMMIT_EDITMSG", NULL}; - -char * syn_gitrebase_commands[] = { - "p","r","e","s","f","x","d", - "pick","reword","edit","squash","fixup", - "exec","drop", - NULL -}; - -int syn_gitrebase_calculate(struct syntax_state * state) { - if (state->i == 0 && charat() == '#') { - while (charat() != -1) paint(1, FLAG_COMMENT); - } else if (state->i == 0 && find_keywords(state, syn_gitrebase_commands, FLAG_KEYWORD, c_keyword_qualifier)) { - while (charat() == ' ') skip(); - while (isxdigit(charat())) paint(1, FLAG_NUMERAL); - return -1; - } - - return -1; -} - -char * syn_gitrebase_ext[] = {"git-rebase-todo",NULL}; - -BIM_SYNTAX(gitcommit, 1) -BIM_SYNTAX(gitrebase, 1) -static char * groovy_keywords[] = { - "as","assert","break","case", - "catch","class","const","continue", - "def","default","do","else","enum", - "extends","finally","for", - "goto","if","implements","import", - "in","instanceof","interface","new", - "package","return","super", - "switch","throw","throws", - "trait","try","while", - NULL -}; - -static char * groovy_stuff[] = { - "true","false","null","this", - NULL -}; - -static char * groovy_primitives[] = { - "byte","char","short","int","long","BigInteger", - NULL -}; - -static int paint_triple_single(struct syntax_state * state) { - while (charat() != -1) { - if (charat() == '\'') { - paint(1, FLAG_STRING); - if (charat() == '\'' && nextchar() == '\'') { - paint(2, FLAG_STRING); - return 0; - } - } else { - paint(1, FLAG_STRING); - } - } - return 2; /* continues */ -} - -int syn_groovy_calculate(struct syntax_state * state) { - if (state->state <= 0) { - if (state->line_no == 0 && state->i == 0 && charat() == '#') { - paint_comment(state); - return -1; - } else if (charat() == '/' && nextchar() == '/') { - /* C++-style comments */ - paint_comment(state); - return -1; - } else if (charat() == '/' && nextchar() == '*') { - if (paint_c_comment(state) == 1) return 1; - } else if (charat() == '"') { - paint_simple_string(state); - return 0; - } else if (charat() == '\'') { - paint_single_string(state); - return 0; - } else if (find_keywords(state, groovy_keywords, FLAG_KEYWORD, c_keyword_qualifier)) { - return 0; - } else if (find_keywords(state, groovy_primitives, FLAG_TYPE, c_keyword_qualifier)) { - return 0; - } else if (find_keywords(state, groovy_stuff, FLAG_NUMERAL, c_keyword_qualifier)) { - return 0; - } else if (!c_keyword_qualifier(lastchar()) && isdigit(charat())) { - paint_c_numeral(state); - return 0; - } else if (charat() != -1) { - skip(); - return 0; - } - return -1; - } else if (state->state == 1) { - if (paint_c_comment(state) == 1) return 1; - return 0; - } else if (state->state == 2) { - return paint_triple_single(state); - } - return -1; -} - -char * syn_groovy_ext[] = {".groovy",".jenkinsfile",".gradle",NULL}; - -BIM_SYNTAX(groovy, 1) -int syn_hosts_calculate(struct syntax_state * state) { - if (state->i == 0) { - if (charat() == '#') { - while (charat() != -1) { - if (common_comment_buzzwords(state)) continue; - else paint(1, FLAG_COMMENT); - } - } else { - while (charat() != '\t' && charat() != ' ' && charat() != -1) paint(1, FLAG_NUMERAL); - while (charat() != -1) paint(1, FLAG_TYPE); - } - } - - return -1; -} - -char * syn_hosts_ext[] = {"hosts",NULL}; - -BIM_SYNTAX(hosts, 1) - -static char * syn_java_keywords[] = { - "assert","break","case","catch","class","continue", - "default","do","else","enum","exports","extends","finally", - "for","if","implements","instanceof","interface","module","native", - "new","requires","return","throws", - "strictfp","super","switch","synchronized","this","throw","try","while", - NULL -}; - -static char * syn_java_types[] = { - "var","boolean","void","short","long","int","double","float","enum","char", - "private","protected","public","static","final","transient","volatile","abstract", - NULL -}; - -static char * syn_java_special[] = { - "true","false","import","package","null", - NULL -}; - -static char * syn_java_at_comments[] = { - "@author","@see","@since","@return","@throws", - "@version","@exception","@deprecated", - /* @param is special */ - NULL -}; - -static int java_at_keyword_qualifier(int c) { - return isalnum(c) || (c == '_') || (c == '@'); -} - -static char * syn_java_brace_comments[] = { - "{@docRoot","{@inheritDoc","{@link","{@linkplain", - "{@value","{@code","{@literal","{@serial", - "{@serialData","{@serialField", - NULL -}; - -static int java_brace_keyword_qualifier(int c) { - return isalnum(c) || (c == '{') || (c == '@') || (c == '_'); -} - -static int paint_java_comment(struct syntax_state * state) { - int last = -1; - while (charat() != -1) { - if (common_comment_buzzwords(state)) continue; - else if (charat() == '@') { - if (!find_keywords(state, syn_java_at_comments, FLAG_ESCAPE, java_at_keyword_qualifier)) { - if (match_and_paint(state, "@param", FLAG_ESCAPE, java_at_keyword_qualifier)) { - while (charat() == ' ') skip(); - while (c_keyword_qualifier(charat())) paint(1, FLAG_TYPE); - } else { - /* Paint the @ */ - paint(1, FLAG_COMMENT); - } - } - } else if (charat() == '{') { - /* see if this terminates */ - if (find_keywords(state, syn_java_brace_comments, FLAG_ESCAPE, java_brace_keyword_qualifier)) { - while (charat() != '}' && charat() != -1) { - paint(1, FLAG_ESCAPE); - } - if (charat() == '}') paint(1, FLAG_ESCAPE); - } else { - paint(1, FLAG_COMMENT); - } - } else if (charat() == '<') { - int is_tag = 0; - for (int i = 1; charrel(i) != -1; ++i) { - if (charrel(i) == '>') { - is_tag = 1; - break; - } - if (!isalnum(charrel(i)) && charrel(i) != '/') { - is_tag = 0; - break; - } - } - if (is_tag) { - paint(1, FLAG_TYPE); - while (charat() != -1 && charat() != '>') { - if (charat() == '/') paint(1, FLAG_TYPE); - else paint(1, FLAG_KEYWORD); - } - if (charat() == '>') paint(1, FLAG_TYPE); - } else { - /* Paint the < */ - paint(1, FLAG_COMMENT); - } - } else if (last == '*' && charat() == '/') { - paint(1, FLAG_COMMENT); - return 0; - } else { - last = charat(); - paint(1, FLAG_COMMENT); - } - } - return 1; -} - -int syn_java_calculate(struct syntax_state * state) { - switch (state->state) { - case -1: - case 0: - if (!c_keyword_qualifier(lastchar()) && isdigit(charat())) { - paint_c_numeral(state); - return 0; - } else if (charat() == '/' && nextchar() == '/') { - /* C++-style comments */ - paint_comment(state); - } else if (charat() == '/' && nextchar() == '*') { - /* C-style comments; TODO: Needs special stuff for @author; ; etc. */ - if (paint_java_comment(state) == 1) return 1; - } else if (find_keywords(state, syn_java_keywords, FLAG_KEYWORD, c_keyword_qualifier)) { - return 0; - } else if (find_keywords(state, syn_java_types, FLAG_TYPE, c_keyword_qualifier)) { - return 0; - } else if (find_keywords(state, syn_java_special, FLAG_NUMERAL, c_keyword_qualifier)) { - return 0; - } else if (charat() == '\"') { - paint_simple_string(state); - return 0; - } else if (charat() == '\'') { - paint_c_char(state); - return 0; - } else if (charat() == '@') { - paint(1, FLAG_PRAGMA); - while (c_keyword_qualifier(charat())) paint(1, FLAG_PRAGMA); - return 0; - } else if (charat() != -1) { - skip(); - return 0; - } - break; - case 1: - if (paint_java_comment(state) == 1) return 1; - return 0; - } - return -1; -} - -char * syn_java_ext[] = {".java",NULL}; - -BIM_SYNTAX_COMPLETER(java) { - for (char ** keyword = syn_java_keywords; *keyword; ++keyword) { - add_if_match((*keyword),"(java keyword)"); - } - for (char ** keyword = syn_java_types; *keyword; ++keyword) { - add_if_match((*keyword),"(java type)"); - } - - /* XXX Massive hack */ - if (env->col_no > 1 && env->lines[env->line_no-1]->text[env->col_no-2].flags == FLAG_COMMENT) { - if (comp[0] == '@') { - for (char ** keyword = syn_java_at_comments; *keyword; ++keyword) { - add_if_match((*keyword),"(javadoc annotation)"); - } - } else if (comp[0] == '{') { - for (char ** keyword = syn_java_brace_comments; *keyword; ++keyword) { - add_if_match((*keyword),"(javadoc annotation)"); - } - } - } - - return 0; -} - -BIM_SYNTAX_EXT(java, 1, java_brace_keyword_qualifier) -char * syn_json_keywords[] = { - "true","false","null", - NULL -}; - -int syn_json_calculate(struct syntax_state * state) { - while (charat() != -1) { - if (charat() == '"') { - int backtrack = state->i; - paint_simple_string(state); - int backtrack_end = state->i; - while (charat() == ' ') skip(); - if (charat() == ':') { - /* This is dumb. */ - state->i = backtrack; - paint(1, FLAG_ESCAPE); - while (state->i < backtrack_end-1) { - paint(1, FLAG_KEYWORD); - } - if (charat() == '"') { - paint(1, FLAG_ESCAPE); - } - } - return 0; - } else if (charat() == '-' || isdigit(charat())) { - if (charat() == '-') paint(1, FLAG_NUMERAL); - if (charat() == '0') { - paint(1, FLAG_NUMERAL); - } else { - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - } - if (charat() == '.') { - paint(1, FLAG_NUMERAL); - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - } - if (charat() == 'e' || charat() == 'E') { - paint(1, FLAG_NUMERAL); - if (charat() == '+' || charat() == '-') { - paint(1, FLAG_NUMERAL); - } - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - } - } else if (find_keywords(state,syn_json_keywords,FLAG_NUMERAL,c_keyword_qualifier)) { - /* ... */ - } else { - skip(); - return 0; - } - } - return -1; -} - -char * syn_json_ext[] = {".json",NULL}; // TODO other stuff that uses json - -BIM_SYNTAX(json, 1) -static char * syn_kotlin_keywords[] = { - "as","as?","break","class","continue","do","else","false","for", - "fun","if","in","!in","interface","is","!is","null","object", - "package","return","super","this","throw","true","try","typealias", - "typeof","val","var","when","while", - "by","catch","constructor","delegate","dynamic","field","file", - "finally","get","import","init","param","property","receiver", - "set","setparam","where", - "actual","abstract","annotation","companion","const", - "crossinline","data","enum","expect","external","final", - "infix","inner","internal","lateinit","noinline","open", - "operator","out","override","private","protected","public", - "reified","sealed","suspend","tailrec","vararg", - "field","it","inline", - NULL -}; - -static char * syn_kotlin_types[] = { - "Byte","Short","Int","Long", - "Float","Double", - NULL -}; - -static char * syn_kotlin_at_comments[] = { - "@author","@see","@since","@return","@throws", - "@version","@exception","@deprecated", - /* @param is special */ - NULL -}; - -static int kt_at_keyword_qualifier(int c) { - return isalnum(c) || (c == '_') || (c == '@'); -} - -static char * syn_kotlin_brace_comments[] = { - "{@docRoot","{@inheritDoc","{@link","{@linkplain", - "{@value","{@code","{@literal","{@serial", - "{@serialData","{@serialField", - NULL -}; - -static int kotlin_keyword_qualifier(int c) { - return isalnum(c) || (c == '?') || (c == '!') || (c == '_'); -} - -static int kt_brace_keyword_qualifier(int c) { - return isalnum(c) || (c == '{') || (c == '@') || (c == '_'); -} - -static int paint_kotlin_comment(struct syntax_state * state) { - int last = -1; - while (charat() != -1) { - if (common_comment_buzzwords(state)) continue; - else if (charat() == '@') { - if (!find_keywords(state, syn_kotlin_at_comments, FLAG_ESCAPE, kt_at_keyword_qualifier)) { - if (match_and_paint(state, "@param", FLAG_ESCAPE, kt_at_keyword_qualifier)) { - while (charat() == ' ') skip(); - while (c_keyword_qualifier(charat())) paint(1, FLAG_TYPE); - } else { - /* Paint the @ */ - paint(1, FLAG_COMMENT); - } - } - } else if (charat() == '{') { - /* see if this terminates */ - if (find_keywords(state, syn_kotlin_brace_comments, FLAG_ESCAPE, kt_brace_keyword_qualifier)) { - while (charat() != '}' && charat() != -1) { - paint(1, FLAG_ESCAPE); - } - if (charat() == '}') paint(1, FLAG_ESCAPE); - } else { - paint(1, FLAG_COMMENT); - } - } else if (charat() == '<') { - int is_tag = 0; - for (int i = 1; charrel(i) != -1; ++i) { - if (charrel(i) == '>') { - is_tag = 1; - break; - } - if (!isalnum(charrel(i)) && charrel(i) != '/') { - is_tag = 0; - break; - } - } - if (is_tag) { - paint(1, FLAG_TYPE); - while (charat() != -1 && charat() != '>') { - if (charat() == '/') paint(1, FLAG_TYPE); - else paint(1, FLAG_KEYWORD); - } - if (charat() == '>') paint(1, FLAG_TYPE); - } else { - /* Paint the < */ - paint(1, FLAG_COMMENT); - } - } else if (last == '*' && charat() == '/') { - paint(1, FLAG_COMMENT); - return 0; - } else { - last = charat(); - paint(1, FLAG_COMMENT); - } - } - return 1; -} - -int syn_kotlin_calculate(struct syntax_state * state) { - switch (state->state) { - case -1: - case 0: - if (!c_keyword_qualifier(lastchar()) && isdigit(charat())) { - paint_c_numeral(state); - return 0; - } else if (charat() == '/' && nextchar() == '/') { - /* C++-style comments */ - paint_comment(state); - } else if (charat() == '/' && nextchar() == '*') { - /* C-style comments; TODO: Needs special stuff for @author; ; etc. */ - if (paint_kotlin_comment(state) == 1) return 1; - } else if (find_keywords(state, syn_kotlin_keywords, FLAG_KEYWORD, kotlin_keyword_qualifier)) { - return 0; - } else if (find_keywords(state, syn_kotlin_types, FLAG_TYPE, c_keyword_qualifier)) { - return 0; - } else if (charat() == '\"') { - paint_simple_string(state); - return 0; - } else if (charat() == '\'') { - paint_c_char(state); - return 0; - } else if (charat() == '@') { - paint(1, FLAG_PRAGMA); - while (c_keyword_qualifier(charat())) paint(1, FLAG_PRAGMA); - return 0; - } else if (charat() != -1) { - skip(); - return 0; - } - break; - case 1: - if (paint_kotlin_comment(state) == 1) return 1; - return 0; - } - return -1; -} - -char * syn_kotlin_ext[] = {".kt",NULL}; - -BIM_SYNTAX_COMPLETER(kotlin) { - for (char ** keyword = syn_kotlin_keywords; *keyword; ++keyword) { - add_if_match((*keyword),"(kotlin keyword)"); - } - for (char ** keyword = syn_kotlin_types; *keyword; ++keyword) { - add_if_match((*keyword),"(kotlin type)"); - } - - /* XXX Massive hack */ - if (env->col_no > 1 && env->lines[env->line_no-1]->text[env->col_no-2].flags == FLAG_COMMENT) { - if (comp[0] == '@') { - for (char ** keyword = syn_kotlin_at_comments; *keyword; ++keyword) { - add_if_match((*keyword),"(javadoc annotation)"); - } - } else if (comp[0] == '{') { - for (char ** keyword = syn_kotlin_brace_comments; *keyword; ++keyword) { - add_if_match((*keyword),"(javadoc annotation)"); - } - } - } - - return 0; -} - -BIM_SYNTAX_EXT(kotlin, 1, kt_brace_keyword_qualifier) - -int lisp_paren_flags[] = { - FLAG_DIFFPLUS, FLAG_TYPE, FLAG_PRAGMA, FLAG_KEYWORD, -}; - -int lisp_paren_flag_from_state(int i) { - return lisp_paren_flags[i % (sizeof(lisp_paren_flags) / sizeof(*lisp_paren_flags))]; -} - -int syn_lisp_calculate(struct syntax_state * state) { - - if (state->state == -1) state->state = 0; - - while (charat() != -1) { - if (charat() == ';') { - while (charat() != -1) paint(1, FLAG_COMMENT); - } else if (charat() == '(') { - paint(1, lisp_paren_flag_from_state(state->state)); - state->state++; - - while (charat() != ' ' && charat() != -1 && charat() != '(' && charat() != ')') { - paint(1, FLAG_KEYWORD); - } - } else if (charat() == ')') { - if (state->state == 0) { - paint(1, FLAG_ERROR); - } else { - state->state--; - paint(1, lisp_paren_flag_from_state(state->state)); - } - } else if (charat() == ':') { - while (charat() != ' ' && charat() != -1 && charat() != '(' && charat() != ')') { - paint(1, FLAG_PRAGMA); - } - } else if (charat() == '"') { - paint_simple_string(state); - } else if (charat() != -1) { - skip(); - } - } - - if (state->state == 0) return -1; - if (charat() == -1) return state->state; - return -1; /* Not sure what happened */ -} - -char * syn_lisp_ext[] = { - ".lisp",".lsp",".cl", - NULL -}; - -BIM_SYNTAX(lisp, 0) -int make_command_qualifier(int c) { - return isalnum(c) || c == '_' || c == '-' || c == '.'; -} - -char * syn_make_commands[] = { - "define","endef","undefine","ifdef","ifndef","ifeq","ifneq","else","endif", - "include","sinclude","override","export","unexport","private","vpath", - "-include", - NULL -}; - -char * syn_make_functions[] = { - "subst","patsubst","findstring","filter","filter-out", - "sort","word","words","wordlist","firstword","lastword", - "dir","notdir","suffix","basename","addsuffix","addprefix", - "join","wildcard","realpath","abspath","error","warning", - "shell","origin","flavor","foreach","if","or","and", - "call","eval","file","value", - NULL -}; - -char * syn_make_special_targets[] = { - "all", /* Not really special, but highlight it 'cause I feel like it. */ - ".PHONY", ".SUFFIXES", ".DEFAULT", ".PRECIOUS", ".INTERMEDIATE", - ".SECONDARY", ".SECONDEXPANSION", ".DELETE_ON_ERROR", ".IGNORE", - ".LOW_RESOLUTION_TIME", ".SILENT", ".EXPORT_ALL_VARIABLES", - ".NOTPARALLEL", ".ONESHELL", ".POSIX", - NULL -}; - -int make_close_paren(struct syntax_state * state) { - paint(2, FLAG_TYPE); - find_keywords(state, syn_make_functions, FLAG_KEYWORD, c_keyword_qualifier); - int i = 1; - while (charat() != -1) { - if (charat() == '(') { - i++; - } else if (charat() == ')') { - i--; - if (i == 0) { - paint(1,FLAG_TYPE); - return 0; - } - } else if (charat() == '"') { - paint_simple_string(state); - } - paint(1,FLAG_TYPE); - } - return 0; -} - -int make_close_brace(struct syntax_state * state) { - paint(2, FLAG_TYPE); - while (charat() != -1) { - if (charat() == '}') { - paint(1, FLAG_TYPE); - return 0; - } - paint(1, FLAG_TYPE); - } - return 0; -} - -int make_variable_or_comment(struct syntax_state * state, int flag) { - while (charat() != -1) { - if (charat() == '$') { - switch (nextchar()) { - case '(': - make_close_paren(state); - break; - case '{': - make_close_brace(state); - break; - default: - paint(2, FLAG_TYPE); - break; - } - } else if (charat() == '#') { - while (charat() != -1) paint(1, FLAG_COMMENT); - } else { - paint(1, flag); - } - } - return 0; -} - -int syn_make_calculate(struct syntax_state * state) { - if (state->i == 0 && charat() == '\t') { - make_variable_or_comment(state, FLAG_NUMERAL); - } else { - while (charat() == ' ') { skip(); } - /* Peek forward to see if this is a rule or a variable */ - int whatisit = 0; - for (int i = 0; charrel(i) != -1; ++i) { - if (charrel(i) == ':' && charrel(i+1) != '=') { - whatisit = 1; - break; - } else if (charrel(i) == '=') { - whatisit = 2; - break; - } else if (charrel(i) == '#') { - break; - } - } - if (!whatisit) { - /* Check for functions */ - while (charat() != -1) { - if (charat() == '#') { - while (charat() != -1) { - if (common_comment_buzzwords(state)) continue; - else paint(1, FLAG_COMMENT); - } - } else if (find_keywords(state, syn_make_commands, FLAG_KEYWORD, make_command_qualifier)) { - continue; - } else if (charat() == '$') { - make_variable_or_comment(state, FLAG_NONE); - } else { - skip(); - } - } - } else if (whatisit == 1) { - /* It's a rule */ - while (charat() != -1) { - if (charat() == '#') { - while (charat() != -1) { - if (common_comment_buzzwords(state)) continue; - else paint(1, FLAG_COMMENT); - } - } else if (charat() == ':') { - paint(1, FLAG_TYPE); - make_variable_or_comment(state, FLAG_NONE); - } else if (find_keywords(state, syn_make_special_targets, FLAG_KEYWORD, make_command_qualifier)) { - continue; - } else { - paint(1, FLAG_TYPE); - } - } - } else if (whatisit == 2) { - /* It's a variable definition */ - match_and_paint(state, "export", FLAG_KEYWORD, c_keyword_qualifier); - while (charat() != -1 && charat() != '+' && charat() != '=' && charat() != ':' && charat() != '?') { - paint(1, FLAG_TYPE); - } - while (charat() != -1 && charat() != '=') skip(); - /* Highlight variable expansions */ - make_variable_or_comment(state, FLAG_NONE); - } - } - return -1; -} - -char * syn_make_ext[] = {"Makefile","makefile","GNUmakefile",".mak",NULL}; - -BIM_SYNTAX(make, 0) -/** - * NROFF highlighter - */ - -int syn_man_calculate(struct syntax_state * state) { - while (charat() != -1) { - if (state->i == 0 && charat() == '.') { - if (nextchar() == 'S' && charrel(2) == 'H' && charrel(3) == ' ') { - paint(3, FLAG_KEYWORD); - while (charat() != -1) paint(1, FLAG_STRING); - } else if (nextchar() == 'B' && charrel(2) == ' ') { - paint(2, FLAG_KEYWORD); - while (charat() != -1) paint(1, FLAG_BOLD); - } else if (isalpha(nextchar())) { - paint(1, FLAG_KEYWORD); - while (charat() != -1 && isalpha(charat())) paint(1, FLAG_KEYWORD); - } else if (nextchar() == '\\' && charrel(2) == '"') { - while (charat() != -1) paint(1, FLAG_COMMENT); - } else { - skip(); - } - } else if (charat() == '\\') { - if (nextchar() == 'f') { - paint(2, FLAG_NUMERAL); - paint(1, FLAG_PRAGMA); - } else { - paint(2, FLAG_ESCAPE); - } - } else { - skip(); - } - } - return -1; -} - -/* Yes this is dumb. No, I don't care. */ -char * syn_man_ext[] = {".1",".2",".3",".4",".5",".6",".7",".8",NULL}; - -BIM_SYNTAX(man, 0) -static struct syntax_definition * syn_c = NULL; -static struct syntax_definition * syn_py = NULL; -static struct syntax_definition * syn_java = NULL; -static struct syntax_definition * syn_json = NULL; -static struct syntax_definition * syn_xml = NULL; -static struct syntax_definition * syn_make = NULL; -static struct syntax_definition * syn_diff = NULL; -static struct syntax_definition * syn_rust = NULL; -static struct syntax_definition * syn_bash = NULL; - -static int _initialized = 0; - -int syn_markdown_calculate(struct syntax_state * state) { - if (!_initialized) { - _initialized = 1; - syn_c = find_syntax_calculator("c"); - syn_py = find_syntax_calculator("py"); - syn_java = find_syntax_calculator("java"); - syn_json = find_syntax_calculator("json"); - syn_xml = find_syntax_calculator("xml"); - syn_make = find_syntax_calculator("make"); - syn_diff = find_syntax_calculator("diff"); - syn_rust = find_syntax_calculator("rust"); - syn_bash = find_syntax_calculator("bash"); - } - if (state->state < 1) { - while (charat() != -1) { - if (state->i == 0 && charat() == '#') { - while (charat() == '#') paint(1, FLAG_KEYWORD); - while (charat() != -1) paint(1, FLAG_BOLD); - return -1; - } else if (state->i == 0) { - while (charat() == ' ') skip(); - if (charat() == '`' && nextchar() == '`' && charrel(2) == '`') { - paint(3, FLAG_STRING); - if (syn_c &&match_forward(state, "c")) { - nest(syn_c->calculate, 100); - } else if (syn_c && match_forward(state,"c++")) { - nest(syn_c->calculate, 100); - } else if (syn_py && (match_forward(state,"py") || match_forward(state,"python"))) { - nest(syn_py->calculate, 200); - } else if (syn_java && match_forward(state, "java")) { - nest(syn_java->calculate, 300); - } else if (syn_json && match_forward(state,"json")) { - nest(syn_json->calculate, 400); - } else if (syn_xml && match_forward(state,"xml")) { - nest(syn_xml->calculate, 500); - } else if (syn_xml && match_forward(state,"html")) { - nest(syn_xml->calculate, 500); // TODO this will be a different highlighter later - } else if (syn_make && match_forward(state,"make")) { - nest(syn_make->calculate, 600); - } else if (syn_diff && match_forward(state, "diff")) { - nest(syn_diff->calculate, 700); - } else if (syn_bash && match_forward(state, "bash")) { - nest(syn_bash->calculate, 800); - } else if (syn_rust && match_forward(state, "rust")) { - nest(syn_rust->calculate, 900); /* Keep this at the end for now */ - } - return 1; - } - } - if (charat() == '`') { - paint(1, FLAG_STRING); - while (charat() != -1) { - if (charat() == '`') { - paint(1, FLAG_STRING); - return 0; - } - paint(1, FLAG_STRING); - } - } else if (charat() == '[') { - skip(); - while (charat() != -1 && charat() != ']') { - paint(1, FLAG_LINK); - } - if (charat() == ']') skip(); - if (charat() == '(') { - skip(); - while (charat() != -1 && charat() != ')') { - paint(1, FLAG_NUMERAL); - } - } - } else { - skip(); - return 0; - } - } - return -1; - } else if (state->state >= 1) { - /* Continuing generic triple-` */ - if (state->i == 0) { - /* Go backwards until we find the source ``` */ - int count = 0; - for (int i = state->line_no; i > 0; i--) { - if (env->lines[i]->istate < 1) { - while (env->lines[i]->text[count].codepoint == ' ') { - if (charrel(count) != ' ') goto _nope; - count++; - } - break; - } - } - if (charrel(count) == '`' && charrel(count+1) == '`' && charrel(count+2) == '`' && charrel(count+3) == -1) { - paint(count+3,FLAG_STRING); - return -1; - } - } -_nope: - if (state->state == 1) { - while (charat() != -1) paint(1, FLAG_STRING); - return 1; - } else if (state->state < 199) { - nest(syn_c->calculate, 100); - } else if (state->state < 299) { - nest(syn_py->calculate, 200); - } else if (state->state < 399) { - nest(syn_java->calculate, 300); - } else if (state->state < 499) { - nest(syn_json->calculate, 400); - } else if (state->state < 599) { - nest(syn_xml->calculate, 500); - } else if (state->state < 699) { - nest(syn_make->calculate, 600); - } else if (state->state < 799) { - nest(syn_diff->calculate, 700); - } else if (state->state < 899) { - nest(syn_bash->calculate, 800); - } else { - nest(syn_rust->calculate, 900); - } - } - return -1; -} - -char * syn_markdown_ext[] = {".md",".markdown",NULL}; - -BIM_SYNTAX(markdown, 1) -char * syn_proto_keywords[] = { - "syntax","import","option","package","message","group","oneof", - "optional","required","repeated","default","extend","extensions","to","max","reserved", - "service","rpc","returns","stream", - NULL -}; - -char * syn_proto_types[] = { - "int32","int64","uint32","uint64","sint32","sint64", - "fixed32","fixed64","sfixed32","sfixed64", - "float","double","bool","string","bytes", - "enum", - NULL -}; - -char * syn_proto_special[] = { - "true","false", - NULL -}; - -int syn_proto_calculate(struct syntax_state * state) { - if (state->state < 1) { - if (charat() == '/' && nextchar() == '/') { - paint_comment(state); - } else if (charat() == '/' && nextchar() == '*') { - if (paint_c_comment(state) == 1) return 1; - return 0; - } else if (find_keywords(state, syn_proto_keywords, FLAG_KEYWORD, c_keyword_qualifier)) { - return 0; - } else if (find_keywords(state, syn_proto_types, FLAG_TYPE, c_keyword_qualifier)) { - return 0; - } else if (find_keywords(state, syn_proto_special, FLAG_NUMERAL, c_keyword_qualifier)) { - return 0; - } else if (charat() == '"') { - paint_simple_string(state); - return 0; - } else if (!c_keyword_qualifier(lastchar()) && isdigit(charat())) { - paint_c_numeral(state); - return 0; - } else if (charat() != -1) { - skip(); - return 0; - } - return -1; - } else { - if (paint_c_comment(state) == 1) return 1; - return 0; - } -} - -char * syn_proto_ext[] = {".proto",NULL}; - -BIM_SYNTAX(proto, 1) -char * syn_py_keywords[] = { - "class","def","return","del","if","else","elif","for","while","continue", - "break","assert","as","and","or","except","finally","from","global", - "import","in","is","lambda","with","nonlocal","not","pass","raise","try","yield", - NULL -}; - -char * syn_py_types[] = { - /* built-in functions */ - "abs","all","any","ascii","bin","bool","breakpoint","bytes", - "bytearray","callable","compile","complex","delattr","chr", - "dict","dir","divmod","enumerate","eval","exec","filter","float", - "format","frozenset","getattr","globals","hasattr","hash","help", - "hex","id","input","int","isinstance","issubclass","iter","len", - "list","locals","map","max","memoryview","min","next","object", - "oct","open","ord","pow","print","property","range","repr","reverse", - "round","set","setattr","slice","sorted","staticmethod","str","sum", - "super","tuple","type","vars","zip", - NULL -}; - -char * syn_py_special[] = { - "True","False","None", - NULL -}; - -int paint_py_triple_double(struct syntax_state * state) { - while (charat() != -1) { - if (charat() == '"') { - paint(1, FLAG_STRING); - if (charat() == '"' && nextchar() == '"') { - paint(2, FLAG_STRING); - return 0; - } - } else { - paint(1, FLAG_STRING); - } - } - return 1; /* continues */ -} - -int paint_py_triple_single(struct syntax_state * state) { - while (charat() != -1) { - if (charat() == '\'') { - paint(1, FLAG_STRING); - if (charat() == '\'' && nextchar() == '\'') { - paint(2, FLAG_STRING); - return 0; - } - } else { - paint(1, FLAG_STRING); - } - } - return 2; /* continues */ -} - -int paint_py_single_string(struct syntax_state * state) { - paint(1, FLAG_STRING); - while (charat() != -1) { - if (charat() == '\\' && nextchar() == '\'') { - paint(2, FLAG_ESCAPE); - } else if (charat() == '\'') { - paint(1, FLAG_STRING); - return 0; - } else if (charat() == '\\') { - paint(2, FLAG_ESCAPE); - } else { - paint(1, FLAG_STRING); - } - } - return 0; -} - -int paint_py_numeral(struct syntax_state * state) { - if (charat() == '0' && (nextchar() == 'x' || nextchar() == 'X')) { - paint(2, FLAG_NUMERAL); - while (isxdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL); - } else if (charat() == '0' && nextchar() == '.') { - paint(2, FLAG_NUMERAL); - while (isdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL); - if ((charat() == '+' || charat() == '-') && (nextchar() == 'e' || nextchar() == 'E')) { - paint(2, FLAG_NUMERAL); - while (isdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL); - } else if (charat() == 'e' || charat() == 'E') { - paint(1, FLAG_NUMERAL); - while (isdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL); - } - if (charat() == 'j') paint(1, FLAG_NUMERAL); - return 0; - } else { - while (isdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL); - if (charat() == '.') { - paint(1, FLAG_NUMERAL); - while (isdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL); - if ((charat() == '+' || charat() == '-') && (nextchar() == 'e' || nextchar() == 'E')) { - paint(2, FLAG_NUMERAL); - while (isdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL); - } else if (charat() == 'e' || charat() == 'E') { - paint(1, FLAG_NUMERAL); - while (isdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL); - } - if (charat() == 'j') paint(1, FLAG_NUMERAL); - return 0; - } - if (charat() == 'j') paint(1, FLAG_NUMERAL); - } - while (charat() == 'l' || charat() == 'L') paint(1, FLAG_NUMERAL); - return 0; -} - -void paint_py_format_string(struct syntax_state * state, char type) { - paint(1, FLAG_STRING); - while (charat() != -1) { - if (charat() == '\\' && nextchar() == type) { - paint(2, FLAG_ESCAPE); - } else if (charat() == type) { - paint(1, FLAG_STRING); - return; - } else if (charat() == '\\') { - paint(2, FLAG_ESCAPE); - } else if (charat() == '{') { - paint(1, FLAG_NUMERAL); - if (charat() == '}') { - state->i--; - paint(2, FLAG_ERROR); /* Can't do that. */ - } else { - while (charat() != -1 && charat() != '}') { - paint(1, FLAG_NUMERAL); - } - paint(1, FLAG_NUMERAL); - } - } else { - paint(1, FLAG_STRING); - } - } -} - -int syn_py_calculate(struct syntax_state * state) { - switch (state->state) { - case -1: - case 0: - if (charat() == '#') { - paint_comment(state); - } else if (state->i == 0 && match_and_paint(state, "import", FLAG_PRAGMA, c_keyword_qualifier)) { - return 0; - } else if (charat() == '@') { - paint(1, FLAG_PRAGMA); - while (c_keyword_qualifier(charat())) paint(1, FLAG_PRAGMA); - return 0; - } else if (charat() == '"') { - if (nextchar() == '"' && charrel(2) == '"') { - paint(3, FLAG_STRING); - return paint_py_triple_double(state); - } else if (lastchar() == 'f') { - /* I don't like backtracking like this, but it makes this parse easier */ - state->i--; - paint(1,FLAG_TYPE); - paint_py_format_string(state,'"'); - return 0; - } else { - paint_simple_string(state); - return 0; - } - } else if (find_keywords(state, syn_py_keywords, FLAG_KEYWORD, c_keyword_qualifier)) { - return 0; - } else if (lastchar() != '.' && find_keywords(state, syn_py_types, FLAG_TYPE, c_keyword_qualifier)) { - return 0; - } else if (find_keywords(state, syn_py_special, FLAG_NUMERAL, c_keyword_qualifier)) { - return 0; - } else if (charat() == '\'') { - if (nextchar() == '\'' && charrel(2) == '\'') { - paint(3, FLAG_STRING); - return paint_py_triple_single(state); - } else if (lastchar() == 'f') { - /* I don't like backtracking like this, but it makes this parse easier */ - state->i--; - paint(1,FLAG_TYPE); - paint_py_format_string(state,'\''); - return 0; - } else { - return paint_py_single_string(state); - } - } else if (!c_keyword_qualifier(lastchar()) && isdigit(charat())) { - paint_py_numeral(state); - return 0; - } else if (charat() != -1) { - skip(); - return 0; - } - break; - case 1: /* multiline """ string */ - return paint_py_triple_double(state); - case 2: /* multiline ''' string */ - return paint_py_triple_single(state); - } - return -1; -} - -char * syn_py_ext[] = {".py",NULL}; - -BIM_SYNTAX(py, 1) -static char * syn_rust_keywords[] = { - "as","break","const","continue","crate","else","enum","extern", - "false","fn","for","if","impl","in","let","loop","match","mod", - "move","mut","pub","ref","return","Self","self","static","struct", - "super","trait","true","type","unsafe","use","where","while", - NULL, -}; - -static char * syn_rust_types[] = { - "bool","char","str", - "i8","i16","i32","i64", - "u8","u16","u32","u64", - "isize","usize", - "f32","f64", - NULL, -}; - -static int paint_rs_comment(struct syntax_state * state) { - while (charat() != -1) { - if (common_comment_buzzwords(state)) continue; - else if (charat() == '*' && nextchar() == '/') { - paint(2, FLAG_COMMENT); - state->state--; - if (state->state == 0) return 0; - } else if (charat() == '/' && nextchar() == '*') { - state->state++; - paint(2, FLAG_COMMENT); - } else { - paint(1, FLAG_COMMENT); - } - } - return state->state; -} - -int paint_rust_numeral(struct syntax_state * state) { - if (charat() == '0' && nextchar() == 'b') { - paint(2, FLAG_NUMERAL); - while (charat() == '0' || charat() == '1' || charat() == '_') paint(1, FLAG_NUMERAL); - } else if (charat() == '0' && nextchar() == 'o') { - paint(2, FLAG_NUMERAL); - while ((charat() >= '0' && charat() <= '7') || charat() == '_') paint(1, FLAG_NUMERAL); - } else if (charat() == '0' && nextchar() == 'x') { - paint(2, FLAG_NUMERAL); - while (isxdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL); - } else if (charat() == '0' && nextchar() == '.') { - paint(2, FLAG_NUMERAL); - while (isdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL); - } else { - while (isdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL); - if (charat() == '.') { - paint(1, FLAG_NUMERAL); - while (isdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL); - } - } - return 0; -} - -static int syn_rust_calculate(struct syntax_state * state) { - switch (state->state) { - case -1: - case 0: - if (charat() == '/' && nextchar() == '/') { - /* C++-style comments */ - paint_comment(state); - } else if (charat() == '/' && nextchar() == '*') { - paint(2, FLAG_COMMENT); - state->state = 1; - return paint_rs_comment(state); - } else if (find_keywords(state, syn_rust_keywords, FLAG_KEYWORD, c_keyword_qualifier)) { - return 0; - } else if (find_keywords(state, syn_rust_types, FLAG_TYPE, c_keyword_qualifier)) { - return 0; - } else if (charat() == '\"') { - paint_simple_string(state); - return 0; - } else if (charat() == '\'') { - paint_c_char(state); - return 0; - } else if (!c_keyword_qualifier(lastchar()) && isdigit(charat())) { - paint_rust_numeral(state); - return 0; - } else if (charat() != -1) { - skip(); - return 0; - } - break; - default: /* Nested comments */ - return paint_rs_comment(state); - } - - return -1; -} - -char * syn_rust_ext[] = {".rs",NULL}; - -BIM_SYNTAX(rust, 1) -static struct syntax_definition * soy_syn_xml = NULL; -static int _soy_initialized = 0; - -static char * soy_keywords[] = { - "call","template","param","namespace","let","and","if","else","elseif", - "switch","case","default","foreach","literal","sp","nil","lb","rb","in", - NULL -}; - -static char * soy_functions[] = { - "isNonnull","strContains","ceiling","floor","max","min","randomInt", - "round","index","isFirst","isLast","length","augmentMap","keys", - NULL -}; - -int soy_keyword_qualifier(int c) { - return isalnum(c) || (c == '_') || (c == '.'); -} - -int syn_soy_calculate(struct syntax_state * state) { - if (!_soy_initialized) { - _soy_initialized = 1; - soy_syn_xml = find_syntax_calculator("xml"); - } - - if (state->state > 0 && state->state <= 4) { - return soy_syn_xml ? soy_syn_xml->calculate(state) : 0; - } else if (state->state == 5) { - if (paint_c_comment(state) == 1) return 5; - return 0; - } else { - if (charat() == '{') { - paint(1, FLAG_TYPE); - while (charat() != -1 && charat() != '}') { - if (find_keywords(state, soy_keywords, FLAG_KEYWORD, soy_keyword_qualifier)) { - continue; - } else if (find_keywords(state, soy_functions, FLAG_TYPE, soy_keyword_qualifier)) { - continue; - } else if (charat() == '\'') { - paint_single_string(state); - } else if (charat() == '\"') { - paint_simple_string(state); - } else if (charat() == '$') { - paint(1,FLAG_NUMERAL); - while (charat() != -1 && soy_keyword_qualifier(charat())) paint(1, FLAG_NUMERAL); - } else { - skip(); - } - } - if (charat() == '}') paint(1, FLAG_TYPE); - return 0; - } else if (charat() == '/' && nextchar() == '*') { - if (paint_c_comment(state) == 1) return 5; - return 0; - } else { - return soy_syn_xml ? soy_syn_xml->calculate(state) : 0; - } - } - - return -1; -} - -char * syn_soy_ext[] = {".soy",NULL}; - -BIM_SYNTAX(soy, 1) -int syn_xml_calculate(struct syntax_state * state) { - switch (state->state) { - case -1: - case 0: - if (charat() == -1) return -1; - if (charat() != '<') { - skip(); - return 0; - } - /* Opening brace */ - if (charat() == '<' && nextchar() == '!' && charrel(2) == '-' && charrel(3) == '-') { - paint(4, FLAG_COMMENT); - goto _comment; - } - paint(1, FLAG_TYPE); - /* Fall through */ - case 1: - /* State 1: We saw an opening brace. */ - while (charat() != -1) { - if (charat() == '/') paint(1, FLAG_TYPE); - if (charat() == '?') paint(1, FLAG_TYPE); - if (charat() == ' ' || charat() == '\t') skip(); - if (isalnum(charat())) { - while (isalnum(charat()) || charat() == '-') paint(1, FLAG_KEYWORD); - if (charat() == -1) return 2; - goto _in_tag; - } else { - paint(1, FLAG_TYPE); - } - } - return -1; -_in_tag: - case 2: - while (charat() != -1) { - if (charat() == '>') { - paint(1, FLAG_TYPE); - return 0; - } else if (charat() == '"') { - paint_simple_string(state); - if (charat() == -1 && lastchar() != '"') { - return 3; - } - } else { - paint(1, FLAG_TYPE); - } - } - return 2; - case 3: - /* In a string in tag */ - if (charat() == '"') { - paint(1, FLAG_STRING); - return 2; - } else { - paint_simple_string(state); - if (charat() == -1 && lastchar() != '"') { - return 3; - } - } - break; -_comment: - case 4: - while (charat() != -1) { - if (charat() == '-' && nextchar() == '-' && charrel(2) == '>') { - paint(3, FLAG_COMMENT); - return 0; - } else { - if (common_comment_buzzwords(state)) continue; - else paint(1, FLAG_COMMENT); - } - } - return 4; - } - return -1; -} - -char * syn_xml_ext[] = {".xml",".htm",".html",".iml",NULL}; // TODO other stuff that uses xml (it's a lot!); FIXME htm/html are temporary; make dedicated SGML ones for this - -BIM_SYNTAX(xml, 1) diff --git a/apps/bim.h b/apps/bim.h new file mode 100644 index 00000000..589158d0 --- /dev/null +++ b/apps/bim.h @@ -0,0 +1,551 @@ +#ifndef _BIM_CORE_H +#define _BIM_CORE_H + +#define _XOPEN_SOURCE 700 +#define _DARWIN_C_SOURCE +#define _DEFAULT_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __DATE__ +# define BIM_BUILD_DATE " built " __DATE__ " at " __TIME__ +#else +# define BIM_BUILD_DATE DATE "" +#endif + +#ifdef GIT_TAG +# define TAG "-" GIT_TAG +#else +# define TAG "-alpha" +#endif + +#define BIM_VERSION "2.99.0" TAG +#define BIM_COPYRIGHT "Copyright 2012-2021 K. Lange <\033[3mklange@toaruos.org\033[23m>" + +#define BLOCK_SIZE 4096 +#define ENTER_KEY '\r' +#define LINE_FEED '\n' +#define BACKSPACE_KEY 0x08 +#define DELETE_KEY 0x7F +#define DEFAULT_KEY_WAIT (global_config.background_task ? 0 : -1) + +enum Key { + KEY_TIMEOUT = -1, + KEY_CTRL_AT = 0, /* Base */ + KEY_CTRL_A, KEY_CTRL_B, KEY_CTRL_C, KEY_CTRL_D, KEY_CTRL_E, KEY_CTRL_F, KEY_CTRL_G, KEY_CTRL_H, + KEY_CTRL_I, KEY_CTRL_J, KEY_CTRL_K, KEY_CTRL_L, KEY_CTRL_M, KEY_CTRL_N, KEY_CTRL_O, KEY_CTRL_P, + KEY_CTRL_Q, KEY_CTRL_R, KEY_CTRL_S, KEY_CTRL_T, KEY_CTRL_U, KEY_CTRL_V, KEY_CTRL_W, KEY_CTRL_X, + KEY_CTRL_Y, KEY_CTRL_Z, /* Note we keep ctrl-z mapped in termios as suspend */ + KEY_CTRL_OPEN, KEY_CTRL_BACKSLASH, KEY_CTRL_CLOSE, KEY_CTRL_CARAT, KEY_CTRL_UNDERSCORE, + /* Space... */ + /* Some of these are equivalent to things above */ + KEY_BACKSPACE = 0x08, + KEY_LINEFEED = '\n', + KEY_ENTER = '\r', + KEY_TAB = '\t', + /* Basic printable characters go here. */ + /* Delete is special */ + KEY_DELETE = 0x7F, + /* Unicode codepoints go here */ + KEY_ESCAPE = 0x400000, /* Escape would normally be 27, but is special because reasons */ + KEY_F1, KEY_F2, KEY_F3, KEY_F4, + KEY_F5, KEY_F6, KEY_F7, KEY_F8, + KEY_F9, KEY_F10, KEY_F11, KEY_F12, + /* TODO ALT, SHIFT, etc., for F keys */ + KEY_MOUSE, /* Must be followed with a 3-byte mouse read */ + KEY_MOUSE_SGR, /* Followed by an SGR-style sequence of mouse data */ + KEY_HOME, KEY_END, KEY_PAGE_UP, KEY_PAGE_DOWN, + KEY_UP, KEY_DOWN, KEY_RIGHT, KEY_LEFT, + KEY_SHIFT_UP, KEY_SHIFT_DOWN, KEY_SHIFT_RIGHT, KEY_SHIFT_LEFT, + KEY_CTRL_UP, KEY_CTRL_DOWN, KEY_CTRL_RIGHT, KEY_CTRL_LEFT, + KEY_ALT_UP, KEY_ALT_DOWN, KEY_ALT_RIGHT, KEY_ALT_LEFT, + KEY_ALT_SHIFT_UP, KEY_ALT_SHIFT_DOWN, KEY_ALT_SHIFT_RIGHT, KEY_ALT_SHIFT_LEFT, + KEY_SHIFT_TAB, + /* Special signals for paste start, paste end */ + KEY_PASTE_BEGIN, KEY_PASTE_END, +}; + +struct key_name_map { + enum Key keycode; + char * name; +}; + +extern struct key_name_map KeyNames[]; + +/** + * Syntax highlighting flags. + */ +#define FLAG_NONE 0 +#define FLAG_KEYWORD 1 +#define FLAG_STRING 2 +#define FLAG_COMMENT 3 +#define FLAG_TYPE 4 +#define FLAG_PRAGMA 5 +#define FLAG_NUMERAL 6 +#define FLAG_ERROR 7 +#define FLAG_DIFFPLUS 8 +#define FLAG_DIFFMINUS 9 +#define FLAG_NOTICE 10 +#define FLAG_BOLD 11 +#define FLAG_LINK 12 +#define FLAG_ESCAPE 13 + +#define FLAG_SELECT (1 << 5) +#define FLAG_SEARCH (1 << 6) + +/** + * Line buffer definitions + * + * Lines are essentially resizable vectors of char_t structs, + * which represent single codepoints in the file. + */ +typedef struct { + uint32_t display_width:4; + uint32_t flags:7; + uint32_t codepoint:21; +} __attribute__((packed)) char_t; + +/** + * Lines have available and actual lengths, describing + * how much space was allocated vs. how much is being + * used at the moment. + */ +typedef struct { + int available; + int actual; + int istate; + int is_current; + int rev_status; + char_t text[]; +} line_t; + +typedef struct background_task { + struct _env * env; + void (*func)(struct background_task*); + struct background_task * next; + int _private_i; + void * _private_p; +} background_task_t; + +/** + * Global configuration state + * + * At the moment, this is all in a global, but in the future + * this should be passed around to various functions. + */ +typedef struct { + /* Terminal size */ + int term_width, term_height; + int bottom_size; + + line_t ** yanks; + size_t yank_count; + int yank_is_full_lines; + + int tty_in; + + const char * bimrc_path; + const char * syntax_fallback; + uint32_t * search; + + int overlay_mode; + line_t * command_buffer; + + int command_offset, command_col_no; + struct syntax_definition * command_syn, * command_syn_back; + int history_point; + int search_direction; + int prev_line, prev_col, prev_coffset, prev_offset; + + unsigned int highlight_on_open:1; + unsigned int initial_file_is_read_only:1; + unsigned int go_to_line:1; + unsigned int break_from_selection:1; + unsigned int can_scroll:1; + unsigned int can_hideshow:1; + unsigned int can_altscreen:1; + unsigned int can_mouse:1; + unsigned int can_unicode:1; + unsigned int can_bright:1; + unsigned int can_title:1; + unsigned int can_bce:1; + unsigned int can_24bit:1; + unsigned int can_256color:1; + unsigned int can_italic:1; + unsigned int can_insert:1; + unsigned int can_bracketedpaste:1; + unsigned int can_sgrmouse:1; + + unsigned int history_enabled:1; + unsigned int highlight_parens:1; + unsigned int smart_case:1; + unsigned int highlight_current_line:1; + unsigned int shift_scrolling:1; + unsigned int check_git:1; + unsigned int color_gutter:1; + unsigned int relative_lines:1; + unsigned int numbers:1; + unsigned int horizontal_shift_scrolling:1; + unsigned int hide_statusbar:1; + unsigned int tabs_visible:1; + unsigned int autohide_tabs:1; + unsigned int smart_complete:1; + unsigned int has_terminal:1; + unsigned int search_wraps:1; + unsigned int had_error:1; + unsigned int use_biminfo:1; + + int cursor_padding; + int split_percent; + int scroll_amount; + int tab_offset; + + char * tab_indicator; + char * space_indicator; + + background_task_t * background_task; + background_task_t * tail_task; + +} global_config_t; + +#define OVERLAY_MODE_NONE 0 +#define OVERLAY_MODE_READ_ONE 1 +#define OVERLAY_MODE_COMMAND 2 +#define OVERLAY_MODE_SEARCH 3 +#define OVERLAY_MODE_COMPLETE 4 +#define OVERLAY_MODE_FILESEARCH 5 + +#define HISTORY_SENTINEL 0 +#define HISTORY_INSERT 1 +#define HISTORY_DELETE 2 +#define HISTORY_REPLACE 3 +#define HISTORY_REMOVE_LINE 4 +#define HISTORY_ADD_LINE 5 +#define HISTORY_REPLACE_LINE 6 +#define HISTORY_MERGE_LINES 7 +#define HISTORY_SPLIT_LINE 8 + +#define HISTORY_BREAK 10 + +typedef struct history { + struct history * previous; + struct history * next; + int type; + int line; + int col; + union { + struct { + int lineno; + int offset; + int codepoint; + int old_codepoint; + } insert_delete_replace; + + struct { + int lineno; + line_t * contents; + line_t * old_contents; + } remove_replace_line; + + struct { + int lineno; + int split; + } add_merge_split_lines; + } contents; +} history_t; + +/** + * Buffer data + * + * A buffer describes a file, and stores + * its name as well as the editor state + * (cursor offsets, etc.) and the actual + * line buffers. + */ +typedef struct _env { + unsigned int loading:1; + unsigned int tabs:1; + unsigned int modified:1; + unsigned int readonly:1; + unsigned int indent:1; + unsigned int checkgitstatusonwrite:1; + unsigned int crnl:1; + unsigned int numbers:1; + unsigned int gutter:1; + unsigned int slowop:1; + + int highlighting_paren; + int maxcolumn; + + short mode; + short tabstop; + + char * file_name; + int offset; + int coffset; + int line_no; + int line_count; + int line_avail; + int col_no; + int preferred_column; + struct syntax_definition * syntax; + line_t ** lines; + + history_t * history; + history_t * last_save_history; + + int width; + int left; + + int start_line; + int sel_col; + int start_col; + int prev_line; +} buffer_t; + +struct theme_def { + const char * name; + void * callable; +}; + +extern struct theme_def * themes; + +extern void add_colorscheme(struct theme_def theme); + +struct syntax_state { + buffer_t * env; + line_t * line; + int line_no; + int state; + int i; +}; + +struct completion_match { + char * string; + char * file; + char * search; +}; + +struct syntax_definition { + char * name; + char ** ext; + int (*calculate)(struct syntax_state *); + int prefers_spaces; + int (*completion_qualifier)(int c); + int (*completion_matcher)(uint32_t * comp, struct completion_match ** matches, int * matches_count, int complete_match, int * matches_len, buffer_t * env); + void * krkFunc; + void * krkClass; +}; + +extern struct syntax_definition * syntaxes; + +/** + * Editor mode states + */ +#define MODE_NORMAL 0 +#define MODE_INSERT 1 +#define MODE_LINE_SELECTION 2 +#define MODE_REPLACE 3 +#define MODE_CHAR_SELECTION 4 +#define MODE_COL_SELECTION 5 +#define MODE_COL_INSERT 6 +#define MODE_DIRECTORY_BROWSE 7 + +struct action_def { + char * name; + void (*action)(); + int options; + const char * description; +}; + +extern struct action_def * mappable_actions; + +#define ARG_IS_INPUT 0x01 /* Takes the key that triggered it as the first argument */ +#define ARG_IS_CUSTOM 0x02 /* Takes a custom argument which is specific to the method */ +#define ARG_IS_PROMPT 0x04 /* Prompts for an argument. */ +#define ACTION_IS_RW 0x08 /* Needs to be able to write. */ + +#define BIM_ACTION(name, options, description) \ + void name (); /* Define the action with unknown arguments */ \ + void __attribute__((constructor)) _install_ ## name (void) { \ + add_action((struct action_def){#name, name, options, description}); \ + } \ + void name + +struct command_def { + char * name; + int (*command)(char *, int, char * arg[]); + const char * description; +}; + +#define BIM_COMMAND(cmd_name, cmd_str, description) \ + int bim_command_ ## cmd_name (char * cmd, int argc, char * argv[]); \ + void __attribute__((constructor)) _install_cmd_ ## cmd_name (void) { \ + add_command((struct command_def){cmd_str, bim_command_ ## cmd_name, description}); \ + } \ + int bim_command_ ## cmd_name (char * cmd __attribute__((unused)), int argc __attribute__((unused)), char * argv[] __attribute__((unused))) + +#define BIM_ALIAS(alias, alias_name, cmd_name) \ + void __attribute__((constructor)) _install_alias_ ## alias_name (void) { \ + add_command((struct command_def){alias, bim_command_ ## cmd_name, "Alias for " #cmd_name}); \ + } + +#define BIM_PREFIX_COMMAND(cmd_name, cmd_prefix, description) \ + int bim_command_ ## cmd_name (char * cmd, int argc, char * argv[]); \ + void __attribute__((constructor)) _install_cmd_ ## cmd_name (void) { \ + add_prefix_command((struct command_def){cmd_prefix, bim_command_ ## cmd_name, description}); \ + } \ + int bim_command_ ## cmd_name (char * cmd __attribute__((unused)), int argc __attribute__((unused)), char * argv[] __attribute__((unused))) + +extern const char * flag_to_color(int _flag); +extern void redraw_line(int x); +extern int git_examine(char * filename); +extern void search_next(void); +extern void set_preferred_column(void); +extern void quit(const char * message); +extern void close_buffer(void); +extern void set_syntax_by_name(const char * name); +extern void rehighlight_search(line_t * line); +extern void try_to_center(); +extern int read_one_character(char * message); +extern void bim_unget(int c); +#define bim_getch() bim_getch_timeout(200) +extern int bim_getch_timeout(int timeout); +extern buffer_t * buffer_new(void); +extern FILE * open_biminfo(void); +extern int fetch_from_biminfo(buffer_t * buf); +extern int update_biminfo(buffer_t * buf, int is_open); +extern buffer_t * buffer_close(buffer_t * buf); +extern int to_eight(uint32_t codepoint, char * out); +extern char * name_from_key(enum Key keycode); +extern void add_action(struct action_def action); +extern void open_file(char * file); +extern void recalculate_selected_lines(void); +extern void add_command(struct command_def command); +extern void add_prefix_command(struct command_def command); +extern void render_command_input_buffer(void); +extern void unhighlight_matching_paren(void); + +extern void add_syntax(struct syntax_definition def); + +struct ColorName { + const char * name; + const char ** value; +}; + +extern struct ColorName color_names[]; + +struct bim_function { + char * command; + struct bim_function * next; +}; + +extern struct bim_function ** user_functions; +extern int run_function(char * name); +extern int has_function(char * name); +extern void find_matching_paren(int * out_line, int * out_col, int in_col); +extern void render_error(char * message, ...); +extern void render_commandline_message(char * message, ...); +extern void pause_for_key(void); + +#define add_match(match_string, match_file, match_search) do { \ + if (*matches_count == *matches_len) { \ + (*matches_len) *= 2; \ + *matches = realloc(*matches, sizeof(struct completion_match) * (*matches_len)); \ + } \ + (*matches)[*matches_count].string = strdup(match_string); \ + (*matches)[*matches_count].file = strdup(match_file); \ + (*matches)[*matches_count].search = strdup(match_search); \ + (*matches_count)++; \ +} while (0) + +#define add_if_match(name,desc) do { \ + int i = 0; \ + while (comp[i] && comp[i] == (unsigned char)name[i]) i++; \ + if (comp[i] == '\0') { \ + add_match(name,desc,""); \ + } \ +} while (0) + +struct action_map { + int key; + void (*method)(); + int options; + int arg; +}; + +#define opt_rep 0x1 /* This action will be repeated */ +#define opt_arg 0x2 /* This action will take a specified argument */ +#define opt_char 0x4 /* This action will read a character to pass as an argument */ +#define opt_nav 0x8 /* This action will consume the nav buffer as its argument */ +#define opt_rw 0x10 /* Must not be read-only */ +#define opt_norm 0x20 /* Returns to normal mode */ +#define opt_byte 0x40 /* Same as opt_char but forces a byte */ + +struct mode_names { + const char * description; + const char * name; + struct action_map ** mode; +}; + +extern struct mode_names mode_names[]; + +#define paint(length, flag) do { for (int i = 0; i < (length) && state->i < state->line->actual; i++, state->i++) { \ + state->line->text[state->i].flags = (state->line->text[state->i].flags & (3 << 5)) | (flag); \ +} } while (0) +#define charat() (state->i < state->line->actual ? state->line->text[(state->i)].codepoint : -1) +#define nextchar() (state->i + 1 < state->line->actual ? state->line->text[(state->i+1)].codepoint : -1) +#define lastchar() (state->i - 1 >= 0 ? state->line->text[(state->i-1)].codepoint : -1) +#define skip() (state->i++) +#define charrel(x) ((state->i + (x) >= 0 && state->i + (x) < state->line->actual) ? state->line->text[(state->i+(x))].codepoint : -1) + +extern int find_keywords(struct syntax_state * state, char ** keywords, int flag, int (*keyword_qualifier)(int c)); +extern int match_and_paint(struct syntax_state * state, const char * keyword, int flag, int (*keyword_qualifier)(int c)); +extern void paint_single_string(struct syntax_state * state); +extern void paint_simple_string(struct syntax_state * state); +extern int common_comment_buzzwords(struct syntax_state * state); +extern int paint_comment(struct syntax_state * state); +extern int match_forward(struct syntax_state * state, char * c); +extern struct syntax_definition * find_syntax_calculator(const char * name); + +#define nest(lang, low) \ + do { \ + state->state = (state->state < 1 ? 0 : state->state - low); \ + do { state->state = lang(state); } while (state->state == 0); \ + if (state->state == -1) return low; \ + return state->state + low; \ + } while (0) + +/* Some of the C stuff is widely used */ +extern int c_keyword_qualifier(int c); +extern int paint_c_numeral(struct syntax_state * state); +extern int paint_c_comment(struct syntax_state * state); +extern void paint_c_char(struct syntax_state * state); + +/* Hacky workaround for isdigit not really accepting Unicode stuff */ +static __attribute__((used)) int _isdigit(int c) { if (c > 128) return 0; return isdigit(c); } +static __attribute__((used)) int _isxdigit(int c) { if (c > 128) return 0; return isxdigit(c); } + +#undef isdigit +#undef isxdigit +#define isdigit(c) _isdigit(c) +#define isxdigit(c) _isxdigit(c) + +#endif /* _BIM_CORE_H */ diff --git a/apps/cat-img.c b/apps/cat-img.c index ccb1fadb..4bc4e478 100644 --- a/apps/cat-img.c +++ b/apps/cat-img.c @@ -91,7 +91,6 @@ int main (int argc, char * argv[]) { while (optind < argc) { sprite_t * image = calloc(sizeof(sprite_t),1); load_sprite(image, argv[optind]); - image->alpha = ALPHA_EMBEDDED; sprite_t * source = image; diff --git a/apps/compositor.c b/apps/compositor.c index c6c42089..6478c89d 100644 --- a/apps/compositor.c +++ b/apps/compositor.c @@ -2174,18 +2174,12 @@ int main(int argc, char * argv[]) { TRACE("Loading sprites..."); #define MOUSE_DIR "/usr/share/cursor/" - load_sprite(&yg->mouse_sprite, MOUSE_DIR "mouse.bmp"); - yg->mouse_sprite.alpha = ALPHA_EMBEDDED; - load_sprite(&yg->mouse_sprite_drag, MOUSE_DIR "drag.bmp"); - yg->mouse_sprite_drag.alpha = ALPHA_EMBEDDED; - load_sprite(&yg->mouse_sprite_resize_v, MOUSE_DIR "resize-vertical.bmp"); - yg->mouse_sprite_resize_v.alpha = ALPHA_EMBEDDED; - load_sprite(&yg->mouse_sprite_resize_h, MOUSE_DIR "resize-horizontal.bmp"); - yg->mouse_sprite_resize_h.alpha = ALPHA_EMBEDDED; - load_sprite(&yg->mouse_sprite_resize_da, MOUSE_DIR "resize-uldr.bmp"); - yg->mouse_sprite_resize_da.alpha = ALPHA_EMBEDDED; - load_sprite(&yg->mouse_sprite_resize_db, MOUSE_DIR "resize-dlur.bmp"); - yg->mouse_sprite_resize_db.alpha = ALPHA_EMBEDDED; + load_sprite(&yg->mouse_sprite, MOUSE_DIR "normal.png"); + load_sprite(&yg->mouse_sprite_drag, MOUSE_DIR "drag.png"); + load_sprite(&yg->mouse_sprite_resize_v, MOUSE_DIR "resize-vertical.png"); + load_sprite(&yg->mouse_sprite_resize_h, MOUSE_DIR "resize-horizontal.png"); + load_sprite(&yg->mouse_sprite_resize_da, MOUSE_DIR "resize-uldr.png"); + load_sprite(&yg->mouse_sprite_resize_db, MOUSE_DIR "resize-dlur.png"); TRACE("Done."); TRACE("Initializing variables..."); diff --git a/apps/file-browser.c b/apps/file-browser.c index 48a3c5dc..5f64e71d 100644 --- a/apps/file-browser.c +++ b/apps/file-browser.c @@ -32,7 +32,6 @@ #include #include #include -#include #define APPLICATION_TITLE "File Browser" #define SCROLL_AMOUNT 120 @@ -61,9 +60,9 @@ static int available_height = 0; /* How much space is available in the main wind static int is_desktop_background = 0; /* If we're in desktop background mode */ static int menu_bar_height = MENU_BAR_HEIGHT + 36; /* Height of the menu bar, if present - it's not in desktop mode */ static sprite_t * wallpaper_buffer = NULL; /* Prebaked wallpaper texture */ -static sprite_t * wallpaper_old = NULL; -static uint64_t timer = 0; -static int restart = 0; +static sprite_t * wallpaper_old = NULL; /* Previous wallpaper when transitioning */ +static uint64_t timer = 0; /* Timer for wallpaper transition fade */ +static int restart = 0; /* Signal for desktop wallpaper to kill itself to save memory (this is dumb) */ static char title[512]; /* Application title bar */ static int FILE_HEIGHT = 80; /* Height of one row of icons */ static int FILE_WIDTH = 100; /* Width of one column of icons */ @@ -77,13 +76,18 @@ static ssize_t file_pointers_len = 0; /* How many files are in the current list static uint64_t last_click = 0; /* For double click */ static int last_click_offset = -1; /* So that clicking two different things quickly doesn't count as a double click */ +/** + * Navigation input box + */ static char nav_bar[512] = {0}; static int nav_bar_cursor = 0; static int nav_bar_cursor_x = 0; static int nav_bar_focused = 0; +/* Status bar displayed at the bottom of the window */ static char window_status[512] = {0}; +/* Button row visibility statuses */ static int _button_hilights[4] = {3,3,3,3}; static int _button_disabled[4] = {1,1,0,0}; static int _button_hover = -1; @@ -99,6 +103,7 @@ static struct menu_bar_entries menu_entries[] = { {NULL, NULL}, }; +/* Right click context menu */ static struct MenuList * context_menu = NULL; /** @@ -191,6 +196,27 @@ static int print_human_readable_size(char * _out, uint64_t s) { #define HILIGHT_GRADIENT_BOTTOM rgb(56,137,220) #define HILIGHT_BORDER_BOTTOM rgb(47,106,167) +/** + * Clip text and add ellipsis to fit a specified display width. + */ +static char * ellipsify(char * input, int font_size, int font, int max_width, int * out_width) { + int len = strlen(input); + char * out = malloc(len + 4); + memcpy(out, input, len + 1); + int width; + while ((width = draw_sdf_string_width(out, font_size, font)) > max_width) { + len--; + out[len+0] = '.'; + out[len+1] = '.'; + out[len+2] = '.'; + out[len+3] = '\0'; + } + + if (out_width) *out_width = width; + + return out; +} + /** * Draw an icon view entry */ @@ -207,17 +233,8 @@ static void draw_file(struct File * f, int offset) { sprite_t * icon = icon_get_48(f->icon); /* If the display name is too long to fit, cut it with an ellipsis. */ - int len = strlen(f->name); - char * name = malloc(len + 4); - memcpy(name, f->name, len + 1); int name_width; - while ((name_width = draw_sdf_string_width(name, 16, SDF_FONT_THIN)) > FILE_WIDTH - 8 /* Padding */) { - len--; - name[len+0] = '.'; - name[len+1] = '.'; - name[len+2] = '.'; - name[len+3] = '\0'; - } + char * name = ellipsify(f->name, 16, SDF_FONT_THIN, FILE_WIDTH - 8, &name_width); /* Draw the icon */ int center_x_icon = (FILE_WIDTH - icon->width) / 2; @@ -276,21 +293,12 @@ static void draw_file(struct File * f, int offset) { draw_sprite_alpha_paint(contents, icon, x + 11, y + 11, 0.3, rgb(255,255,255)); } - int len = strlen(f->name); - char * name = malloc(len + 4); - memcpy(name, f->name, len + 1); - int name_width; - while ((name_width = draw_sdf_string_width(name, 16, SDF_FONT_BOLD)) > FILE_WIDTH - 81) { - len--; - name[len+0] = '.'; - name[len+1] = '.'; - name[len+2] = '.'; - name[len+3] = '\0'; - } + char * name = ellipsify(f->name, 16, SDF_FONT_BOLD, FILE_WIDTH - 81, NULL); + char * type = ellipsify(f->filetype, 16, SDF_FONT_THIN, FILE_WIDTH - 81, NULL); if (f->type == 0) { draw_sdf_string(contents, x + 70, y + 8, name, 16, text_color, SDF_FONT_BOLD); - draw_sdf_string(contents, x + 70, y + 25, f->filetype, 16, text_color, SDF_FONT_THIN); + draw_sdf_string(contents, x + 70, y + 25, type, 16, text_color, SDF_FONT_THIN); char line_three[48] = {0}; if (*f->link) { @@ -301,10 +309,11 @@ static void draw_file(struct File * f, int offset) { draw_sdf_string(contents, x + 70, y + 42, line_three, 16, text_color, SDF_FONT_THIN); } else { draw_sdf_string(contents, x + 70, y + 15, name, 16, text_color, SDF_FONT_BOLD); - draw_sdf_string(contents, x + 70, y + 32, f->filetype, 16, text_color, SDF_FONT_THIN); + draw_sdf_string(contents, x + 70, y + 32, type, 16, text_color, SDF_FONT_THIN); } free(name); + free(type); } else if (view_mode == VIEW_MODE_LIST) { sprite_t * icon = icon_get_16(f->icon); uint32_t text_color = rgb(0,0,0); @@ -327,21 +336,12 @@ static void draw_file(struct File * f, int offset) { draw_sprite(contents, icon, x + 4, y + 4); } - - int len = strlen(f->name); - char * name = malloc(len + 4); - memcpy(name, f->name, len + 1); - int name_width; - while ((name_width = draw_sdf_string_width(name, 16, SDF_FONT_THIN)) > FILE_WIDTH - 26) { - len--; - name[len+0] = '.'; - name[len+1] = '.'; - name[len+2] = '.'; - name[len+3] = '\0'; - } + char * name = ellipsify(f->name, 16, SDF_FONT_THIN, FILE_WIDTH - 26, NULL); draw_sdf_string(contents, x + 24, y + 2, name, 16, text_color, SDF_FONT_THIN); + free(name); + } } @@ -405,9 +405,18 @@ static int has_extension(struct File * f, char * extension) { return 0; } +/** + * Forward/backward history; we're always in the middle of these. + * When we navigate somewhere new, clear the forward history, but + * keep the back history and append the previous location. + */ static list_t * history_back; static list_t * history_forward; +/** + * Update the status bar text at the bottom of the window + * based on the selected items in the file view. + */ static void update_status(void) { uint64_t total_size = 0; uint64_t selected_size = 0; @@ -445,17 +454,22 @@ static void load_directory(const char * path, int modifies_history) { DIR * dirp = opendir(path); if (!dirp) { - /* XXX show a dialog */ + /* + * Display a warning dialog with the appropriate error message for + * why this directory failed to load. XXX This uses `showdialog` + * but it should use a dialog library like with the buttons. + */ if (!fork()) { char tmp[512]; sprintf(tmp, "Could not open directory \"%s\": %s", path, strerror(errno)); - char * args[] = {"showdialog","File Browser","/usr/share/icons/48/folder.bmp",tmp,NULL}; + char * args[] = {"showdialog","File Browser","/usr/share/icons/48/folder.png",tmp,NULL}; execvp(args[0],args); exit(0); } return; } + /* Free the previously loaded directory */ if (file_pointers) { for (int i = 0; i < file_pointers_len; ++i) { free(file_pointers[i]); @@ -479,18 +493,21 @@ static void load_directory(const char * path, int modifies_history) { free(current_directory); } - _button_disabled[0] = !(history_back->length); - _button_disabled[1] = !(history_forward->length); - _button_disabled[2] = 0; - _button_disabled[3] = 0; + /* Set button displays appropriately */ + _button_disabled[0] = !(history_back->length); /* Back */ + _button_disabled[1] = !(history_forward->length); /* Forward */ + _button_disabled[2] = 0; /* Up */ + _button_disabled[3] = 0; /* Home */ char * home = getenv("HOME"); if (home && !strcmp(path, home)) { /* If the current directory is the user's homedir, present it that way in the title */ set_title("Home"); + /* Disable the 'go home' button */ _button_disabled[3] = 1; } else if (!strcmp(path, "/")) { set_title("File System"); + /* If this is the root of the file system, disable the up button */ _button_disabled[2] = 1; } else { /* Otherwise use just the directory base name */ @@ -614,6 +631,10 @@ static void load_directory(const char * path, int modifies_history) { sprintf(f->icon, "image"); sprintf(f->launcher, "exec imgviewer"); sprintf(f->filetype, "JPEG Image"); + } else if (has_extension(f, ".png")) { + sprintf(f->icon, "image"); + sprintf(f->launcher, "exec imgviewer"); + sprintf(f->filetype, "Portable Network Graphics Image"); } else if (has_extension(f, ".sdf")) { sprintf(f->icon, "font"); sprintf(f->filetype, "SDF Font"); @@ -758,14 +779,18 @@ static void reinitialize_contents(void) { #define BUTTON_SPACE 34 #define BUTTON_COUNT 4 +/** + * Render toolbar buttons + */ static void _draw_buttons(struct decor_bounds bounds) { + + /* Draws the toolbar background as a gradient; XXX hardcoded theme details */ uint32_t gradient_top = rgb(59,59,59); uint32_t gradient_bot = rgb(40,40,40); for (int i = 0; i < 36; ++i) { uint32_t c = interp_colors(gradient_top, gradient_bot, i * 255 / 36); draw_rectangle(ctx, bounds.left_width, bounds.top_height + MENU_BAR_HEIGHT + i, BUTTON_SPACE * BUTTON_COUNT, 1, c); - //ctx->width - bounds.width, 1, c); } int x = 0; @@ -775,12 +800,17 @@ static void _draw_buttons(struct decor_bounds bounds) { ttk_button_draw(ctx, &_up); \ x += BUTTON_SPACE; i++; } while (0) + /* Draw actual buttons */ draw_button("back"); draw_button("forward"); draw_button("up"); draw_button("home"); } +/** + * Determine what character offset the cursor should be at for + * a given X coordinate. + */ static void _figure_out_navbar_cursor(int x, struct decor_bounds bounds) { x = x - bounds.left_width - 2 - BUTTON_SPACE * BUTTON_COUNT - 5; if (x <= 0) { @@ -798,6 +828,12 @@ static void _figure_out_navbar_cursor(int x, struct decor_bounds bounds) { free(tmp); } +/** + * Recalculate the location of the cursor indicator bar + * based on the current cursor character offset; + * also handles cursor bounds within the text + * (eg. to avoid cursor moving beyond the beginning) + */ static void _recalculate_nav_bar_cursor(void) { if (nav_bar_cursor < 0) { nav_bar_cursor = 0; @@ -811,7 +847,12 @@ static void _recalculate_nav_bar_cursor(void) { free(tmp); } +/** + * Draw the navigation input box. + */ static void _draw_nav_bar(struct decor_bounds bounds) { + + /* Draw toolbar background */ uint32_t gradient_top = rgb(59,59,59); uint32_t gradient_bot = rgb(40,40,40); int x = BUTTON_SPACE * BUTTON_COUNT; @@ -822,6 +863,7 @@ static void _draw_nav_bar(struct decor_bounds bounds) { ctx->width - bounds.width - BUTTON_SPACE * BUTTON_COUNT, 1, c); } + /* Draw input box */ if (nav_bar_focused) { struct gradient_definition edge = {28, bounds.top_height + MENU_BAR_HEIGHT + 3, rgb(0,120,220), rgb(0,120,220)}; draw_rounded_rectangle_pattern(ctx, bounds.left_width + 2 + x + 1, bounds.top_height + MENU_BAR_HEIGHT + 4, main_window->width - bounds.width - x - 6, 26, 4, gfx_vertical_gradient_pattern, &edge); @@ -832,21 +874,11 @@ static void _draw_nav_bar(struct decor_bounds bounds) { draw_rounded_rectangle(ctx, bounds.left_width + 2 + x + 2, bounds.top_height + MENU_BAR_HEIGHT + 5, main_window->width - bounds.width - x - 8, 24, 3, rgb(250,250,250)); } - /* Draw the nav bar text */ + /* Draw the nav bar text, ellipsified if needed */ int max_width = main_window->width - bounds.width - x - 12; - int len = strlen(nav_bar); - char * name = malloc(len + 5); - memcpy(name, nav_bar, len + 1); - int name_width; - while ((name_width = draw_sdf_string_width(name, 16, SDF_FONT_THIN)) > max_width) { - len--; - name[len+0] = '.'; - name[len+1] = '.'; - name[len+2] = '.'; - name[len+3] = '\0'; - } - + char * name = ellipsify(nav_bar, 16, SDF_FONT_THIN, max_width, NULL); draw_sdf_string(ctx, bounds.left_width + 2 + x + 5, bounds.top_height + MENU_BAR_HEIGHT + 8, name, 16, rgb(0,0,0), SDF_FONT_THIN); + free(name); if (nav_bar_focused) { /* Draw cursor indicator at cursor_x */ @@ -860,7 +892,12 @@ static void _draw_nav_bar(struct decor_bounds bounds) { } #define STATUS_HEIGHT 24 +/** + * Draw the status bar at the bottom of the window + */ static void _draw_status(struct decor_bounds bounds) { + + /* Background gradient */ uint32_t gradient_top = rgb(80,80,80); uint32_t gradient_bot = rgb(59,59,59); draw_rectangle(ctx, bounds.left_width, ctx->height - bounds.bottom_height - STATUS_HEIGHT, @@ -871,6 +908,8 @@ static void _draw_status(struct decor_bounds bounds) { ctx->width - bounds.width, 1, c ); } + + /* Text with draw shadow */ { sprite_t * _tmp_s = create_sprite(ctx->width - bounds.width - 4, STATUS_HEIGHT-3, ALPHA_EMBEDDED); gfx_context_t * _tmp = init_graphics_sprite(_tmp_s); @@ -887,6 +926,9 @@ static void _draw_status(struct decor_bounds bounds) { } } +/** + * Redraw the navigation input box (while typing) + */ static void _redraw_nav_bar(void) { struct decor_bounds bounds; _decor_get_bounds(main_window, &bounds); @@ -895,6 +937,9 @@ static void _redraw_nav_bar(void) { yutani_flip(yctx, main_window); } +/** + * navbar: Text editing helpers for ^W, deletes one directory element + */ static void nav_bar_backspace_word(void) { if (!*nav_bar) return; if (nav_bar_cursor == 0) return; @@ -917,6 +962,9 @@ static void nav_bar_backspace_word(void) { _redraw_nav_bar(); } +/** + * navbar: Text editing helper for backspace, deletes one character + */ static void nav_bar_backspace(void) { if (nav_bar_cursor == 0) return; @@ -932,6 +980,9 @@ static void nav_bar_backspace(void) { _redraw_nav_bar(); } +/** + * navbar: Text editing helper for inserting characters + */ static void nav_bar_insert_char(char c) { char * tmp = strdup(nav_bar); tmp[nav_bar_cursor] = '\0'; @@ -945,12 +996,18 @@ static void nav_bar_insert_char(char c) { _redraw_nav_bar(); } +/** + * navbar: Move editing cursor one character left + */ static void nav_bar_cursor_left(void) { nav_bar_cursor--; _recalculate_nav_bar_cursor(); _redraw_nav_bar(); } +/** + * navbar: Move editing cursor one character right + */ static void nav_bar_cursor_right(void) { nav_bar_cursor++; _recalculate_nav_bar_cursor(); @@ -1056,7 +1113,7 @@ static void draw_background(int width, int height) { } } - load_sprite_jpg(wallpaper, wallpaper_path); + load_sprite(wallpaper, wallpaper_path); if (free_it) { free(wallpaper_path); @@ -1232,7 +1289,7 @@ static void _menu_action_paste(struct MenuEntry * entry) { static void _menu_action_about(struct MenuEntry * entry) { /* Show About dialog */ char about_cmd[1024] = "\0"; - strcat(about_cmd, "about \"About File Browser\" /usr/share/icons/48/folder.bmp \"ToaruOS File Browser\" \"(C) 2018 K. Lange\n-\nPart of ToaruOS, which is free software\nreleased under the NCSA/University of Illinois\nlicense.\n-\n%https://toaruos.org\n%https://github.com/klange/toaruos\" "); + strcat(about_cmd, "about \"About File Browser\" /usr/share/icons/48/folder.png \"PonyOS File Browser\" \"(C) 2018 K. Lange\n-\nPart of PonyOS, which is free software\nreleased under the NCSA/University of Illinois\nlicense.\n-\n%https://ponyos.org\n%https://github.com/klange/ponyos\" "); char coords[100]; sprintf(coords, "%d %d &", (int)main_window->x + (int)main_window->width / 2, (int)main_window->y + (int)main_window->height / 2); strcat(about_cmd, coords); @@ -1284,7 +1341,7 @@ static void open_file(struct File * f) { /* "SELF" launchers are for binaries. */ sprintf(tmp, "exec ./%s", f->name); } else { - /* Other launchers shouuld take file names as arguments. + /* Other launchers should take file names as arguments. * NOTE: If you don't want the file name, you can append # to your launcher. * Since it's parsed by the shell, this will yield a comment. */ @@ -1330,6 +1387,14 @@ static void _menu_action_select_all(struct MenuEntry * self) { redraw_window(); } +/** + * Set the view mode for the file view + * We support three modes: + * - Icons: Standard view, label centered below icon. + * - Tiles: Like Windows Explorer, labels left-aligned to the right of + * the icon, with extra details also displayed. Bold file name. + * - List: One-line-per-file, icon on the left, left aligne file name. + */ static void set_view_mode(int mode) { switch (mode) { @@ -1355,6 +1420,10 @@ static void set_view_mode(int mode) { redraw_window(); } +/** + * Dropdown action handler for view mode entries; + * use menuitem action to determine which view mode to set. + */ static void _menu_action_view_mode(struct MenuEntry * entry) { struct MenuEntry_Normal * _entry = (void*)entry; int mode = VIEW_MODE_ICONS; @@ -1368,6 +1437,17 @@ static void _menu_action_view_mode(struct MenuEntry * entry) { set_view_mode(mode); } +/** + * Receive pastes, which are presumed to be file names of files + * which have been copied and should now be pasted into a new + * directory; will not overwrite if pasted into the same directory + * or a directory with a file with the same name. + * + * XXX: Calls `cp` to perform actual copy. + * + * TODO: Actually check if clipboard contains a file name. + * TODO: Handle pastes into the navbar of arbitrary text. + */ static void handle_clipboard(char * contents) { fprintf(stderr, "Received clipboard:\n%s\n",contents); @@ -1390,14 +1470,14 @@ static void handle_clipboard(char * contents) { struct stat statbuf; if (!stat(destination, &statbuf)) { char message[4096]; - sprintf(message, "showdialog \"File Browser\" /usr/share/icons/48/folder.bmp \"Not overwriting file '%s'.\"", cheap_basename); + sprintf(message, "showdialog \"File Browser\" /usr/share/icons/48/folder.png \"Not overwriting file '%s'.\"", cheap_basename); launch_application(message); } else { char cp[1024]; sprintf(cp, "cp -r \"%s\" \"%s\"", file, current_directory); if (system(cp)) { char message[4096]; - sprintf(message, "showdialog \"File Browser\" /usr/share/icons/48/folder.bmp \"Error copying file '%s'.\"", cheap_basename); + sprintf(message, "showdialog \"File Browser\" /usr/share/icons/48/folder.png \"Error copying file '%s'.\"", cheap_basename); launch_application(message); } } @@ -1442,6 +1522,9 @@ static void toggle_selected(int hilighted_offset, int modifiers) { redraw_window(); } +/** + * Handle button hover highlights + */ static int _down_button = -1; static void _set_hilight(int index, int hilight) { int _update = 0; @@ -1461,6 +1544,9 @@ static void _set_hilight(int index, int hilight) { } } +/** + * Handle toolbar button clicking + */ static void _handle_button_press(int index) { if (index != -1 && _button_disabled[index]) return; /* can't click disabled buttons */ switch (index) { @@ -1505,6 +1591,9 @@ static void _handle_button_press(int index) { } } +/** + * Scroll file view up + */ static void _scroll_up(void) { scroll_offset -= SCROLL_AMOUNT; if (scroll_offset < 0) { @@ -1512,6 +1601,9 @@ static void _scroll_up(void) { } } +/** + * Scroll file view down + */ static void _scroll_down(void) { if (available_height > contents->height) { scroll_offset = 0; @@ -1533,11 +1625,21 @@ static void sig_usr2(int sig) { signal(SIGUSR2, sig_usr2); } +/** + * Desktop mode responds to sig_usr1 by resizing the window + * to the current display size. + */ static void sig_usr1(int sig) { yutani_window_resize_offer(yctx, main_window, yctx->display_width, yctx->display_height); signal(SIGUSR1, sig_usr1); } +/** + * Accept keyboard arrows left/right/up/down and select the appropriate + * file in the file view based on the currently selected file; if multiple + * files are currently selected, the first one (up/left) is used as the basis + * for the new selection. + */ static void arrow_select(int x, int y) { if (!file_pointers_len) return; @@ -1588,6 +1690,11 @@ static void arrow_select(int x, int y) { redraw_window(); } +static void redraw_window_callback(struct menu_bar * self) { + (void)self; + redraw_window(); +} + int main(int argc, char * argv[]) { yctx = yutani_init(); @@ -1625,7 +1732,7 @@ int main(int argc, char * argv[]) { set_title(NULL); menu_bar.entries = menu_entries; - menu_bar.redraw_callback = redraw_window; + menu_bar.redraw_callback = redraw_window_callback; menu_bar.set = menu_set_create(); @@ -1651,11 +1758,6 @@ int main(int argc, char * argv[]) { menu_set_insert(menu_bar.set, "view", m); m = menu_create(); /* Go */ - /* TODO implement input dialog for Path... */ -#if 0 - menu_insert(m, menu_create_normal("open",NULL,"Path...", _menu_action_input_path)); - menu_insert(m, menu_create_separator()); -#endif menu_insert(m, menu_create_normal("home",getenv("HOME"),"Home",_menu_action_navigate)); menu_insert(m, menu_create_normal(NULL,"/","File System",_menu_action_navigate)); menu_insert(m, menu_create_normal("up",NULL,"Up",_menu_action_up)); diff --git a/apps/glogin-provider.c b/apps/glogin-provider.c index b475290b..40ae3add 100644 --- a/apps/glogin-provider.c +++ b/apps/glogin-provider.c @@ -25,7 +25,6 @@ #include #include #include -#include #include #define TRACE_APP_NAME "glogin-provider" @@ -57,7 +56,7 @@ static int BOX_COLOR_G=0; static int BOX_COLOR_B=0; static int BOX_COLOR_A=127; static char * WALLPAPER = "/usr/share/wallpaper.jpg"; -static char * LOGO = "/usr/share/logo_login.bmp"; +static char * LOGO = "/usr/share/logo_login.png"; #define TEXTBOX_INTERIOR_LEFT 4 #define EXTRA_TEXT_OFFSET 15 @@ -261,7 +260,6 @@ int main (int argc, char ** argv) { TRACE("Loading logo..."); load_sprite(&logo, LOGO); - logo.alpha = ALPHA_EMBEDDED; TRACE("... done."); /* Generate surface for background */ @@ -295,7 +293,7 @@ int main (int argc, char ** argv) { TRACE("Loading wallpaper..."); { sprite_t * wallpaper = malloc(sizeof(sprite_t)); - load_sprite_jpg(wallpaper, WALLPAPER); + load_sprite(wallpaper, WALLPAPER); float x = (float)width / (float)wallpaper->width; float y = (float)height / (float)wallpaper->height; @@ -379,7 +377,7 @@ int main (int argc, char ** argv) { { struct utsname u; uname(&u); - char * os_name_ = "ToaruOS"; + char * os_name_ = "PonyOS"; snprintf(kernel_v, 512, "%s %s", os_name_, u.release); } diff --git a/apps/gunzip.c b/apps/gunzip.c new file mode 100644 index 00000000..fa6f38dd --- /dev/null +++ b/apps/gunzip.c @@ -0,0 +1,104 @@ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * This file is part of ToaruOS and is released under the terms + * of the NCSA / University of Illinois License - see LICENSE.md + * Copyright (C) 2020 K. Lange + * + * gunzip - decompress gzip-compressed payloads + */ +#include +#include +#include +#include +#include + +#include + +static int to_stdout = 0; +static int keep = 0; + +static uint8_t _get(struct inflate_context * ctx) { + return fgetc(ctx->input_priv); +} + +static void _write(struct inflate_context * ctx, unsigned int sym) { + fputc(sym, ctx->output_priv); +} + +static int usage(int argc, char * argv[]) { + fprintf(stderr, + "gunzip - decompress gzip-compressed payloads\n" + "\n" + "usage: %s [-c] name...\n" + "\n" + " -c \033[3mwrite to stdout; implies -k\033[0m\n" + " -k \033[3mkeep original files unchanged\033[0m\n" + "\n", argv[0]); + return 1; +} + +int main(int argc, char * argv[]) { + + int opt; + while ((opt = getopt(argc, argv, "?ck")) != -1) { + switch (opt) { + case 'c': + to_stdout = 1; + keep = 1; + break; + case 'k': + keep = 1; + break; + case '?': + return usage(argc, argv); + default: + fprintf(stderr, "%s: unrecognized option '%c'\n", argv[0], opt); + break; + } + } + + FILE * f; + if (optind >= argc || !strcmp(argv[optind],"-")) { + f = stdin; + } else { + f =fopen(argv[optind],"r"); + } + + if (!f) { + fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno)); + return 1; + } + + if (!to_stdout && strcmp(&argv[optind][strlen(argv[optind])-3],".gz")) { + fprintf(stderr, "%s: %s: Don't know how to interpret file name\n", argv[0], argv[optind]); + return 1; + } + + struct inflate_context ctx; + ctx.input_priv = f; + if (to_stdout) { + ctx.output_priv = stdout; + } else { + char * tmp = strdup(argv[optind]); + tmp[strlen(argv[optind])-3] = '\0'; + ctx.output_priv = fopen(tmp,"w"); + free(tmp); + } + ctx.get_input = _get; + ctx.write_output = _write; + ctx.ring = NULL; /* Use the global one */ + + if (gzip_decompress(&ctx)) { + return 1; + } + + if (!to_stdout) { + fclose(ctx.output_priv); + } + + if (!keep) { + unlink(argv[optind]); + } + + return 0; +} + diff --git a/apps/help-browser.c b/apps/help-browser.c index 2038ef4e..f045b146 100644 --- a/apps/help-browser.c +++ b/apps/help-browser.c @@ -1,7 +1,7 @@ /* vim: tabstop=4 shiftwidth=4 noexpandtab * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md - * Copyright (C) 2018 K. Lange + * Copyright (C) 2018-2020 K. Lange * * help-browser - Display documentation. * @@ -22,6 +22,7 @@ #include #define APPLICATION_TITLE "Help Browser" +#define HELP_DIR "/usr/share/help" static yutani_t * yctx; static yutani_window_t * main_window; @@ -32,19 +33,19 @@ static int application_running = 1; static gfx_context_t * contents = NULL; static sprite_t * contents_sprite = NULL; -static char * current_topic = "This is the ToaruOS Help Browser. No help document is currently loaded."; +static char * current_topic = NULL; /* Markup Renderer { */ #define BASE_X 0 #define BASE_Y 0 #define LINE_HEIGHT 20 +#define HEAD_HEIGHT 28 static gfx_context_t * nctx = NULL; static int cursor_y = 0; static int cursor_x = 0; static list_t * state = NULL; static int current_state = 0; -static int size = 16; struct Char { char c; /* TODO: unicode */ @@ -66,6 +67,13 @@ static int state_to_font(int current_state) { return SDF_FONT_THIN; } +static int current_size(void) { + if (current_state & (1 << 2)) { + return 24; + } + return 16; +} + static int buffer_width(list_t * buffer) { int out = 0; foreach(node, buffer) { @@ -73,7 +81,7 @@ static int buffer_width(list_t * buffer) { char tmp[2] = {c->c, '\0'}; - out += draw_sdf_string_width(tmp, size, state_to_font(c->state)); + out += draw_sdf_string_width(tmp, current_size(), state_to_font(c->state)); } return out; } @@ -84,7 +92,7 @@ static int draw_buffer(list_t * buffer) { node_t * node = list_dequeue(buffer); struct Char * c = node->value; char tmp[2] = { c->c, '\0' }; - x += draw_sdf_string(nctx, cursor_x + x, cursor_y, tmp, size, 0xFF000000, state_to_font(c->state)); + x += draw_sdf_string(nctx, cursor_x + x, cursor_y, tmp, current_size(), 0xFF000000, state_to_font(c->state)); free(c); free(node); } @@ -92,10 +100,18 @@ static int draw_buffer(list_t * buffer) { return x; } +static int current_line_height(void) { + if (current_state & (1 << 2)) { + return HEAD_HEIGHT; + } else { + return LINE_HEIGHT; + } +} + static void write_buffer(void) { if (buffer_width(buffer) + cursor_x > nctx->width) { cursor_x = BASE_X; - cursor_y += LINE_HEIGHT; + cursor_y += current_line_height(); } cursor_x += draw_buffer(buffer); } @@ -107,10 +123,13 @@ static int parser_open(struct markup_state * self, void * user, struct markup_ta } else if (!strcmp(tag->name, "i")) { list_insert(state, (void*)current_state); current_state |= (1 << 1); + } else if (!strcmp(tag->name, "h1")) { + list_insert(state, (void*)current_state); + current_state |= (1 << 2); } else if (!strcmp(tag->name, "br")) { write_buffer(); cursor_x = BASE_X; - cursor_y += LINE_HEIGHT; + cursor_y += current_line_height(); } markup_free_tag(tag); return 0; @@ -125,6 +144,13 @@ static int parser_close(struct markup_state * self, void * user, char * tag_name node_t * nstate = list_pop(state); current_state = (int)nstate->value; free(nstate); + } else if (!strcmp(tag_name, "h1")) { + write_buffer(); + cursor_x = BASE_X; + cursor_y += current_line_height(); + node_t * nstate = list_pop(state); + current_state = (int)nstate->value; + free(nstate); } return 0; } @@ -132,7 +158,7 @@ static int parser_close(struct markup_state * self, void * user, char * tag_name static int parser_data(struct markup_state * self, void * user, char * data) { char * c = data; while (*c) { - if (*c == ' ') { + if (*c == ' ' || *c == '\n') { if (buffer->length) { write_buffer(); } @@ -245,36 +271,30 @@ static void resize_finish(int w, int h) { static void navigate(const char * t) { - if (!strcmp(t,"file-browser.trt")) { - current_topic = - "File Browser
" - "The File Browser shows files. Double click to navigate through the file system."; - } else if (!strcmp(t,"terminal.trt")) { - current_topic = - "Terminal
" - "It's a terminal emulator. Supports a large set of xterm escapes, plus 24-bit color."; - } else if (!strcmp(t,"help_browser.trt")) { - current_topic = - "Help Browser
" - "A bit meta, reading about the Help Browser from within the Help Browser...
" - "This is an incomplete port of the original Python Help Browser, which was, effectively " - "a very bad web browser, built off of the expanding text label widget library."; - } else if (!strcmp(t,"package-manager.trt")) { - current_topic = - "Package Manager
" - "Install additional third-party software through packages from the ToaruOS Package Repository. " - "Packages can be installed by double-clicking. Dependencies will automatically be installed. " - "At this time it is not possible to remove packages."; - } else if (!strcmp(t,"0_index.trt")) { - current_topic = - "Welcome!
" - "Welcome to ToaruOS. This Help Browser is still a bit experimental."; - } else if (!strcmp(t,"special:contents")) { - current_topic = - "A list of topics should go here, but, alas..."; + if (current_topic) free(current_topic); + + char file_path[1024]; + + if (t[0] == '/') { + sprintf(file_path, "%s", t); + } else { + sprintf(file_path, "%s/%s", HELP_DIR, t); + } + + FILE * f = fopen(file_path, "r"); + + if (!f) { + current_topic = strdup("File not found."); } else { - current_topic = - "No help document exists for this topic."; + fseek(f, 0, SEEK_END); + size_t size = ftell(f); + fseek(f, 0, SEEK_SET); + + current_topic = malloc(size+1); + fread(current_topic, 1, size, f); + current_topic[size] = '\0'; + + fclose(f); } reinitialize_contents(); @@ -301,7 +321,7 @@ static void _menu_action_forward(struct MenuEntry * entry) { static void _menu_action_about(struct MenuEntry * entry) { /* Show About dialog */ char about_cmd[1024] = "\0"; - strcat(about_cmd, "about \"About Help Browser\" /usr/share/icons/48/help.bmp \"ToaruOS Help Browser\" \"(C) 2018 K. Lange\n-\nPart of ToaruOS, which is free software\nreleased under the NCSA/University of Illinois\nlicense.\n-\n%https://toaruos.org\n%https://github.com/klange/toaruos\" "); + strcat(about_cmd, "about \"About Help Browser\" /usr/share/icons/48/help.png \"PonyOS Help Browser\" \"(C) 2018-2020 K. Lange\n-\nPart of PonyOS, which is free software\nreleased under the NCSA/University of Illinois\nlicense.\n-\n%https://ponyos.org\n%https://github.com/klange/ponyos\" "); char coords[100]; sprintf(coords, "%d %d &", (int)main_window->x + (int)main_window->width / 2, (int)main_window->y + (int)main_window->height / 2); strcat(about_cmd, coords); @@ -309,6 +329,11 @@ static void _menu_action_about(struct MenuEntry * entry) { redraw_window(); } +static void redraw_window_callback(struct menu_bar * self) { + (void)self; + redraw_window(); +} + int main(int argc, char * argv[]) { yctx = yutani_init(); @@ -320,7 +345,7 @@ int main(int argc, char * argv[]) { yutani_window_advertise_icon(yctx, main_window, APPLICATION_TITLE, "help"); menu_bar.entries = menu_entries; - menu_bar.redraw_callback = redraw_window; + menu_bar.redraw_callback = redraw_window_callback; menu_bar.set = menu_set_create(); @@ -340,7 +365,7 @@ int main(int argc, char * argv[]) { menu_set_insert(menu_bar.set, "go", m); m = menu_create(); - menu_insert(m, menu_create_normal("help","help_browser.trt","Contents",_menu_action_navigate)); + menu_insert(m, menu_create_normal("help","help-browser.trt","Contents",_menu_action_navigate)); menu_insert(m, menu_create_separator()); menu_insert(m, menu_create_normal("star",NULL,"About " APPLICATION_TITLE,_menu_action_about)); menu_set_insert(menu_bar.set, "help", m); @@ -348,8 +373,7 @@ int main(int argc, char * argv[]) { if (argc > 1) { navigate(argv[1]); } else { - reinitialize_contents(); - redraw_window(); + navigate("0_index.trt"); } while (application_running) { diff --git a/apps/highlight-source.krk b/apps/highlight-source.krk new file mode 100755 index 00000000..77edb26a --- /dev/null +++ b/apps/highlight-source.krk @@ -0,0 +1,11 @@ +#!/bin/kuroko +import fileio, syntax.highlighter, kuroko + +let code +with fileio.open(kuroko.argv[-1]) as f: + code = f.read() + +let highlighter = syntax.highlighter.KurokoHighlighter(code) + +highlighter.highlight() +syntax.highlighter.toTerminal(highlighter.process()) diff --git a/apps/imgviewer.c b/apps/imgviewer.c index 6c1d54a7..0a251eb8 100644 --- a/apps/imgviewer.c +++ b/apps/imgviewer.c @@ -25,7 +25,6 @@ #include #include #include -#include /* Pointer to graphics memory */ static yutani_t * yctx; @@ -150,17 +149,11 @@ int main(int argc, char * argv[]) { decor_width = bounds.width; decor_height = bounds.height; - int status; - if (strstr(argv[optind],".jpg")) { - status = load_sprite_jpg(&img, argv[optind]); - } else { - status = load_sprite(&img, argv[optind]); - } + int status = load_sprite(&img, argv[optind]); if (status) { fprintf(stderr, "%s: failed to open image %s\n", argv[0], argv[optind]); return 1; } - img.alpha = ALPHA_EMBEDDED; width = img.width; height = img.height; diff --git a/apps/kbd-test.c b/apps/kbd-test.c new file mode 100644 index 00000000..5ff67b79 --- /dev/null +++ b/apps/kbd-test.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include + +static unsigned short * textmemptr = (unsigned short *)0xB8000; +static void placech(unsigned char c, int x, int y, int attr) { + unsigned short *where; + unsigned att = attr << 8; + where = textmemptr + (y * 80 + x); + *where = c | att; +} + +static void clear_screen(void) { + for (int y = 0; y < 24; ++y) { + for (int x = 0; x < 80; ++x) { + placech(' ', x, y, 0); /* Clear */ + } + } +} + +#define BUF_SIZE 4096 +static char keys[256] = {0}; + +static void redraw(void) { + int i = 0; + for (int c = 'a'; c <= 'z'; ++c, i += 2) { + placech(c, i, 0, keys[c] ? 0x2 : 0x7); + } +} + +static void print_scancode(unsigned int sc) { + char buf[10]; + sprintf(buf, "%d", sc); + + int i = 0; + for (char * c = buf; *c; ++c, ++i) { + placech(*c, i, 1, 0x7); + } + for (; i < 4; ++i) { + placech(' ', i, 1, 0x7); + } +} + +int main(int argc, char * argv[]) { + clear_screen(); + int kfd = open("/dev/kbd", O_RDONLY); + key_event_t event; + key_event_state_t kbd_state = {0}; + + while (1) { + unsigned char buf[BUF_SIZE]; + int r = read(kfd, buf, BUF_SIZE); + for (int i = 0; i < r; ++i) { + kbd_scancode(&kbd_state, buf[i], &event); + if (event.keycode >= 'a' && event.keycode < 'z') { + keys[event.keycode] = (event.action == KEY_ACTION_DOWN); + } + print_scancode(buf[i]); + } + redraw(); + + } + +} diff --git a/apps/kill.c b/apps/kill.c index 45c4d5bb..6600448b 100644 --- a/apps/kill.c +++ b/apps/kill.c @@ -94,7 +94,7 @@ int main(int argc, char * argv[]) { s++; } } else { - if (!isdigit(argv[1][1] < '0')) { + if (!isdigit(argv[1][1])) { struct sig_def * s = signals; while (s->name) { if (!strcmp(argv[1]+1,s->name)) { diff --git a/apps/krk_test_noise.krk b/apps/krk_test_noise.krk new file mode 100755 index 00000000..c4994531 --- /dev/null +++ b/apps/krk_test_noise.krk @@ -0,0 +1,47 @@ +#!/bin/kuroko +import os +if 'KUROKO_TEST_ENV' in os.environ: + return 0 + +from time import sleep +from fileio import open, stdin +from threading import Thread + +let d = {} +let stop = False + +for y in range(0x40): + for x in range(0x40): + d[(y,x)] = 0 + +class NoisePainter(Thread): + def run(self): + let myRando = open('/dev/urandom','rb') + while not stop: + let bytes = myRando.read(3) + let x = bytes[0] & 0x3F + let y = bytes[1] & 0x3F + d[(y,x)] = bytes[2] + +let painters = [NoisePainter() for i in range(5)] + +for painter in painters: + painter.start() + +def drawScreen(): + print("\[[H",end="") + for y in range(0x20): + for x in range(0x40): + let top = d[(y*2,x)] + let bottom = d[(y*2+1,x)] + print("\[[38","2",top,top,top,"48","2",bottom,bottom,bottom,sep=";",end="m▀") + print("\[[0m") + +for i in range(5): + drawScreen() + +stop = True +for painter in painters: + painter.join() + +drawScreen() diff --git a/apps/krk_yutani_test.krk b/apps/krk_yutani_test.krk new file mode 100644 index 00000000..fc841066 --- /dev/null +++ b/apps/krk_yutani_test.krk @@ -0,0 +1,77 @@ +#!/bin/kuroko +from _yutani import (color, Yutani, Window, Decorator, Message, + MenuBar, MenuList, MenuEntry, MenuEntrySeparator + ) + +let running = True +let y = Yutani() +let w = Window(640,480,title="Test Window",doublebuffer=True) + +w.move(200,200) + +let mb = MenuBar((("File",'file'),("Help",'help'))) +let _menu_File = MenuList() + +_menu_File.insert(MenuEntry("Test", lambda menu: print("hello, world"))) +_menu_File.insert(MenuEntrySeparator()) +_menu_File.insert(MenuEntry("Quit", lambda menu: (running = False))) + +mb.insert('file', _menu_File) +let _menu_Help = MenuList() +let _menu_Help_help = MenuEntry("Help",lambda menu: print("oh no!")) +_menu_Help.insert(_menu_Help_help) +mb.insert('help', _menu_Help) + +def drawWindow(): + w.fill(color(255,255,255)) + Decorator.render(w) + let bounds = Decorator.get_bounds(w) + mb.place(bounds['left_width'],bounds['top_height'],w.width-bounds['width'],w) + mb.render(w) + w.flip() + +drawWindow() + +mb.callback = lambda x: drawWindow() + +print(globals()) + +def handleMouseEvent(msg): + let decResponse = Decorator.handle_event(msg) + if decResponse == Decorator.DECOR_CLOSE: + w.close() + return True + else if decResponse == Decorator.DECOR_RIGHT: + Decorator.show_default_menu(w,w.x+msg.new_x,w.y+msg.new_y) + mb.mouse_event(w,msg) + +def finishResize(msg): + w.resize_accept(msg.width, msg.height) + w.reinit() + drawWindow() + w.resize_done() + w.flip() + +while running: + let msg = y.poll() + if y.menu_process_event(msg): + drawWindow() + if msg.type == Message.MSG_SESSION_END: + w.close() + break + else if msg.type == Message.MSG_KEY_EVENT: + print(msg.keycode) + else if msg.type == Message.MSG_WINDOW_FOCUS_CHANGE: + if msg.wid == w.wid: + print("focus changed") + w.set_focused(msg.focused) + drawWindow() + else if msg.type == Message.MSG_RESIZE_OFFER: + finishResize(msg) + else if msg.type == Message.MSG_WINDOW_MOUSE_EVENT: + if msg.wid == w.wid: + if handleMouseEvent(msg): break + else if msg.type == Message.MSG_WINDOW_CLOSE: + w.close() + break + diff --git a/apps/kuroko.c b/apps/kuroko.c new file mode 100644 index 00000000..0beb193a --- /dev/null +++ b/apps/kuroko.c @@ -0,0 +1,1149 @@ +/** + * Kuroko interpreter main executable. + * + * Reads lines from stdin with the `rline` library and executes them, + * or executes scripts from the argument list. + */ +#define _DEFAULT_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#ifdef __toaru__ +#include +#else +#ifndef NO_RLINE +#include "vendor/rline.h" +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PROMPT_MAIN ">>> " +#define PROMPT_BLOCK " > " + +#define CALLGRIND_TMP_FILE "/tmp/kuroko.callgrind.tmp" + +static int enableRline = 1; +static int exitRepl = 0; +static int pasteEnabled = 0; + +KRK_FUNC(exit,{ + FUNCTION_TAKES_NONE(); + exitRepl = 1; +}) + +KRK_FUNC(paste,{ + FUNCTION_TAKES_AT_MOST(1); + if (argc) { + CHECK_ARG(0,bool,int,enabled); + pasteEnabled = enabled; + } else { + pasteEnabled = !pasteEnabled; + } + fprintf(stderr, "Pasting is %s.\n", pasteEnabled ? "enabled" : "disabled"); +}) + +static int doRead(char * buf, size_t bufSize) { +#ifndef NO_RLINE + if (enableRline) + return rline(buf, bufSize); + else +#endif + return read(STDIN_FILENO, buf, bufSize); +} + +static KrkValue readLine(char * prompt, int promptWidth, char * syntaxHighlighter) { + struct StringBuilder sb = {0}; + +#ifndef NO_RLINE + if (enableRline) { + rline_exit_string = ""; + rline_exp_set_prompts(prompt, "", promptWidth, 0); + rline_exp_set_syntax(syntaxHighlighter); + rline_exp_set_tab_complete_func(NULL); + } else +#endif + { + fprintf(stdout, "%s", prompt); + fflush(stdout); + } + + /* Read a line of input using a method that we can guarantee will be + * interrupted by signal delivery. */ + while (1) { + char buf[4096]; + ssize_t bytesRead = doRead(buf, 4096); + if (krk_currentThread.flags & KRK_THREAD_SIGNALLED) goto _exit; + if (bytesRead < 0) { + krk_runtimeError(vm.exceptions->ioError, "%s", strerror(errno)); + goto _exit; + } else if (bytesRead == 0 && !sb.length) { + krk_runtimeError(vm.exceptions->baseException, "EOF"); + goto _exit; + } else { + pushStringBuilderStr(&sb, buf, bytesRead); + } + /* Was there a linefeed? Then we can exit. */ + if (sb.length && sb.bytes[sb.length-1] == '\n') { + sb.length--; + break; + } + } + + return finishStringBuilder(&sb); + +_exit: + discardStringBuilder(&sb); + return NONE_VAL(); +} + +/** + * @brief Read a line of input. + * + * In an interactive session, presents the configured prompt without + * a trailing linefeed. + */ +KRK_FUNC(input,{ + FUNCTION_TAKES_AT_MOST(1); + + char * prompt = ""; + int promptLength = 0; + char * syntaxHighlighter = NULL; + + if (argc) { + CHECK_ARG(0,str,KrkString*,_prompt); + prompt = _prompt->chars; + promptLength = _prompt->codesLength; + } + + if (hasKw) { + KrkValue promptwidth; + if (krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("promptwidth")), &promptwidth)) { + if (!IS_INTEGER(promptwidth)) return TYPE_ERROR(int,promptwidth); + promptLength = AS_INTEGER(promptwidth); + } + + KrkValue syntax; + if (krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("syntax")), &syntax)) { + if (!IS_STRING(syntax)) return TYPE_ERROR(str,syntax); + syntaxHighlighter = AS_CSTRING(syntax); + } + } + + return readLine(prompt, promptLength, syntaxHighlighter); +}) + +#ifndef NO_RLINE +/** + * Given an object, find a property with the same name as a scanner token. + * This can be either a field of an instance, or a method of the type of + * the of the object. If we can't find anything by that name, return None. + * + * We can probably also use valueGetProperty which does correct binding + * for native dynamic fields... + */ +static KrkValue findFromProperty(KrkValue current, KrkToken next) { + KrkValue value; + KrkValue member = OBJECT_VAL(krk_copyString(next.start, next.literalWidth)); + krk_push(member); + + if (IS_INSTANCE(current)) { + /* try fields */ + if (krk_tableGet(&AS_INSTANCE(current)->fields, member, &value)) goto _found; + if (krk_tableGet(&AS_INSTANCE(current)->_class->methods, member, &value)) goto _found; + } else { + /* try methods */ + KrkClass * _class = krk_getType(current); + if (krk_tableGet(&_class->methods, member, &value)) goto _found; + } + + krk_pop(); + return NONE_VAL(); + +_found: + krk_pop(); + return value; +} + +static void tab_complete_func(rline_context_t * c) { + /* Figure out where the cursor is and if we should be completing anything. */ + if (c->offset) { + /* Copy up to the cursor... */ + char * tmp = malloc(c->offset + 1); + memcpy(tmp, c->buffer, c->offset); + tmp[c->offset] = '\0'; + /* and pass it to the scanner... */ + krk_initScanner(tmp); + /* Logically, there can be at most (offset) tokens, plus some wiggle room. */ + KrkToken * space = malloc(sizeof(KrkToken) * (c->offset + 2)); + int count = 0; + do { + space[count++] = krk_scanToken(); + } while (space[count-1].type != TOKEN_EOF && space[count-1].type != TOKEN_ERROR); + + /* If count == 1, it was EOF or an error and we have nothing to complete. */ + if (count == 1) { + goto _cleanup; + } + + /* Otherwise we want to see if we're on an identifier or a dot. */ + int base = 2; + int n = base; + if (space[count-base].type == TOKEN_DOT) { + /* Dots we need to look back at the previous tokens for */ + n--; + base--; + } else if (space[count-base].type >= TOKEN_IDENTIFIER && space[count-base].type <= TOKEN_WITH) { + /* Something alphanumeric; only for the last element */ + } else { + /* Some other symbol */ + goto _cleanup; + } + + /* Work backwards to find the start of this chain of identifiers */ + while (n < count) { + if (space[count-n-1].type != TOKEN_DOT) break; + n++; + if (n == count) break; + if (space[count-n-1].type != TOKEN_IDENTIFIER) break; + n++; + } + + if (n <= count) { + /* Now work forwards, starting from the current globals. */ + KrkValue root = OBJECT_VAL(krk_currentThread.module); + int isGlobal = 1; + while (n > base) { + /* And look at the potential fields for instances/classes */ + KrkValue next = findFromProperty(root, space[count-n]); + if (IS_NONE(next)) { + /* If we hit None, we found something invalid (or literally hit a None + * object, but really the difference is minimal in this case: Nothing + * useful to tab complete from here. */ + goto _cleanup; + } + isGlobal = 0; + root = next; + n -= 2; /* To skip every other dot. */ + } + + /* Now figure out what we're completing - did we already have a partial symbol name? */ + int length = (space[count-base].type == TOKEN_DOT) ? 0 : (space[count-base].length); + isGlobal = isGlobal && (length != 0); + + /* Collect up to 256 of those that match */ + char * matches[256]; + int matchCount = 0; + + /* Take the last symbol name from the chain and get its member list from dir() */ + + for (;;) { + KrkValue dirList = krk_dirObject(1,(KrkValue[]){root},0); + krk_push(dirList); + if (!IS_INSTANCE(dirList)) { + fprintf(stderr,"\nInternal error while tab completting.\n"); + goto _cleanup; + } + + for (size_t i = 0; i < AS_LIST(dirList)->count; ++i) { + KrkString * s = AS_STRING(AS_LIST(dirList)->values[i]); + krk_push(OBJECT_VAL(s)); + KrkToken asToken = {.start = s->chars, .literalWidth = s->length}; + KrkValue thisValue = findFromProperty(root, asToken); + krk_push(thisValue); + if (IS_CLOSURE(thisValue) || IS_BOUND_METHOD(thisValue) || + (IS_NATIVE(thisValue) && !((KrkNative*)AS_OBJECT(thisValue))->flags & KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY)) { + size_t allocSize = s->length + 2; + char * tmp = malloc(allocSize); + size_t len = snprintf(tmp, allocSize, "%s(", s->chars); + s = krk_takeString(tmp, len); + krk_pop(); + krk_push(OBJECT_VAL(s)); + } + + /* If this symbol is shorter than the current submatch, skip it. */ + if (length && (int)s->length < length) continue; + + /* See if it's already in the matches */ + int found = 0; + for (int i = 0; i < matchCount; ++i) { + if (!strcmp(matches[i], s->chars)) { + found = 1; + break; + } + } + if (found) continue; + + if (!memcmp(s->chars, space[count-base].start, length)) { + matches[matchCount] = s->chars; + matchCount++; + if (matchCount == 255) goto _toomany; + } + } + + /* + * If the object we were scanning was the current module, + * then we should also throw the builtins into the ring. + */ + if (isGlobal && AS_OBJECT(root) == (KrkObj*)krk_currentThread.module) { + root = OBJECT_VAL(vm.builtins); + continue; + } else if (isGlobal && AS_OBJECT(root) == (KrkObj*)vm.builtins) { + extern char * syn_krk_keywords[]; + KrkInstance * fakeKeywordsObject = krk_newInstance(vm.baseClasses->objectClass); + root = OBJECT_VAL(fakeKeywordsObject); + krk_push(root); + for (char ** keyword = syn_krk_keywords; *keyword; keyword++) { + krk_attachNamedValue(&fakeKeywordsObject->fields, *keyword, NONE_VAL()); + } + continue; + } else { + break; + } + } +_toomany: + + /* Now we can do things with the matches. */ + if (matchCount == 1) { + /* If there was only one, just fill it. */ + rline_insert(c, matches[0] + length); + rline_place_cursor(); + } else if (matchCount) { + /* Otherwise, try to find a common substring among them... */ + int j = length; + while (1) { + char m = matches[0][j]; + if (!m) break; + int diff = 0; + for (int i = 1; i < matchCount; ++i) { + if (matches[i][j] != m) { + diff = 1; + break; + } + } + if (diff) break; + j++; + } + /* If no common sub string could be filled in, we print the list. */ + if (j == length) { + /* First find the maximum width of an entry */ + int maxWidth = 0; + for (int i = 0; i < matchCount; ++i) { + if ((int)strlen(matches[i]) > maxWidth) maxWidth = strlen(matches[i]); + } + /* Now how many can we fit in a screen */ + int colsPerLine = rline_terminal_width / (maxWidth + 2); /* +2 for the spaces */ + fprintf(stderr, "\n"); + int column = 0; + for (int i = 0; i < matchCount; ++i) { + fprintf(stderr, "%-*s ", maxWidth, matches[i]); + column += 1; + if (column >= colsPerLine) { + fprintf(stderr, "\n"); + column = 0; + } + } + if (column != 0) fprintf(stderr, "\n"); + } else { + /* If we do have a common sub string, fill in those characters. */ + for (int i = length; i < j; ++i) { + char tmp[2] = {matches[0][i], '\0'}; + rline_insert(c, tmp); + } + } + } + } +_cleanup: + free(tmp); + free(space); + krk_resetStack(); + return; + } +} +#endif + +#ifdef DEBUG +static char * lastDebugCommand = NULL; +static int debuggerHook(KrkCallFrame * frame) { + + /* File information */ + fprintf(stderr, "At offset 0x%04lx of function '%s' from '%s' on line %lu:\n", + (unsigned long)(frame->ip - frame->closure->function->chunk.code), + frame->closure->function->name->chars, + frame->closure->function->chunk.filename->chars, + (unsigned long)krk_lineNumber(&frame->closure->function->chunk, + (unsigned long)(frame->ip - frame->closure->function->chunk.code))); + + /* Opcode trace */ + krk_disassembleInstruction( + stderr, + frame->closure->function, + (size_t)(frame->ip - frame->closure->function->chunk.code)); + + krk_debug_dumpStack(stderr, frame); + + while (1) { + char buf[4096] = {0}; +#ifndef NO_RLINE + if (enableRline) { + rline_exit_string=""; + rline_exp_set_prompts("(dbg) ", "", 6, 0); + rline_exp_set_syntax("krk-dbg"); + rline_exp_set_tab_complete_func(NULL); + if (rline(buf, 4096) == 0) goto _dbgQuit; + } else { +#endif + fprintf(stderr, "(dbg) "); + fflush(stderr); + char * out = fgets(buf, 4096, stdin); + if (!out || !strlen(buf)) { + fprintf(stdout, "^D\n"); + goto _dbgQuit; + } +#ifndef NO_RLINE + } +#endif + + char * nl = strstr(buf,"\n"); + if (nl) *nl = '\0'; + + if (!strlen(buf)) { + if (lastDebugCommand) { + strcpy(buf, lastDebugCommand); + } else { + continue; + } + } else { +#ifndef NO_RLINE + if (enableRline) { + rline_history_insert(strdup(buf)); + rline_scroll = 0; + } +#endif + if (lastDebugCommand) free(lastDebugCommand); + lastDebugCommand = strdup(buf); + } + + /* Try to tokenize the first bit */ + char * arg = NULL; + char * sp = strstr(buf," "); + if (sp) { + *sp = '\0'; + arg = sp + 1; + } + /* Now check commands */ + if (!strcmp(buf, "c") || !strcmp(buf,"continue")) { + return KRK_DEBUGGER_CONTINUE; + } else if (!strcmp(buf, "s") || !strcmp(buf, "step")) { + return KRK_DEBUGGER_STEP; + } else if (!strcmp(buf, "abort")) { + return KRK_DEBUGGER_ABORT; + } else if (!strcmp(buf, "q") || !strcmp(buf, "quit")) { + return KRK_DEBUGGER_QUIT; + } else if (!strcmp(buf, "bt") || !strcmp(buf, "backtrace")) { + krk_debug_dumpTraceback(); + } else if (!strcmp(buf, "p") || !strcmp(buf, "print")) { + if (!arg) { + fprintf(stderr, "print requires an argument\n"); + } else { + size_t frameCount = krk_currentThread.frameCount; + /* Compile statement */ + KrkCodeObject * expression = krk_compile(arg,""); + if (expression) { + /* Make sure stepping is disabled first. */ + krk_debug_disableSingleStep(); + /* Turn our compiled expression into a callable. */ + krk_push(OBJECT_VAL(expression)); + krk_push(OBJECT_VAL(krk_newClosure(expression))); + /* Stack silliness, don't ask. */ + krk_push(NONE_VAL()); + krk_pop(); + /* Call the compiled expression with no args, but claim 2 method extras. */ + krk_push(krk_callSimple(krk_peek(0), 0, 2)); + fprintf(stderr, "\033[1;30m=> "); + krk_printValue(stderr, krk_peek(0)); + fprintf(stderr, "\033[0m\n"); + krk_pop(); + } + if (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) { + krk_dumpTraceback(); + krk_currentThread.flags &= ~(KRK_THREAD_HAS_EXCEPTION); + } + krk_currentThread.frameCount = frameCount; + } + } else if (!strcmp(buf, "break") || !strcmp(buf, "b")) { + char * filename = arg; + if (!filename) { + fprintf(stderr, "usage: break FILE LINE [type]\n"); + continue; + } + + char * lineno = strstr(filename, " "); + if (!lineno) { + fprintf(stderr, "usage: break FILE LINE [type]\n"); + continue; + } + + /* Advance whitespace */ + *lineno = '\0'; + lineno++; + + /* collect optional type */ + int flags = KRK_BREAKPOINT_NORMAL; + char * type = strstr(lineno, " "); + if (type) { + *type = '\0'; type++; + if (!strcmp(type, "repeat") || !strcmp(type,"r")) { + flags = KRK_BREAKPOINT_REPEAT; + } else if (!strcmp(type, "once") || !strcmp(type,"o")) { + flags = KRK_BREAKPOINT_ONCE; + } else { + fprintf(stderr, "Unrecognized breakpoint type: %s\n", type); + continue; + } + } + + int lineInt = atoi(lineno); + int result = krk_debug_addBreakpointFileLine(krk_copyString(filename, strlen(filename)), lineInt, flags); + + if (result == -1) { + fprintf(stderr, "Sorry, couldn't add breakpoint.\n"); + } else { + fprintf(stderr, "Breakpoint %d enabled.\n", result); + } + + } else if (!strcmp(buf, "i") || !strcmp(buf, "info")) { + if (!arg) { + fprintf(stderr, " info breakpoints - Show breakpoints.\n"); + continue; + } + + if (!strcmp(arg,"breakpoints")) { + KrkCodeObject * codeObject = NULL; + size_t offset = 0; + int flags = 0; + int enabled = 0; + int breakIndex = 0; + while (1) { + int result = krk_debug_examineBreakpoint(breakIndex, &codeObject, &offset, &flags, &enabled); + if (result == -1) break; + if (result == -2) continue; + + fprintf(stderr, "%-4d in %s+%d %s %s\n", + breakIndex, + codeObject->name->chars, + (int)offset, + flags == KRK_BREAKPOINT_NORMAL ? "normal": + flags == KRK_BREAKPOINT_REPEAT ? "repeat" : + flags == KRK_BREAKPOINT_ONCE ? "once" : "?", + enabled ? "enabled" : "disabled"); + + breakIndex++; + } + } else { + fprintf(stderr, "Unrecognized info object: %s\n", arg); + } + + } else if (!strcmp(buf, "e") || !strcmp(buf, "enable")) { + if (!arg) { + fprintf(stderr, "enable requires an argument\n"); + continue; + } + + int breakIndex = atoi(arg); + + if (krk_debug_enableBreakpoint(breakIndex)) { + fprintf(stderr, "Invalid breakpoint handle.\n"); + } else { + fprintf(stderr, "Breakpoint %d enabled.\n", breakIndex); + } + } else if (!strcmp(buf, "d") || !strcmp(buf, "disable")) { + if (!arg) { + fprintf(stderr, "disable requires an argument\n"); + continue; + } + + int breakIndex = atoi(arg); + + if (krk_debug_disableBreakpoint(breakIndex)) { + fprintf(stderr, "Invalid breakpoint handle.\n"); + } else { + fprintf(stderr, "Breakpoint %d disabled.\n", breakIndex); + } + } else if (!strcmp(buf, "r") || !strcmp(buf, "remove")) { + if (!arg) { + fprintf(stderr, "remove requires an argument\n"); + continue; + } + + int breakIndex = atoi(arg); + + if (krk_debug_removeBreakpoint(breakIndex)) { + fprintf(stderr, "Invalid breakpoint handle.\n"); + } else { + fprintf(stderr, "Breakpoint %d removed.\n", breakIndex); + } + } else if (!strcmp(buf, "help")) { + fprintf(stderr, + "Kuroko Interactive Debugger\n" + " c continue - Continue until the next breakpoint.\n" + " s step - Execute this instruction and return to the debugger.\n" + " bt backtrace - Print a backtrace.\n" + " q quit - Exit the interpreter.\n" + " abort - Abort the interpreter (may create a core dump).\n" + " b break ... - Set a breakpoint.\n" + " e enable N - Enable breakpoint 'N'.\n" + " d disable N - Disable breakpoint 'N'.\n" + " r remove N - Remove breakpoint 'N'.\n" + " i info ... - See information about breakpoints.\n" + "\n" + "Empty input lines will repeat the last command.\n" + ); + } else { + fprintf(stderr, "Unrecognized command: %s\n", buf); + } + + } + + return KRK_DEBUGGER_CONTINUE; +_dbgQuit: + return KRK_DEBUGGER_QUIT; +} +#endif + +static void handleSigint(int sigNum) { + /* Don't set the signal flag if the VM is not running */ + if (!krk_currentThread.frameCount) return; + krk_currentThread.flags |= KRK_THREAD_SIGNALLED; +} + +static void handleSigtrap(int sigNum) { + if (!krk_currentThread.frameCount) return; + krk_currentThread.flags |= KRK_THREAD_SINGLE_STEP; +} + +static void bindSignalHandlers(void) { + signal(SIGINT, handleSigint); + signal(SIGTRAP, handleSigtrap); +} + +static void findInterpreter(char * argv[]) { +#ifdef _WIN32 + vm.binpath = strdup(_pgmptr); +#else + /* Try asking /proc */ + char tmp[4096]; + char * binpath = realpath("/proc/self/exe", tmp); + if (!binpath || (access(binpath, X_OK) != 0)) { + if (strchr(argv[0], '/')) { + binpath = realpath(argv[0], tmp); + } else { + /* Search PATH for argv[0] */ + char * _path = strdup(getenv("PATH")); + char * path = _path; + while (path) { + char * next = strchr(path,':'); + if (next) *next++ = '\0'; + + snprintf(tmp, 4096, "%s/%s", path, argv[0]); + if (access(tmp, X_OK) == 0) { + binpath = tmp; + break; + } + path = next; + } + free(_path); + } + } + if (binpath) { + vm.binpath = strdup(binpath); + } /* Else, give up at this point and just don't attach it at all. */ +#endif +} + +static int runString(char * argv[], int flags, char * string) { + findInterpreter(argv); + krk_initVM(flags); + krk_startModule("__main__"); + krk_attachNamedValue(&krk_currentThread.module->fields,"__doc__", NONE_VAL()); + krk_interpret(string, ""); + krk_freeVM(); + return 0; +} + +static int compileFile(char * argv[], int flags, char * fileName) { + findInterpreter(argv); + krk_initVM(flags); + + /* Open the file. */ + FILE * f = fopen(fileName,"r"); + if (!f) { + fprintf(stderr, "%s: could not read file '%s': %s\n", argv[0], fileName, strerror(errno)); + return 1; + } + + /* Read it like we normally do... */ + fseek(f, 0, SEEK_END); + size_t size = ftell(f); + fseek(f, 0, SEEK_SET); + char * buf = malloc(size+1); + if (fread(buf, 1, size, f) != size) return 2; + fclose(f); + buf[size] = '\0'; + + /* Set up a module scope */ + krk_startModule("__main__"); + + /* Call the compiler directly. */ + KrkCodeObject * func = krk_compile(buf, fileName); + + /* See if there was an exception. */ + if (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) { + krk_dumpTraceback(); + } + + /* Free the source string */ + free(buf); + + /* Close out the compiler */ + krk_freeVM(); + + return func == NULL; +} + +#ifdef BUNDLE_LIBS +#define BUNDLED(name) do { \ + extern KrkValue krk_module_onload_ ## name (); \ + KrkValue moduleOut = krk_module_onload_ ## name (); \ + krk_attachNamedValue(&vm.modules, # name, moduleOut); \ + krk_attachNamedObject(&AS_INSTANCE(moduleOut)->fields, "__name__", (KrkObj*)krk_copyString(#name, sizeof(#name)-1)); \ + krk_attachNamedValue(&AS_INSTANCE(moduleOut)->fields, "__file__", NONE_VAL()); \ +} while (0) +#endif + +int main(int argc, char * argv[]) { +#ifdef _WIN32 + SetConsoleOutputCP(65001); + SetConsoleCP(65001); +#endif + char * runCmd = NULL; + int flags = 0; + int moduleAsMain = 0; + int inspectAfter = 0; + int opt; + while ((opt = getopt(argc, argv, "+:c:C:dgGim:rstTMSV-:")) != -1) { + switch (opt) { + case 'c': + runCmd = optarg; + goto _finishArgs; + case 'd': + /* Disassemble code blocks after compilation. */ + flags |= KRK_THREAD_ENABLE_DISASSEMBLY; + break; + case 'g': + /* Always garbage collect during an allocation. */ + flags |= KRK_GLOBAL_ENABLE_STRESS_GC; + break; + case 'G': + flags |= KRK_GLOBAL_REPORT_GC_COLLECTS; + break; + case 's': + /* Print debug information during compilation. */ + flags |= KRK_THREAD_ENABLE_SCAN_TRACING; + break; + case 'S': + flags |= KRK_THREAD_SINGLE_STEP; + break; + case 't': + /* Disassemble instructions as they are executed. */ + flags |= KRK_THREAD_ENABLE_TRACING; + break; + case 'T': { + flags |= KRK_GLOBAL_CALLGRIND; + vm.callgrindFile = fopen(CALLGRIND_TMP_FILE,"w"); + break; + } + case 'i': + inspectAfter = 1; + break; + case 'm': + moduleAsMain = 1; + optind--; /* to get us back to optarg */ + goto _finishArgs; + case 'r': + enableRline = 0; + break; + case 'M': + return runString(argv,0,"import kuroko; print(kuroko.module_paths)\n"); + case 'V': + return runString(argv,0,"import kuroko; print('Kuroko',kuroko.version)\n"); + case 'C': + return compileFile(argv,flags,optarg); + case ':': + fprintf(stderr, "%s: option '%c' requires an argument\n", argv[0], optopt); + return 1; + case '?': + if (optopt != '-') { + fprintf(stderr, "%s: unrecognized option '%c'\n", argv[0], optopt); + return 1; + } + optarg = argv[optind]+2; + /* fall through */ + case '-': + if (!strcmp(optarg,"version")) { + return runString(argv,0,"import kuroko; print('Kuroko',kuroko.version)\n"); + } else if (!strcmp(optarg,"help")) { +#ifndef KRK_NO_DOCUMENTATION + fprintf(stderr,"usage: %s [flags] [FILE...]\n" + "\n" + "Interpreter options:\n" + " -d Debug output from the bytecode compiler.\n" + " -g Collect garbage on every allocation.\n" + " -G Report GC collections.\n" + " -i Enter repl after a running -c, -m, or FILE.\n" + " -m mod Run a module as a script.\n" + " -r Disable complex line editing in the REPL.\n" + " -s Debug output from the scanner/tokenizer.\n" + " -t Disassemble instructions as they are exceuted.\n" + " -T Write call trace file.\n" + " -C file Compile 'file', but do not execute it.\n" + " -M Print the default module import paths.\n" + " -S Enable single-step debugging.\n" + " -V Print version information.\n" + "\n" + " --version Print version information.\n" + " --help Show this help text.\n" + "\n" + "If no files are provided, the interactive REPL will run.\n", + argv[0]); +#endif + return 0; + } else { + fprintf(stderr,"%s: unrecognized option '--%s'\n", + argv[0], optarg); + return 1; + } + } + } + +_finishArgs: + findInterpreter(argv); + krk_initVM(flags); + +#ifdef DEBUG + krk_debug_registerCallback(debuggerHook); +#endif + + /* Attach kuroko.argv - argv[0] will be set to an empty string for the repl */ + if (argc == optind) krk_push(OBJECT_VAL(krk_copyString("",0))); + for (int arg = optind; arg < argc; ++arg) { + krk_push(OBJECT_VAL(krk_copyString(argv[arg],strlen(argv[arg])))); + } + KrkValue argList = krk_list_of(argc - optind + (optind == argc), &krk_currentThread.stackTop[-(argc - optind + (optind == argc))],0); + krk_push(argList); + krk_attachNamedValue(&vm.system->fields, "argv", argList); + krk_pop(); + for (int arg = optind; arg < argc + (optind == argc); ++arg) krk_pop(); + + /* Bind interrupt signal */ + bindSignalHandlers(); + +#ifdef BUNDLE_LIBS + /* Add any other modules you want to include that are normally built as shared objects. */ + BUNDLED(math); + BUNDLED(socket); +#endif + + KrkValue result = INTEGER_VAL(0); + + /** + * Add general builtins that aren't part of the core VM. + * This is where we provide @c input in particular. + */ + KRK_DOC(BIND_FUNC(vm.builtins,input), "@brief Read a line of input.\n" + "@arguments [prompt], promptwidth=None, syntax=None\n\n" + "Read a line of input from @c stdin. If the @c rline library is available, " + "it will be used to gather input. Input reading stops on end-of file or when " + "a read ends with a line feed, which will be removed from the returned string. " + "If a prompt is provided, it will be printed without a line feed before requesting " + "input. If @c rline is available, the prompt will be passed to the library as the " + "left-hand prompt string. If not provided, @p promptwidth will default to the width " + "of @p prompt in codepoints; if you are providing a prompt with escape characters or " + "characters with multi-column East-Asian Character Width be sure to pass a value " + "for @p promptwidth that reflects the display width of your prompt. " + "If provided, @p syntax specifies the name of an @c rline syntax module to " + "provide color highlighting of the input line."); + + if (moduleAsMain) { + krk_push(OBJECT_VAL(krk_copyString("__main__",8))); + int out = !krk_importModule( + AS_STRING(AS_LIST(argList)->values[0]), + AS_STRING(krk_peek(0))); + if (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) { + krk_dumpTraceback(); + krk_resetStack(); + } + if (!inspectAfter) return out; + if (IS_INSTANCE(krk_peek(0))) { + krk_currentThread.module = AS_INSTANCE(krk_peek(0)); + } + } else if (optind != argc) { + krk_startModule("__main__"); + result = krk_runfile(argv[optind],argv[optind]); + if (IS_NONE(result) && krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) result = INTEGER_VAL(1); + } + + if (!krk_currentThread.module) { + /* The repl runs in the context of a top-level module so each input + * line can share a globals state with the others. */ + krk_startModule("__main__"); + krk_attachNamedValue(&krk_currentThread.module->fields,"__doc__", NONE_VAL()); + } + + if (runCmd) { + result = krk_interpret(runCmd, ""); + } + + if ((!moduleAsMain && !runCmd && optind == argc) || inspectAfter) { + /* Add builtins for the repl, but hide them from the globals() list. */ + KRK_DOC(BIND_FUNC(vm.builtins,exit), "@brief Exit the interactive repl.\n\n" + "Only available from the interactive interpreter; exits the repl."); + KRK_DOC(BIND_FUNC(vm.builtins,paste), "@brief Toggle paste mode.\n" + "@arguments enabled=None\n\n" + "Toggles paste-safe mode, disabling automatic indentation in the repl. " + "If @p enabled is specified, the mode can be directly specified, otherwise " + "it will be set to the opposite of the current mode. The new mode will be " + "printed to stderr."); + + /** + * Python stores version info in a built-in module called `sys`. + * We are not Python, we'll use `sys` to pretend to be Python + * in emulation mode, so we use a different module to store + * this sort of thing: kuroko + * + * This module won't be imported by default, but it's still in + * the modules list, so we can look for it there. + */ + KrkValue systemModule; + if (krk_tableGet(&vm.modules, OBJECT_VAL(krk_copyString("kuroko",6)), &systemModule)) { + KrkValue version, buildenv, builddate; + krk_tableGet(&AS_INSTANCE(systemModule)->fields, OBJECT_VAL(krk_copyString("version",7)), &version); + krk_tableGet(&AS_INSTANCE(systemModule)->fields, OBJECT_VAL(krk_copyString("buildenv",8)), &buildenv); + krk_tableGet(&AS_INSTANCE(systemModule)->fields, OBJECT_VAL(krk_copyString("builddate",9)), &builddate); + + fprintf(stdout, "Kuroko %s (%s) with %s\n", + AS_CSTRING(version), AS_CSTRING(builddate), AS_CSTRING(buildenv)); + } + + fprintf(stdout, "Type `help` for guidance, `paste()` to toggle automatic indentation, `license` for copyright information.\n"); + + while (!exitRepl) { + size_t lineCapacity = 8; + size_t lineCount = 0; + char ** lines = ALLOCATE(char *, lineCapacity); + size_t totalData = 0; + int valid = 1; + char * allData = NULL; + int inBlock = 0; + int blockWidth = 0; + +#ifndef NO_RLINE + /* Main prompt is >>> like in Python */ + rline_exp_set_prompts(PROMPT_MAIN, "", 4, 0); + /* Set ^D to send EOF */ + rline_exit_string=""; + /* Enable syntax highlight for Kuroko */ + rline_exp_set_syntax("krk"); + /* Bind a callback for \t */ + rline_exp_set_tab_complete_func(tab_complete_func); +#endif + + while (1) { + /* This would be a nice place for line editing */ + char buf[4096] = {0}; + +#ifndef NO_RLINE + if (inBlock) { + /* When entering multiple lines, the additional lines + * will show a single > (and keep the left side aligned) */ + rline_exp_set_prompts(PROMPT_BLOCK, "", 4, 0); + /* Also add indentation as necessary */ + if (!pasteEnabled) { + rline_preload = malloc(blockWidth + 1); + for (int i = 0; i < blockWidth; ++i) { + rline_preload[i] = ' '; + } + rline_preload[blockWidth] = '\0'; + } + } + + if (!enableRline) { +#else + if (1) { +#endif + fprintf(stdout, "%s", inBlock ? PROMPT_BLOCK : PROMPT_MAIN); + fflush(stdout); + } + +#ifndef NO_RLINE + rline_scroll = 0; + if (enableRline) { + if (rline(buf, 4096) == 0) { + valid = 0; + exitRepl = 1; + break; + } + } else { +#endif + char * out = fgets(buf, 4096, stdin); + if (!out || !strlen(buf)) { + fprintf(stdout, "^D\n"); + valid = 0; + exitRepl = 1; + break; + } +#ifndef NO_RLINE + } +#endif + + if (buf[strlen(buf)-1] != '\n') { + /* rline shouldn't allow this as it doesn't accept ^D to submit input + * unless the line is empty, but just in case... */ + fprintf(stderr, "Expected end of line in repl input. Did you ^D early?\n"); + valid = 0; + break; + } + + if (lineCapacity < lineCount + 1) { + /* If we need more space, grow as needed... */ + size_t old = lineCapacity; + lineCapacity = GROW_CAPACITY(old); + lines = GROW_ARRAY(char *,lines,old,lineCapacity); + } + + int i = lineCount++; + lines[i] = strdup(buf); + + size_t lineLength = strlen(lines[i]); + totalData += lineLength; + + /* Figure out indentation */ + int isSpaces = 1; + int countSpaces = 0; + for (size_t j = 0; j < lineLength; ++j) { + if (lines[i][j] != ' ' && lines[i][j] != '\n') { + isSpaces = 0; + break; + } + countSpaces += 1; + } + + /* Naively detect the start of a new block so we can + * continue to accept input. Our compiler isn't really + * set up to let us compile "on the fly" so we can't just + * run lines through it and see if it wants more... */ + if (lineLength > 1 && lines[i][lineLength-2] == ':') { + inBlock = 1; + blockWidth = countSpaces + 4; + continue; + } else if (lineLength > 1 && lines[i][lineLength-2] == '\\') { + inBlock = 1; + continue; + } else if (inBlock && lineLength != 1) { + if (isSpaces) { + free(lines[i]); + totalData -= lineLength; + lineCount--; + break; + } + blockWidth = countSpaces; + continue; + } else if (lineLength > 1 && lines[i][countSpaces] == '@') { + inBlock = 1; + blockWidth = countSpaces; + continue; + } + + /* Ignore blank lines. */ + if (isSpaces && !i) valid = 0; + + /* If we're not in a block, or have entered a blank line, + * we can stop reading new lines and jump to execution. */ + break; + } + + if (valid) { + allData = malloc(totalData + 1); + allData[0] = '\0'; + } + + for (size_t i = 0; i < lineCount; ++i) { + if (valid) strcat(allData, lines[i]); +#ifndef NO_RLINE + if (enableRline) { + rline_history_insert(strdup(lines[i])); + rline_scroll = 0; + } +#endif + free(lines[i]); + } + FREE_ARRAY(char *, lines, lineCapacity); + + if (valid) { + KrkValue result = krk_interpret(allData, ""); + if (!IS_NONE(result)) { + KrkClass * type = krk_getType(result); + const char * formatStr = " \033[1;30m=> %s\033[0m\n"; + if (type->_reprer) { + krk_push(result); + result = krk_callSimple(OBJECT_VAL(type->_reprer), 1, 0); + } else if (type->_tostr) { + krk_push(result); + result = krk_callSimple(OBJECT_VAL(type->_tostr), 1, 0); + } + if (!IS_STRING(result)) { + fprintf(stdout, " \033[1;31m=> Unable to produce representation for value.\033[0m\n"); + } else { + fprintf(stdout, formatStr, AS_CSTRING(result)); + } + } + krk_resetStack(); + free(allData); + } + + (void)blockWidth; + } + } + + if (vm.globalFlags & KRK_GLOBAL_CALLGRIND) { + fclose(vm.callgrindFile); + vm.globalFlags &= ~(KRK_GLOBAL_CALLGRIND); + + krk_resetStack(); + krk_startModule(""); + krk_attachNamedObject(&krk_currentThread.module->fields, "filename", (KrkObj*)S(CALLGRIND_TMP_FILE)); + krk_interpret( + "from callgrind import processFile\n" + "import kuroko\n" + "import os\n" + "processFile(filename, os.getpid(), ' '.join(kuroko.argv))",""); + } + + krk_freeVM(); + + if (IS_INTEGER(result)) return AS_INTEGER(result); + + return 0; +} diff --git a/apps/lspci.c b/apps/lspci.c index b1643046..c27731a1 100644 --- a/apps/lspci.c +++ b/apps/lspci.c @@ -31,6 +31,8 @@ struct device_class { {0x0604, "PCI bridge"}, {0x0680, "Bridge"}, {0x0880, "System peripheral"}, + {0x0c03, "USB controller"}, + {0x0c05, "SMBus controller"}, }; struct { diff --git a/apps/msk.c b/apps/msk.c index dcde7b32..b4369c86 100644 --- a/apps/msk.c +++ b/apps/msk.c @@ -361,10 +361,9 @@ static int install_package(char * pkg) { } char cmd[1024]; - sprintf(cmd, "cd %s; ungz %s /tmp/%s.tar; tar -xf /tmp/%s.tar; rm /tmp/%s.tar", + sprintf(cmd, "cd %s; tar -xzf %s", confreader_get(msk_manifest, pkg, "destination"), - confreader_get(msk_manifest, pkg, "source"), - pkg, pkg, pkg); + confreader_get(msk_manifest, pkg, "source")); int status; if ((status = system(cmd))) { diff --git a/apps/package-manager.c b/apps/package-manager.c index 1c0912a7..d1b5b6b3 100644 --- a/apps/package-manager.c +++ b/apps/package-manager.c @@ -341,7 +341,7 @@ static void install_package(struct Package * package) { static void _menu_action_about(struct MenuEntry * entry) { /* Show About dialog */ char about_cmd[1024] = "\0"; - strcat(about_cmd, "about \"About " APPLICATION_TITLE "\" /usr/share/icons/48/package.bmp \"ToaruOS " APPLICATION_TITLE "\" \"(C) 2018 K. Lange\n-\nPart of ToaruOS, which is free software\nreleased under the NCSA/University of Illinois\nlicense.\n-\n%https://toaruos.org\n%https://github.com/klange/toaruos\" "); + strcat(about_cmd, "about \"About " APPLICATION_TITLE "\" /usr/share/icons/48/package.png \"PonyOS " APPLICATION_TITLE "\" \"(C) 2018 K. Lange\n-\nPart of PonyOS, which is free software\nreleased under the NCSA/University of Illinois\nlicense.\n-\n%https://ponyos.org\n%https://github.com/klange/ponyos\" "); char coords[100]; sprintf(coords, "%d %d &", (int)main_window->x + (int)main_window->width / 2, (int)main_window->y + (int)main_window->height / 2); strcat(about_cmd, coords); @@ -433,13 +433,18 @@ static void arrow_select(int y) { redraw_window(); } +static void redraw_window_callback(struct menu_bar * self) { + (void)self; + redraw_window(); +} + int main(int argc, char * argv[]) { if (geteuid() != 0) { char * args[] = { "showdialog", "Package Manager", - "/usr/share/icons/48/package.bmp", + "/usr/share/icons/48/package.png", "Only root can manage packages.", NULL, }; @@ -455,7 +460,7 @@ int main(int argc, char * argv[]) { yutani_window_advertise_icon(yctx, main_window, APPLICATION_TITLE, "package"); menu_bar.entries = menu_entries; - menu_bar.redraw_callback = redraw_window; + menu_bar.redraw_callback = redraw_window_callback; menu_bar.set = menu_set_create(); diff --git a/apps/panel.c b/apps/panel.c index 24ac3d7e..057962d7 100644 --- a/apps/panel.c +++ b/apps/panel.c @@ -310,7 +310,7 @@ static void update_weather_status(void) { if (widgets_weather_enabled) { widgets_weather_enabled = 0; /* Unshow */ - widgets_width -= WIDGET_WIDTH; + widgets_width -= 2*WIDGET_WIDTH; } return; } @@ -318,7 +318,7 @@ static void update_weather_status(void) { weather_status_valid = 1; if (!widgets_weather_enabled) { widgets_weather_enabled = 1; - widgets_width += WIDGET_WIDTH; + widgets_width += 2*WIDGET_WIDTH; } if (weather_title_str) free(weather_title_str); @@ -362,9 +362,8 @@ static void update_weather_status(void) { if (!hashmap_has(weather_icons, icon)) { sprite_t * tmp = malloc(sizeof(sprite_t)); char path[512]; - sprintf(path,"/usr/share/icons/weather/%s.bmp", icon); + sprintf(path,"/usr/share/icons/weather/%s.png", icon); load_sprite(tmp, path); - tmp->alpha = ALPHA_FORCE_SLOW_EMBEDDED; hashmap_set(weather_icons, icon, tmp); } @@ -825,7 +824,7 @@ static void launch_application_menu(struct MenuEntry * self) { struct MenuEntry_Normal * _self = (void *)self; if (!strcmp((char *)_self->action,"log-out")) { - if (system("showdialog \"Log Out\" /usr/share/icons/48/exit.bmp \"Are you sure you want to log out?\"") == 0) { + if (system("showdialog \"Log Out\" /usr/share/icons/48/exit.png \"Are you sure you want to log out?\"") == 0) { yutani_session_end(yctx); _continue = 0; } @@ -1366,8 +1365,7 @@ struct MenuEntry * menu_create_clock(void) { if (!watchface) { watchface = malloc(sizeof(sprite_t)); - load_sprite(watchface, "/usr/share/icons/watchface.bmp"); - watchface->alpha = ALPHA_EMBEDDED; + load_sprite(watchface, "/usr/share/icons/watchface.png"); } out->_type = -1; /* Special */ @@ -1524,10 +1522,8 @@ int main (int argc, char ** argv) { sprite_panel = malloc(sizeof(sprite_t)); sprite_logout = malloc(sizeof(sprite_t)); - load_sprite(sprite_panel, "/usr/share/panel.bmp"); - sprite_panel->alpha = ALPHA_EMBEDDED; - load_sprite(sprite_logout, "/usr/share/icons/panel-shutdown.bmp"); - sprite_logout->alpha = ALPHA_FORCE_SLOW_EMBEDDED; + load_sprite(sprite_panel, "/usr/share/panel.png"); + load_sprite(sprite_logout, "/usr/share/icons/panel-shutdown.png"); struct stat stat_tmp; if (!stat("/dev/dsp",&stat_tmp)) { @@ -1537,14 +1533,10 @@ int main (int argc, char ** argv) { sprite_volume_low = malloc(sizeof(sprite_t)); sprite_volume_med = malloc(sizeof(sprite_t)); sprite_volume_high = malloc(sizeof(sprite_t)); - load_sprite(sprite_volume_mute, "/usr/share/icons/24/volume-mute.bmp"); - sprite_volume_mute->alpha = ALPHA_FORCE_SLOW_EMBEDDED; - load_sprite(sprite_volume_low, "/usr/share/icons/24/volume-low.bmp"); - sprite_volume_low->alpha = ALPHA_FORCE_SLOW_EMBEDDED; - load_sprite(sprite_volume_med, "/usr/share/icons/24/volume-medium.bmp"); - sprite_volume_med->alpha = ALPHA_FORCE_SLOW_EMBEDDED; - load_sprite(sprite_volume_high, "/usr/share/icons/24/volume-full.bmp"); - sprite_volume_high->alpha = ALPHA_FORCE_SLOW_EMBEDDED; + load_sprite(sprite_volume_mute, "/usr/share/icons/24/volume-mute.png"); + load_sprite(sprite_volume_low, "/usr/share/icons/24/volume-low.png"); + load_sprite(sprite_volume_med, "/usr/share/icons/24/volume-medium.png"); + load_sprite(sprite_volume_high, "/usr/share/icons/24/volume-full.png"); /* XXX store current volume */ } @@ -1552,11 +1544,9 @@ int main (int argc, char ** argv) { widgets_network_enabled = 1; widgets_width += WIDGET_WIDTH; sprite_net_active = malloc(sizeof(sprite_t)); - load_sprite(sprite_net_active, "/usr/share/icons/24/net-active.bmp"); - sprite_net_active->alpha = ALPHA_FORCE_SLOW_EMBEDDED; + load_sprite(sprite_net_active, "/usr/share/icons/24/net-active.png"); sprite_net_disabled = malloc(sizeof(sprite_t)); - load_sprite(sprite_net_disabled, "/usr/share/icons/24/net-disconnected.bmp"); - sprite_net_disabled->alpha = ALPHA_FORCE_SLOW_EMBEDDED; + load_sprite(sprite_net_disabled, "/usr/share/icons/24/net-disconnected.png"); } /* TODO Probably should use the app launch shortcut */ diff --git a/apps/pong.c b/apps/pong.c index 8b83546f..03e1a6ed 100644 --- a/apps/pong.c +++ b/apps/pong.c @@ -232,12 +232,9 @@ int main (int argc, char ** argv) { ball.vel_x = -10.0; fprintf(stderr, "Loading sprites...\n"); - load_sprite(&left.sprite, GAME_PATH "/paddle-red.bmp"); - left.sprite.alpha = ALPHA_EMBEDDED; - load_sprite(&right.sprite,GAME_PATH "/paddle-blue.bmp"); - right.sprite.alpha = ALPHA_EMBEDDED; - load_sprite(&ball.sprite, GAME_PATH "/ball.bmp"); - ball.sprite.alpha = ALPHA_EMBEDDED; + load_sprite(&left.sprite, GAME_PATH "/paddle-red.png"); + load_sprite(&right.sprite,GAME_PATH "/paddle-blue.png"); + load_sprite(&ball.sprite, GAME_PATH "/ball.png"); redraw(); update_left(); diff --git a/apps/sh.c b/apps/sh.c index 88e56b96..cdc0963b 100644 --- a/apps/sh.c +++ b/apps/sh.c @@ -38,7 +38,6 @@ #include #include #include -#include #ifndef environ extern char **environ; @@ -58,6 +57,7 @@ int SHELL_COMMANDS = 64; char ** shell_commands; /* Command names */ shell_command_t * shell_pointers; /* Command functions */ char ** shell_descript; /* Command descriptions */ +FILE * shell_stderr; /* specifically for `time` */ /* This is the number of actual commands installed */ int shell_commands_len = 0; @@ -66,7 +66,6 @@ int shell_interactive = 1; int last_ret = 0; char ** shell_argv = NULL; int shell_argc = 0; -int experimental_rline = 1; static int current_line = 0; static char * current_file = NULL; @@ -363,26 +362,6 @@ void sig_break_loop(int sig) { } } -void redraw_prompt_func(rline_context_t * context) { - draw_prompt(); -} - -void draw_prompt_c() { - char * ps2 = getenv("PS2"); - if (ps2) { - char buf[1024]; - int display_width; - print_extended_ps(ps2, buf, &display_width); - fprintf(stdout, "%s", buf); - } else { - printf("> "); - } - fflush(stdout); -} -void redraw_prompt_func_c(rline_context_t * context) { - draw_prompt_c(); -} - void tab_complete_func(rline_context_t * c) { char * dup = malloc(LINE_LEN); @@ -441,6 +420,11 @@ void tab_complete_func(rline_context_t * c) { command_adj += 1; } + if (command_adj < argc && (!strcmp(argv[command_adj], "time"))) { + cursor_adj -= 1; + command_adj += 1; + } + /* sudo should shift commands */ if (command_adj < argc && (!strcmp(argv[command_adj], "sudo") || !strcmp(argv[command_adj], "gsudo"))) { cursor_adj -= 1; @@ -693,48 +677,30 @@ void add_environment(list_t * env) { } int read_entry(char * buffer) { - if (experimental_rline) { - char lprompt[1024], rprompt[1024]; - int lwidth, rwidth; - - char * ps1 = getenv("PS1_LEFT"); - print_extended_ps(ps1 ? ps1 : FALLBACK_PS1, lprompt, &lwidth); - - char * ps1r = getenv("PS1_RIGHT"); - print_extended_ps(ps1r ? ps1r : "", rprompt, &rwidth); - - rline_exit_string="exit"; - rline_exp_set_syntax("esh"); - rline_exp_set_prompts(lprompt, rprompt, lwidth, rwidth); - rline_exp_set_shell_commands(shell_commands, shell_commands_len); - rline_exp_set_tab_complete_func(tab_complete_func); - return rline_experimental(buffer, LINE_LEN); - } else { - rline_callbacks_t callbacks = { - tab_complete_func, redraw_prompt_func, NULL, - NULL, NULL, NULL, NULL, NULL - }; - draw_prompt(); - return rline((char *)buffer, LINE_LEN, &callbacks); - } + char lprompt[1024], rprompt[1024]; + int lwidth, rwidth; + + char * ps1 = getenv("PS1_LEFT"); + print_extended_ps(ps1 ? ps1 : FALLBACK_PS1, lprompt, &lwidth); + + char * ps1r = getenv("PS1_RIGHT"); + print_extended_ps(ps1r ? ps1r : "", rprompt, &rwidth); + + rline_exit_string="exit"; + rline_exp_set_syntax("esh"); + rline_exp_set_prompts(lprompt, rprompt, lwidth, rwidth); + rline_exp_set_shell_commands(shell_commands, shell_commands_len); + rline_exp_set_tab_complete_func(tab_complete_func); + return rline(buffer, LINE_LEN); } int read_entry_continued(char * buffer) { - if (experimental_rline) { - rline_exit_string="exit"; - rline_exp_set_syntax("esh"); - rline_exp_set_prompts("> ", "", 2, 0); - rline_exp_set_shell_commands(shell_commands, shell_commands_len); - rline_exp_set_tab_complete_func(tab_complete_func); - return rline_experimental(buffer, LINE_LEN); - } else { - rline_callbacks_t callbacks = { - tab_complete_func, redraw_prompt_func_c, NULL, - NULL, NULL, NULL, NULL, NULL - }; - draw_prompt_c(); - return rline((char *)buffer, LINE_LEN, &callbacks); - } + rline_exit_string="exit"; + rline_exp_set_syntax("esh"); + rline_exp_set_prompts("> ", "", 2, 0); + rline_exp_set_shell_commands(shell_commands, shell_commands_len); + rline_exp_set_tab_complete_func(tab_complete_func); + return rline(buffer, LINE_LEN); } int variable_char(uint8_t c) { @@ -1702,13 +1668,13 @@ int main(int argc, char ** argv) { install_commands(); + int err = dup(STDERR_FILENO); + shell_stderr = fdopen(err, "w"); + if (argc > 1) { int c; - while ((c = getopt(argc, argv, "Rc:v?")) != -1) { + while ((c = getopt(argc, argv, "c:v?")) != -1) { switch (c) { - case 'R': - experimental_rline = 0; - break; case 'c': shell_interactive = 0; { @@ -2287,6 +2253,45 @@ uint32_t shell_cmd_rehash(int argc, char * argv[]) { return 0; } +uint32_t shell_cmd_time(int argc, char * argv[]) { + int pid, ret_code = 0; + struct timeval start, end; + gettimeofday(&start, NULL); + + if (argc > 1) { + pid_t child_pid = fork(); + if (!child_pid) { + set_pgid(0); + set_pgrp(getpid()); + is_subshell = 1; + run_cmd(&argv[1]); + } + + do { + pid = waitpid(child_pid, &ret_code, 0); + } while (pid != -1 || (pid == -1 && errno != ECHILD)); + + reset_pgrp(); + handle_status(ret_code); + } + + gettimeofday(&end, NULL); + + time_t sec_diff = end.tv_sec - start.tv_sec; + suseconds_t usec_diff = end.tv_usec - start.tv_usec; + if (end.tv_usec < start.tv_usec) { + sec_diff -= 1; + usec_diff = (1000000 + end.tv_usec) - start.tv_usec; + } + + int minutes = sec_diff / 60; + sec_diff = sec_diff % 60; + + fprintf(shell_stderr, "\nreal\t%dm%d.%.03ds\n", minutes, (int)sec_diff, (int)(usec_diff / 1000)); + + return WEXITSTATUS(ret_code); +} + void install_commands() { shell_commands = malloc(sizeof(char *) * SHELL_COMMANDS); shell_pointers = malloc(sizeof(shell_command_t) * SHELL_COMMANDS); @@ -2313,4 +2318,5 @@ void install_commands() { shell_install_command("jobs", shell_cmd_jobs, "list stopped jobs"); shell_install_command("bg", shell_cmd_bg, "restart suspended job in the background"); shell_install_command("rehash", shell_cmd_rehash, "reset shell command memory"); + shell_install_command("time", shell_cmd_time, "time a command"); } diff --git a/apps/showdialog.c b/apps/showdialog.c index 32565da5..e9acd6ba 100644 --- a/apps/showdialog.c +++ b/apps/showdialog.c @@ -74,7 +74,7 @@ static void redraw(void) { static void init_default(void) { title_str = "Dialog Prompt"; - icon_path = "/usr/share/icons/48/folder.bmp"; + icon_path = "/usr/share/icons/48/folder.png"; copyright_str[0] = "This is a demonstration of a dialog box."; copyright_str[1] = "You can press \"Okay\" or \"Cancel\" or close the window."; @@ -180,7 +180,6 @@ int main(int argc, char * argv[]) { ctx = init_graphics_yutani_double_buffer(window); setup_buttons(); load_sprite(&logo, icon_path); - logo.alpha = ALPHA_EMBEDDED; redraw(); struct TTKButton * _down_button = NULL; diff --git a/apps/sleep.c b/apps/sleep.c index 383b3cd6..d4f64737 100644 --- a/apps/sleep.c +++ b/apps/sleep.c @@ -13,6 +13,11 @@ int main(int argc, char ** argv) { int ret = 0; + if (argc < 2) { + fprintf(stderr, "%s: missing operand\n", argv[0]); + return 1; + } + char * arg = strdup(argv[1]); float time = atof(arg); diff --git a/apps/snow.c b/apps/snow.c index caa5a3f9..4be7665e 100644 --- a/apps/snow.c +++ b/apps/snow.c @@ -110,9 +110,6 @@ int main (int argc, char ** argv) { } load_sprite(&snowflake, "/usr/share/snowflake.bmp"); - snowflake.alpha = ALPHA_EMBEDDED; - snowflake.masks = NULL; - snowflake.blank = 0; wina = yutani_window_create(yctx, 100, 100); if (argc < 2 || strcmp(argv[1],"--no-ad")) { diff --git a/apps/tar.c b/apps/tar.c index 33d9b544..a0c2e93d 100644 --- a/apps/tar.c +++ b/apps/tar.c @@ -19,7 +19,7 @@ #include #include -#include +#include struct ustar { char filename[100]; @@ -44,11 +44,11 @@ struct ustar { char dev_minor[8]; char prefix[155]; + char padding[12]; }; -static struct ustar * file_from_offset(FILE * f, size_t offset) { +static struct ustar * extract_file(FILE * f) { static struct ustar _ustar; - fseek(f, offset, SEEK_SET); if (fread(&_ustar, 1, sizeof(struct ustar), f) != sizeof(struct ustar)) { fprintf(stderr, "failed to read file\n"); return NULL; @@ -64,13 +64,6 @@ static struct ustar * file_from_offset(FILE * f, size_t offset) { return &_ustar; } -static unsigned int round_to_512(unsigned int i) { - unsigned int t = i % 512; - - if (!t) return i; - return i + (512 - t); -} - static unsigned int interpret_mode(struct ustar * file) { return ((file->mode[0] - '0') << 18) | @@ -137,9 +130,8 @@ static void dump_file(struct ustar * file) { #endif #define CHUNK_SIZE 4096 -static void write_file(struct ustar * file, FILE * f, FILE * mf, size_t off, char * name) { +static void write_file(struct ustar * file, FILE * f, FILE * mf, char * name) { size_t length = interpret_size(file); - fseek(f, off + 512, SEEK_SET); char buf[CHUNK_SIZE]; while (length > CHUNK_SIZE) { fread( buf, 1, CHUNK_SIZE, f); @@ -150,10 +142,36 @@ static void write_file(struct ustar * file, FILE * f, FILE * mf, size_t off, cha fread( buf, 1, length, f); fwrite(buf, 1, length, mf); } - fclose(mf); - fseek(f, off, SEEK_SET); - /* TODO: fchmod? */ - chmod(name, interpret_mode(file)); + if (mf != stdout) { + fclose(mf); + chmod(name, interpret_mode(file)); + } +} + +static void _seek_forward(FILE * f, size_t amount) { + for (size_t i = 0; i < amount; ++i) { + fgetc(f); + } +} + +static void usage(char * argv[]) { + fprintf(stderr, + "tar - extract ustar archives\n" + "\n" + "usage: %s [-ctxvaf] [name]\n" + "\n" + " -f \033[3mfile archive to open\033[0m\n" + " -x \033[3mextract\033[0m\n" + "\n", argv[0]); +} + +static int matches_files(int argc, char * argv[], int optind, char * filename) { + while (optind < argc) { + if (!strcmp(argv[optind], filename)) return 1; + optind++; + } + + return 0; } int main(int argc, char * argv[]) { @@ -162,11 +180,14 @@ int main(int argc, char * argv[]) { char * fname = NULL; int verbose = 0; int action = 0; + int compressed = 0; + int to_stdout = 0; + int only_matches = 0; #define TAR_ACTION_EXTRACT 1 #define TAR_ACTION_CREATE 2 #define TAR_ACTION_LIST 3 - while ((opt = getopt(argc, argv, "ctxvaf:")) != -1) { + while ((opt = getopt(argc, argv, "?ctxzvaf:O")) != -1) { switch (opt) { case 'c': if (action) { @@ -195,6 +216,15 @@ int main(int argc, char * argv[]) { case 'v': verbose = 1; break; + case 'z': + compressed = 1; + break; + case 'O': + to_stdout = 1; + break; + case '?': + usage(argv); + return 1; default: fprintf(stderr, "%s: unsupported option '%c'\n", argv[0], opt); return 1; @@ -202,40 +232,72 @@ int main(int argc, char * argv[]) { } if (!fname) { - fprintf(stderr, "%s: todo: stdin/stdout\n", argv[0]); - return 1; + fname = "-"; } - if (action == TAR_ACTION_EXTRACT || action == TAR_ACTION_LIST) { + if (optind < argc) { + only_matches = 1; + } - hashmap_t * files = hashmap_create(10); + if (action == TAR_ACTION_EXTRACT || action == TAR_ACTION_LIST) { - FILE * f = fopen(fname,"r"); + FILE * f; + if (!strcmp(fname,"-")) { + f = stdin; + } else { + f = fopen(fname,"r"); + } if (!f) { fprintf(stderr, "%s: %s: %s\n", argv[0], fname, strerror(errno)); return 1; } - fseek(f, 0, SEEK_END); - size_t length = ftell(f); - fseek(f, 0, SEEK_SET); + if (compressed) { + int fds[2]; + pipe(fds); + + int child = fork(); + if (child == 0) { + /* Close the read end */ + close(fds[0]); + /* Put f's fd into stdin */ + dup2(fileno(f), STDIN_FILENO); + /* Make stdout the pipe */ + dup2(fds[1], STDOUT_FILENO); + /* Execeute gzunzip */ + char * args[] = {"gunzip","-c",NULL}; + exit(execvp("gunzip",args)); + } else if (child < 0) { + fprintf(stderr, "%s: failed to fork gunzip for compressed archive\n", argv[0]); + return 1; + } + + /* Reattach f to pipe */ + close(fds[1]); + f = fdopen(fds[0], "r"); + } char tmpname[1024] = {0}; int last_was_long = 0; - size_t off = 0; while (!feof(f)) { - struct ustar * file = file_from_offset(f, off); + struct ustar * file = extract_file(f); if (!file) { break; } - if (action == TAR_ACTION_LIST || verbose) { - fprintf(stdout, "%.155s%.100s\n", file->prefix, file->filename); - } - - if (action == TAR_ACTION_EXTRACT) { + if (action == TAR_ACTION_LIST) { + if (verbose) { + fprintf(stdout, "%10d %c %.155s%.100s\n", interpret_size(file), file->type[0], file->prefix, file->filename); + } else { + fprintf(stdout, "%.155s%.100s\n", file->prefix, file->filename); + } + _seek_forward(f, interpret_size(file)); + } else if (action == TAR_ACTION_EXTRACT) { + if (verbose) { + fprintf(stdout, "%.155s%.100s\n", file->prefix, file->filename); + } char name[1024] = {0}; if (last_was_long) { strncat(name, tmpname, 1023); @@ -246,61 +308,79 @@ int main(int argc, char * argv[]) { } if (file->type[0] == '0' || file->type[0] == 0) { - FILE * mf = fopen(name,"w"); + FILE * mf = to_stdout ? stdout : fopen(name,"w"); if (!mf) { fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, strerror(errno)); + _seek_forward(f, interpret_size(file)); } else { - write_file(file,f,mf,off,name); + if (!only_matches || matches_files(argc,argv,optind,name)) { + write_file(file,f,mf,name); + } } - struct ustar * tmp = malloc(sizeof(struct ustar)); - memcpy(tmp, file, sizeof(struct ustar)); - hashmap_set(files, name, tmp); } else if (file->type[0] == '5') { - if (name[strlen(name)-1] == '/') { - name[strlen(name)-1] = '\0'; - } - if (strlen(name)) { - if (mkdir(name, 0777) < 0) { - if (errno != EEXIST) { - fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, strerror(errno)); + if (!to_stdout) { + if (name[strlen(name)-1] == '/') { + name[strlen(name)-1] = '\0'; + } + if (strlen(name)) { + if (!only_matches || matches_files(argc,argv,optind,name)) { + if (mkdir(name, 0777) < 0) { + if (errno != EEXIST) { + fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, strerror(errno)); + } + } } } } } else if (file->type[0] == '1') { - char tmp[101] = {0}; - strncat(tmp, file->link, 100); - if (!hashmap_has(files, tmp)) { - fprintf(stderr, "%s: %s: %s: %s: missing target\n", argv[0], fname, name, tmp); - } else { + if (!to_stdout && (!only_matches || matches_files(argc,argv,optind,name))) { + char tmp[101] = {0}; + strncat(tmp, file->link, 100); FILE * mf = fopen(name,"w"); if (!mf) { fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, strerror(errno)); } else { - write_file(hashmap_get(files,tmp),f,mf,off,name); + FILE * source = fopen(tmp, "r"); + if (!source) { + fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, tmp, strerror(errno)); + } else { + while (!feof(source)) { + char buf[4096]; + ssize_t r = fread(buf, 1, 4096, source); + fwrite(buf, 1, r, mf); + } + fclose(source); + } + fclose(mf); + chmod(name, interpret_mode(file)); } } + _seek_forward(f, interpret_size(file)); } else if (file->type[0] == '2') { - char tmp[101] = {0}; - strncat(tmp, file->link, 100); - if (symlink(tmp, name) < 0) { - fprintf(stderr, "%s: %s: %s: %s: %s\n", argv[0], fname, name, tmp, strerror(errno)); + if (!to_stdout && (!only_matches || matches_files(argc,argv,optind,name))) { + char tmp[101] = {0}; + strncat(tmp, file->link, 100); + if (symlink(tmp, name) < 0) { + fprintf(stderr, "%s: %s: %s: %s: %s\n", argv[0], fname, name, tmp, strerror(errno)); + } } + _seek_forward(f, interpret_size(file)); } else if (file->type[0] == 'L') { /* This is a GNU Long Name block; store its contents as a file name */ size_t s = interpret_size(file); - fseek(f, off + 512, SEEK_SET); fread(tmpname, 1, s, f); tmpname[s] = '\0'; - fseek(f, off, SEEK_SET); last_was_long = 1; } else { fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, type_to_string(file->type[0])); + _seek_forward(f, interpret_size(file)); } } - off += 512; - off += round_to_512(interpret_size(file)); - if (off >= length) break; + size_t file_size = interpret_size(file); + if (file_size % 512) { + _seek_forward(f, 512 - (file_size % 512)); + } } } else { fprintf(stderr, "%s: unsupported action\n", argv[0]); diff --git a/apps/terminal.c b/apps/terminal.c index 4b6e51e1..94e5659c 100644 --- a/apps/terminal.c +++ b/apps/terminal.c @@ -2179,7 +2179,7 @@ static void _menu_action_toggle_free_size(struct MenuEntry * self) { static void _menu_action_show_about(struct MenuEntry * self) { char about_cmd[1024] = "\0"; - strcat(about_cmd, "about \"About Terminal\" /usr/share/icons/48/utilities-terminal.bmp \"ToaruOS Terminal\" \"(C) 2013-2020 K. Lange\n-\nPart of ToaruOS, which is free software\nreleased under the NCSA/University of Illinois\nlicense.\n-\n%https://toaruos.org\n%https://github.com/klange/toaruos\" "); + strcat(about_cmd, "about \"About Terminal\" /usr/share/icons/48/utilities-terminal.png \"PonyOS Terminal\" \"(C) 2013-2020 K. Lange\n-\nPart of PonyOS, which is free software\nreleased under the NCSA/University of Illinois\nlicense.\n-\n%https://ponyos.org\n%https://github.com/klange/ponyos\" "); char coords[100]; sprintf(coords, "%d %d &", (int)window->x + (int)window->width / 2, (int)window->y + (int)window->height / 2); strcat(about_cmd, coords); @@ -2212,6 +2212,11 @@ static void _menu_action_set_scale(struct MenuEntry * self) { reinit(); } +static void render_decors_callback(struct menu_bar * self) { + (void)self; + render_decors(); +} + int main(int argc, char ** argv) { window_width = char_width * 80; @@ -2324,7 +2329,7 @@ int main(int argc, char ** argv) { /* Set up menus */ terminal_menu_bar.entries = terminal_menu_entries; - terminal_menu_bar.redraw_callback = render_decors; + terminal_menu_bar.redraw_callback = render_decors_callback; struct MenuEntry * _menu_exit = menu_create_normal("exit","exit","Exit", _menu_action_exit); struct MenuEntry * _menu_copy = menu_create_normal(NULL, NULL, "Copy", _menu_action_copy); diff --git a/apps/test-printf.c b/apps/test-printf.c new file mode 100644 index 00000000..c47be46c --- /dev/null +++ b/apps/test-printf.c @@ -0,0 +1,11 @@ +#include + +int main(int argc, char * argv[]) { + printf("%.3d\n", 42); + printf("%.10d\n", 12345); + printf("%.1d\n", 0); + printf("%.0d\n", 0); + printf("%.0d\n", 1); + printf("%.0d\n", 123); + return 0; +} diff --git a/apps/test-tls.c b/apps/test-tls.c new file mode 100644 index 00000000..c8733471 --- /dev/null +++ b/apps/test-tls.c @@ -0,0 +1,37 @@ +#include +#include +#include + +__thread int myvalue; + +void * getaddressinthread(void * _unused) { + fprintf(stderr, "in thread before:\n"); + fprintf(stderr, "&myvalue = %p\n", (void*)&myvalue); + fprintf(stderr, "myvalue = %d\n", myvalue); + myvalue = 1234; + fprintf(stderr, "in thread after:\n"); + fprintf(stderr, "&myvalue = %p\n", (void*)&myvalue); + fprintf(stderr, "myvalue = %d\n", myvalue); + return NULL; +} + +int main(int argc, char * argv[]) { + + myvalue = 42; + + fprintf(stderr, "main thread before:\n"); + fprintf(stderr, "&myvalue = %p\n", (void*)&myvalue); + fprintf(stderr, "myvalue = %d\n", myvalue); + + pthread_t mythread; + pthread_create(&mythread, NULL, getaddressinthread, NULL); + + void * retval; + pthread_join(mythread, &retval); + + fprintf(stderr, "main thread after:\n"); + fprintf(stderr, "&myvalue = %p\n", (void*)&myvalue); + fprintf(stderr, "myvalue = %d\n", myvalue); + + return 0; +} diff --git a/apps/tutorial.c b/apps/tutorial.c index e6dd9c35..1d736f5e 100644 --- a/apps/tutorial.c +++ b/apps/tutorial.c @@ -165,7 +165,7 @@ static void load_page(int page) { body_text[i++] = ""; body_text[i++] = "When you're ready to continue, press \"Next\"."; body_text[i++] = ""; - body_text[i++] = "^Happy April Fools 2020!"; + body_text[i++] = "^Happy April Fools 2021!"; body_text[i++] = NULL; break; case 1: @@ -323,20 +323,11 @@ int main(int argc, char * argv[]) { window = yutani_window_create(yctx, width + bounds.width, height + bounds.height); /* Load icons */ - load_sprite(&logo, "/usr/share/logo_login.bmp"); - logo.alpha = ALPHA_EMBEDDED; - - load_sprite(&terminal, "/usr/share/icons/48/utilities-terminal.bmp"); - terminal.alpha = ALPHA_EMBEDDED; - - load_sprite(&folder, "/usr/share/icons/48/folder.bmp"); - folder.alpha = ALPHA_EMBEDDED; - - load_sprite(&package, "/usr/share/icons/48/package.bmp"); - package.alpha = ALPHA_EMBEDDED; - - load_sprite(&mouse_drag, "/usr/share/cursor/drag.bmp"); - mouse_drag.alpha = ALPHA_EMBEDDED; + load_sprite(&logo, "/usr/share/logo_login.png"); + load_sprite(&terminal, "/usr/share/icons/48/utilities-terminal.png"); + load_sprite(&folder, "/usr/share/icons/48/folder.png"); + load_sprite(&package, "/usr/share/icons/48/package.png"); + load_sprite(&mouse_drag, "/usr/share/cursor/drag.png"); load_page(0); diff --git a/apps/uname.c b/apps/uname.c index 19d3eb09..639515a5 100644 --- a/apps/uname.c +++ b/apps/uname.c @@ -124,7 +124,7 @@ int main(int argc, char * argv[]) { if (flags & FLAG_OSNAME) { if (space++) printf(" "); - printf("%s", "ToaruOS"); + printf("%s", "PonyOS"); } printf("\n"); diff --git a/apps/wallpaper-picker.c b/apps/wallpaper-picker.c index a1507bda..abe55c89 100644 --- a/apps/wallpaper-picker.c +++ b/apps/wallpaper-picker.c @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -157,8 +156,8 @@ void set_hilight(struct TTKButton * button, int hilight) { void load_wallpaper(void) { if (wallpaper.bitmap) free(wallpaper.bitmap); wallpaper.bitmap = NULL; - /* load JPG */ - load_sprite_jpg(&wallpaper, wallpaper_path); + /* load wallpaper */ + load_sprite(&wallpaper, wallpaper_path); /* Ensures we render correctly when scaling */ wallpaper.alpha = ALPHA_EMBEDDED; } diff --git a/apps/yutani-kbd.c b/apps/yutani-kbd.c new file mode 100644 index 00000000..1965313e --- /dev/null +++ b/apps/yutani-kbd.c @@ -0,0 +1,103 @@ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * This file is part of ToaruOS and is released under the terms + * of the NCSA / University of Illinois License - see LICENSE.md + * Copyright (C) 2015-2018 K. Lange + * + * yutani-test - Yutani Test Tool + * + * Kinda like xev: Pops up a window and displays events in a + * human-readable format. + * + */ +#include +#include +#include + +#include +#include + +static int left, top, width, height; + +static yutani_t * yctx; +static yutani_window_t * wina; +static gfx_context_t * ctx; +static int should_exit = 0; + +char * modifiers(unsigned int m) { + static char out[] = "........"; + + if (m & YUTANI_KEY_MODIFIER_LEFT_CTRL) out[0] = 'c'; else out[0] = '.'; + if (m & YUTANI_KEY_MODIFIER_LEFT_SHIFT) out[1] = 's'; else out[1] = '.'; + if (m & YUTANI_KEY_MODIFIER_LEFT_ALT) out[2] = 'a'; else out[2] = '.'; + if (m & YUTANI_KEY_MODIFIER_LEFT_SUPER) out[3] = 'x'; else out[3] = '.'; + if (m & YUTANI_KEY_MODIFIER_RIGHT_CTRL) out[4] = 'c'; else out[4] = '.'; + if (m & YUTANI_KEY_MODIFIER_RIGHT_SHIFT) out[5] = 's'; else out[5] = '.'; + if (m & YUTANI_KEY_MODIFIER_RIGHT_ALT) out[6] = 'a'; else out[6] = '.'; + if (m & YUTANI_KEY_MODIFIER_RIGHT_SUPER) out[7] = 'x'; else out[7] = '.'; + + return out; +} + +void redraw(void) { + draw_fill(ctx, rgb(0,0,0)); + + int w = width - 1, h = height - 1; + + draw_line(ctx, 0, w, 0, 0, rgb(255,255,255)); + draw_line(ctx, 0, w, h, h, rgb(255,255,255)); + + draw_line(ctx, 0, 0, 0, h, rgb(255,255,255)); + draw_line(ctx, w, w, 0, h, rgb(255,255,255)); +} + +int main (int argc, char ** argv) { + left = 100; + top = 100; + width = 500; + height = 500; + + yctx = yutani_init(); + wina = yutani_window_create(yctx, width, height); + yutani_window_move(yctx, wina, left, top); + + ctx = init_graphics_yutani(wina); + + redraw(); + + char keys[256] = {0}; + + printf("\033[H\033[2J"); + + while (!should_exit) { + yutani_msg_t * m = yutani_poll(yctx); + if (m) { + switch (m->type) { + case YUTANI_MSG_KEY_EVENT: + { + struct yutani_msg_key_event * ke = (void*)m->data; + if (ke->event.keycode >= 'a' && ke->event.keycode < 'z') { + keys[ke->event.keycode] = (ke->event.action == KEY_ACTION_DOWN); + } + printf("\033[1;1H"); + for (int i = 'a'; i < 'z'; ++i) { + printf("\033[%dm%c ", keys[i] ? 0 : 31, i); + } + fflush(stdout); + } + break; + case YUTANI_MSG_WINDOW_CLOSE: + case YUTANI_MSG_SESSION_END: + should_exit = 1; + break; + default: + break; + } + } + free(m); + } + + yutani_close(yctx, wina); + + return 0; +} + diff --git a/base/etc/os-release b/base/etc/os-release index 82e0024e..61fb2f54 100644 --- a/base/etc/os-release +++ b/base/etc/os-release @@ -1,8 +1,8 @@ -PRETTY_NAME="ToaruOS 1.10" -NAME="ToaruOS" -VERSION_ID="1.10.9" -VERSION="1.10.9" -ID=toaru -HOME_URL="https://toaruos.org/" -SUPPORT_URL="https://github.com/klange/toaruos" -BUG_REPORT_URL="https://github.com/klange/toaruos" +PRETTY_NAME="PonyOS 7.0" +NAME="PonyOS" +VERSION_ID="7.0.0" +VERSION="7.0.0" +ID=ponyos +HOME_URL="https://ponyos.org/" +SUPPORT_URL="https://github.com/klange/ponyos" +BUG_REPORT_URL="https://github.com/klange/ponyos" diff --git a/base/etc/panel.menu b/base/etc/panel.menu index 511a6b5b..71dc3bae 100644 --- a/base/etc/panel.menu +++ b/base/etc/panel.menu @@ -4,7 +4,7 @@ &settings,folder,Settings - exec help-browser,help,Help Browser -exec about,star,About ToaruOS +exec about,star,About PonyOS log-out,exit,Log Out :accessories exec file-browser,folder,File Browser diff --git a/base/etc/startup.d/99_runstart.sh b/base/etc/startup.d/99_runstart.sh index c15f4851..8a551f4e 100755 --- a/base/etc/startup.d/99_runstart.sh +++ b/base/etc/startup.d/99_runstart.sh @@ -6,10 +6,12 @@ export-cmd START kcmdline -g start export USER=root export HOME=/home/root +export-cmd GETTY_ARGS qemu-fwcfg opt/org.toaruos.gettyargs + echo -n "Launching startup application..." > /dev/pex/splash echo -n "!quit" > /dev/pex/splash if equals? "$START" "--vga" then exec /bin/terminal-vga -l -if equals? "$START" "--headless" then exec /bin/getty +if equals? "$START" "--headless" then exec /bin/getty ${GETTY_ARGS} if empty? "$START" then exec /bin/compositor else exec /bin/compositor $START diff --git a/base/home/local/.bim3rc b/base/home/local/.bim3rc new file mode 100644 index 00000000..0866ea36 --- /dev/null +++ b/base/home/local/.bim3rc @@ -0,0 +1,25 @@ +''' +Sample config file for bim3 +''' + +# Quirks work the same as they did in bim2, except for the obvious syntax change +quirk('TERM','screen','no24bit','noitalic') +quirk('TERM','xterm-256color','caninsert','canpaste','cansgrmouse') +quirk('TERMINAL_EMULATOR','JetBrains','nobce') + +# checkprop() returns 0 if we _can_ do something, so +# call it with 'not' to check capabilities... +if not checkprop('can_unicode'): + tabindicator('»') + spaceindicator('·') + +# Themes are actually Kuroko functions now, but we load +# them into a table like always for the :theme command +theme('sunsmoke') + +# Non-string values are coerced into strings, so these commands +# can take integer values. +global.git(1) +global.statusbar(1) +global.autohidetabs(1) +smartcomplete(1) diff --git a/base/home/local/.eshrc b/base/home/local/.eshrc index e6632d70..f5589659 100644 --- a/base/home/local/.eshrc +++ b/base/home/local/.eshrc @@ -1,9 +1,6 @@ export PS1_TITLE="\[\e]1;\u@\h:\w\\007\e]2;\u@\h:\w\\007\]" export PS1_RIGHT="\[\\e[1m\e[38;5;59m\][\[\e[38;5;173m\]\d \[\e[38;5;167m\]\t\[\e[38;5;59m\]] \[\e[0m\]" export PS1_LEFT="${PS1_TITLE}\[\e[1m\e[38;5;221m\]\u\[\e[38;5;59m\]@\[\e[38;5;81m\]\h \[\e[38;5;167m\]\r\[\e[0m\]\w\U\\\$\[\e[0m\] " -export RLINE_THEME="sunsmoke" -# Old prompt -export PS1="\e]1;\u@\h:\w\\007\e]2;\u@\\h:\w\007\\e[1m\e[s\e[400C\e[16D\e[1m\e[38;5;59m[\e[38;5;173m\d \e[38;5;167m\t\e[38;5;59m]\e[u\e[38;5;221m\u\e[38;5;59m@\e[38;5;81m\h \e[38;5;167m\r\e[0m\w\U\\\$\e[0m " if equals? "$TERM" "toaru-vga" then export RLINE_THEME="default" else export RLINE_THEME="sunsmoke" diff --git a/base/home/local/README.md b/base/home/local/README.md index 597be2e4..222f894c 100644 --- a/base/home/local/README.md +++ b/base/home/local/README.md @@ -20,7 +20,7 @@ Or you can install Python with: The password for the default user (`local`) is `local`. -ToaruOS's compositing window server includes many common keybindings: +PonyOS's compositing window server includes many common keybindings: - Hold Alt to drag windows. - Super (Win) combined with the arrow keys will "grid" windows to the sides or top and bottom of the screen. Combine with Ctrl and Shift diff --git a/base/usr/include/arpa/inet.h b/base/usr/include/arpa/inet.h new file mode 100644 index 00000000..bb2988f0 --- /dev/null +++ b/base/usr/include/arpa/inet.h @@ -0,0 +1,11 @@ +#pragma once + +#include <_cheader.h> +#include +#include + +_Begin_C_Header + +#define INADDR_ANY (unsigned long int)0x0 + +_End_C_Header diff --git a/base/usr/include/kernel/process.h b/base/usr/include/kernel/process.h index 6b332804..215f3e7d 100644 --- a/base/usr/include/kernel/process.h +++ b/base/usr/include/kernel/process.h @@ -35,6 +35,8 @@ typedef struct thread { uint8_t padding[32]; /* I don't know */ page_directory_t * page_directory; /* Page Directory */ + uintptr_t gsbase; + } thread_t; /* Portable image struct */ diff --git a/base/usr/include/kernel/system.h b/base/usr/include/kernel/system.h index 6d7b7f3b..140dfa58 100644 --- a/base/usr/include/kernel/system.h +++ b/base/usr/include/kernel/system.h @@ -75,6 +75,8 @@ extern uint8_t startswith(const char * str, const char * accept); extern void gdt_install(void); extern void gdt_set_gate(uint8_t num, uint64_t base, uint64_t limit, uint8_t access, uint8_t gran); extern void set_kernel_stack(uintptr_t stack); +extern void gdt_set_gsbase(uintptr_t base); +extern uintptr_t gdt_get_gsbase(void); /* IDT */ extern void idt_install(void); @@ -86,7 +88,7 @@ extern void idt_set_gate(uint8_t num, void (*base)(void), uint16_t sel, uint8_t * the correct offsets as well. */ struct regs { - unsigned int gs, fs, es, ds; + unsigned int _unused, fs, es, ds; unsigned int edi, esi, ebp, esp, ebx, edx, ecx, eax; unsigned int int_no, err_code; unsigned int eip, cs, eflags, useresp, ss; diff --git a/base/usr/include/kuroko b/base/usr/include/kuroko new file mode 120000 index 00000000..9432f989 --- /dev/null +++ b/base/usr/include/kuroko @@ -0,0 +1 @@ +../../../kuroko/src/kuroko \ No newline at end of file diff --git a/base/usr/include/netdb.h b/base/usr/include/netdb.h new file mode 100644 index 00000000..da3c1795 --- /dev/null +++ b/base/usr/include/netdb.h @@ -0,0 +1,23 @@ +#pragma once + +#include <_cheader.h> + +#include + +_Begin_C_Header + +extern int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, + char *host, socklen_t hostlen, + char *serv, socklen_t servlen, int flags); + +extern int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, + struct addrinfo **res); + +extern void freeaddrinfo(struct addrinfo *res); + +#define NI_NUMERICHOST 1 +#define NI_MAXHOST 255 + + +_End_C_Header diff --git a/base/usr/include/pthread.h b/base/usr/include/pthread.h index eeae188a..7f7b83bf 100644 --- a/base/usr/include/pthread.h +++ b/base/usr/include/pthread.h @@ -12,6 +12,12 @@ typedef struct { } pthread_t; typedef unsigned int pthread_attr_t; +typedef struct { + int volatile atomic_lock; + int volatile readers; + int writerPid; +} pthread_rwlock_t; + extern int pthread_create(pthread_t * thread, pthread_attr_t * attr, void *(*start_routine)(void *), void * arg); extern void pthread_exit(void * value); extern int pthread_kill(pthread_t thread, int sig); @@ -38,5 +44,10 @@ extern int pthread_mutex_destroy(pthread_mutex_t *mutex); extern int pthread_attr_init(pthread_attr_t *attr); extern int pthread_attr_destroy(pthread_attr_t *attr); +extern int pthread_rwlock_init(pthread_rwlock_t * lock, void * args); +extern int pthread_rwlock_wrlock(pthread_rwlock_t * lock); +extern int pthread_rwlock_rdlock(pthread_rwlock_t * lock); +extern int pthread_rwlock_unlock(pthread_rwlock_t * lock); +extern int pthread_rwlock_destroy(pthread_rwlock_t * lock); _End_C_Header diff --git a/base/usr/include/sys/socket.h b/base/usr/include/sys/socket.h index 277f0512..9e90de95 100644 --- a/base/usr/include/sys/socket.h +++ b/base/usr/include/sys/socket.h @@ -18,6 +18,7 @@ _Begin_C_Header #define SOL_SOCKET 0 #define SO_KEEPALIVE 1 +#define SO_REUSEADDR 2 struct hostent { char *h_name; /* official name of host */ @@ -73,6 +74,10 @@ struct msghdr { int msg_flags; /* flags on received message */ }; +struct sockaddr_storage { + unsigned short ss_family; + char _ss_pad[128]; +}; typedef uint32_t in_addr_t; typedef uint16_t in_port_t; @@ -100,6 +105,9 @@ extern int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen); extern int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen); extern int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); +extern int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen); +extern int shutdown(int sockfd, int how); + _End_C_Header diff --git a/base/usr/include/sys/sysfunc.h b/base/usr/include/sys/sysfunc.h index dd876de4..cb4b9860 100644 --- a/base/usr/include/sys/sysfunc.h +++ b/base/usr/include/sys/sysfunc.h @@ -23,6 +23,7 @@ #define TOARU_SYS_FUNC_THREADNAME 11 #define TOARU_SYS_FUNC_DEBUGPRINT 12 #define TOARU_SYS_FUNC_SETVGACURSOR 13 +#define TOARU_SYS_FUNC_SETGSBASE 14 _Begin_C_Header extern int sysfunc(int command, char ** args); diff --git a/base/usr/include/time.h b/base/usr/include/time.h index bc307efb..d517f3ba 100644 --- a/base/usr/include/time.h +++ b/base/usr/include/time.h @@ -37,4 +37,16 @@ typedef int clock_t; extern clock_t clock(void); #define CLOCKS_PER_SEC 1 +struct timespec { + time_t tv_sec; + long tv_nsec; +}; + +typedef int clockid_t; + +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC 1 + +extern int clock_gettime(clockid_t clk_id, struct timespec *tp); + _End_C_Header diff --git a/base/usr/include/toaru/graphics.h b/base/usr/include/toaru/graphics.h index 5855fec3..d9808978 100644 --- a/base/usr/include/toaru/graphics.h +++ b/base/usr/include/toaru/graphics.h @@ -75,6 +75,7 @@ extern void blur_context_box(gfx_context_t * _src, int radius); extern void sprite_free(sprite_t * sprite); extern int load_sprite(sprite_t * sprite, char * filename); +extern int load_sprite_bmp(sprite_t * sprite, char * filename); //extern int load_sprite_png(sprite_t * sprite, char * file); extern void draw_sprite(gfx_context_t * ctx, sprite_t * sprite, int32_t x, int32_t y); extern void draw_line(gfx_context_t * ctx, int32_t x0, int32_t x1, int32_t y0, int32_t y1, uint32_t color); diff --git a/base/usr/include/toaru/inflate.h b/base/usr/include/toaru/inflate.h new file mode 100644 index 00000000..5f532e05 --- /dev/null +++ b/base/usr/include/toaru/inflate.h @@ -0,0 +1,37 @@ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * This file is part of ToaruOS and is released under the terms + * of the NCSA / University of Illinois License - see LICENSE.md + * Copyright (C) 2020 K. Lange + */ + +#pragma once + +#include <_cheader.h> +#include + +_Begin_C_Header + +struct huff_ring; + +struct inflate_context { + + /* Consumer-private pointers for input/output storage (eg. FILE *) */ + void * input_priv; + void * output_priv; + + /* Methods for reading / writing from the input /output */ + uint8_t (*get_input)(struct inflate_context * ctx); + void (*write_output)(struct inflate_context * ctx, unsigned int sym); + + /* Bit buffer, which holds at most 8 bits from the input */ + int bit_buffer; + int buffer_size; + + /* Output ringbuffer for backwards lookups */ + struct huff_ring * ring; +}; + +int deflate_decompress(struct inflate_context * ctx); +int gzip_decompress(struct inflate_context * ctx); + +_End_C_Header diff --git a/base/usr/include/toaru/menu.h b/base/usr/include/toaru/menu.h index cc524d7d..c4c76248 100644 --- a/base/usr/include/toaru/menu.h +++ b/base/usr/include/toaru/menu.h @@ -19,6 +19,7 @@ struct MenuList; struct MenuEntry { enum MenuEntry_Type _type; struct MenuList * _owner; + void * _private; int height; /* All must have a height, so put it here. */ int width; /* Actual width */ @@ -115,7 +116,8 @@ struct menu_bar { int num_entries; - void (*redraw_callback)(void); + void * _private; + void (*redraw_callback)(struct menu_bar *); }; extern void menu_bar_render(struct menu_bar * self, gfx_context_t * ctx); diff --git a/base/usr/include/toaru/png.h b/base/usr/include/toaru/png.h new file mode 100644 index 00000000..a5179b0d --- /dev/null +++ b/base/usr/include/toaru/png.h @@ -0,0 +1,11 @@ +#pragma once + +#include <_cheader.h> +#include + +_Begin_C_Header + +extern int load_sprite_png(sprite_t * sprite, char * filename); + +_End_C_Header + diff --git a/base/usr/include/toaru/rline.h b/base/usr/include/toaru/rline.h index d955ad95..b6face56 100644 --- a/base/usr/include/toaru/rline.h +++ b/base/usr/include/toaru/rline.h @@ -1,8 +1,5 @@ #pragma once -#include <_cheader.h> - -_Begin_C_Header struct rline_callback; typedef struct { @@ -30,16 +27,31 @@ typedef struct rline_callback { rline_callback_t rev_search; } rline_callbacks_t; -extern void rline_redraw(rline_context_t * context); -extern void rline_redraw_clean(rline_context_t * context); -extern void rline_insert(rline_context_t * context, const char * what); -extern int rline(char * buffer, int buf_size, rline_callbacks_t * callbacks); -extern void rline_reverse_search(rline_context_t * context); +typedef enum { + /* Base colors */ + RLINE_STYLE_MAIN, + RLINE_STYLE_ALT, + /* Syntax flags */ + RLINE_STYLE_KEYWORD, + RLINE_STYLE_STRING, + RLINE_STYLE_COMMENT, + RLINE_STYLE_TYPE, + RLINE_STYLE_PRAGMA, + RLINE_STYLE_NUMERAL, +} rline_style_t; +extern int rline(char * buffer, int buf_size); +extern int rline_exp_set_prompts(char * left, char * right, int left_width, int right_width); +extern int rline_exp_set_shell_commands(char ** cmds, int len); +extern int rline_exp_set_tab_complete_func(rline_callback_t func); +extern int rline_exp_set_syntax(char * name); extern void rline_history_insert(char * str); extern void rline_history_append_line(char * str); extern char * rline_history_get(int item); extern char * rline_history_prev(int item); +extern void rline_place_cursor(void); +extern void rline_set_colors(rline_style_t style); +extern int rline_terminal_width; #define RLINE_HISTORY_ENTRIES 128 extern char * rline_history[RLINE_HISTORY_ENTRIES]; @@ -47,6 +59,8 @@ extern int rline_history_count; extern int rline_history_offset; extern int rline_scroll; extern char * rline_exit_string; +extern char * rline_preload; -_End_C_Header - +/* Legacy stuff */ +extern void rline_redraw(rline_context_t * context); +extern void rline_insert(rline_context_t * context, const char * what); diff --git a/base/usr/include/toaru/rline_exp.h b/base/usr/include/toaru/rline_exp.h deleted file mode 100644 index a3a5fd0e..00000000 --- a/base/usr/include/toaru/rline_exp.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include <_cheader.h> -#include - -_Begin_C_Header - -extern int rline_experimental(char * buffer, int buf_size); -extern int rline_exp_set_prompts(char * left, char * right, int left_width, int right_width); -extern int rline_exp_set_shell_commands(char ** cmds, int len); -extern int rline_exp_set_tab_complete_func(rline_callback_t func); -extern int rline_exp_set_syntax(char * name); - -_End_C_Header diff --git a/base/usr/include/unistd.h b/base/usr/include/unistd.h index cac6b7bc..3e15c436 100644 --- a/base/usr/include/unistd.h +++ b/base/usr/include/unistd.h @@ -97,4 +97,7 @@ extern unsigned int alarm(unsigned int seconds); extern void *sbrk(intptr_t increment); +extern void sync(void); +extern int truncate(const char *, off_t); + _End_C_Header diff --git a/base/usr/share/bim/syntax/__init__.krk b/base/usr/share/bim/syntax/__init__.krk new file mode 100644 index 00000000..3d239ca3 --- /dev/null +++ b/base/usr/share/bim/syntax/__init__.krk @@ -0,0 +1,47 @@ +from bim import SyntaxState, bindHighlighter as bind + +class Highlighter(SyntaxState): + + def matchPrefix(prefix): + let i = 0 + while True: + if i == len(prefix): return True + if prefix[i] != self[i]: return False + if not self[i]: return False + i++ + + def paintSingleString(): + self.paint(1, self.FLAG_STRING) + while self[0]: + if self[0] == '\\' and self[1] == "'": + self.paint(2, self.FLAG_ESCAPE) + else if self[0] == "'": + self.paint(1, self.FLAG_STRING) + return + else if self[0] == '\\': + self.paint(2, self.FLAG_ESCAPE) + else: + self.paint(1, self.FLAG_STRING) + + def paintSimpleString(): + self.paint(1, self.FLAG_STRING) + while self[0]: + if self[0] == '\\' and self[1] == '"': + self.paint(2, self.FLAG_ESCAPE) + else if self[0] == '"': + self.paint(1, self.FLAG_STRING) + return + else if self[0] == '\\': + self.paint(2, self.FLAG_ESCAPE) + else: + self.paint(1, self.FLAG_STRING) + + @staticmethod + def isalpha(c): + c = c if isinstance(c,int) else ord(c) + return (c >= ord('a') and c <= ord('z')) or (c >= ord('A') and c <= ord('Z')) + + @staticmethod + def isalnum(c): + c = c if isinstance(c,int) else ord(c) + return (c >= ord('a') and c <= ord('z')) or (c >= ord('A') and c <= ord('Z')) or (c >= ord('0') and c <= ord('9')) diff --git a/base/usr/share/bim/syntax/bash.krk b/base/usr/share/bim/syntax/bash.krk new file mode 100644 index 00000000..48433f8c --- /dev/null +++ b/base/usr/share/bim/syntax/bash.krk @@ -0,0 +1,147 @@ +from syntax import Highlighter, bind + +class BashHighlighter(Highlighter): + name = "bash" + extensions = ('.sh','.bash','.bashrc') + + keywords = [ + 'if','then','else','elif','fi','case','esac','for','coproc', + 'select','while','until','do','done','in','function','time', + 'exit','return','source','export','alias','complete','shopt', + 'local','eval','echo','cd','pushd','popd','printf','sed', + 'rm','mv' + ] + + def popState(self, state): + let newState = state / 100 + return newState * 10 + + def pushState(self, state, newState): + return state * 10 + newState + + def paintTick(self, outState): + let last = None + while self[0] != None: + if last != '\\' and self[0] == "'": + self.paint(1, self.FLAG_STRING) + return self.popState(outState) + else if last == '\\': + self.paint(1, self.FLAG_STRING) + last = None + else if self[0] != None: + last = self[0] + self.paint(1, self.FLAG_STRING) + return outState + + def paintBracedVariable(self): + while self[0] != None: + if self[0] == '}': + self.paint(1, self.FLAG_NUMERAL) + return 0 + self.paint(1, self.FLAG_NUMERAL) + return 0 + + def specialVariable(self, c): + return c == '@' or c == '?' + + def paintString(self, term, outState, color): + let last = None + while self[0] != None: + if last != '\\' and self[0] == term: + self.paint(1, color) + return self.popState(outState) + else if last == '\\': + self.paint(1, color) + last = None + else if term != '`' and self[0] == '`': + self.paint(1, self.FLAG_ESCAPE) + outState = self.paintString('`', self.pushState(outState, 20), self.FLAG_ESCAPE) + else if term != ')' and self[0] == '$' and self[1] == '(': + self.paint(2, self.FLAG_TYPE) + outState = self.paintString(')', self.pushState(outState, 30), self.FLAG_TYPE) + else if self[0] == '$' and self[1] == '{': + self.paint(2, self.FLAG_NUMERAL) + self.paintBracedVariable() + else if self[0] == '$': + self.paint(1, self.FLAG_NUMERAL) + if self.specialVariable(self[0]): + self.paint(1, self.FLAG_NUMERAL) + continue + while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_NUMERAL) + else if term != '"' and self[0] == '"': + self.paint(1, self.FLAG_STRING) + outState = self.paintString('"', self.pushState(outState, 40), self.FLAG_STRING) + else if term != '"' and self[0] == "'": + self.paint(1, self.FLAG_STRING) + outState = self.paintTick(outState) + else if self[0] != -1: + last = self[0] + self.paint(1, color) + return outState + + def calculate(self): + if self.state < 1: + if self[0] == '#' and self[-1] != '\\': + while self[0] != None: + if self.commentBuzzwords(): continue + self.paint(1, self.FLAG_COMMENT) + return None + else if self[0] == "'" and self[-1] != '\\': + self.paint(1, self.FLAG_STRING) + return self.paintTick(10) + else if self[0] == '`' and self[-1] != '\\': + self.paint(1, self.FLAG_ESCAPE) + return self.paintString('`', 20, self.FLAG_ESCAPE) + else if self[0] == '$' and self[1] == '(' and self[-1] != '\\': + self.paint(2, self.FLAG_TYPE) + return self.paintString(')', 30, self.FLAG_TYPE) + else if self[0] == '"' and self[-1] != '\\': + self.paint(1, self.FLAG_STRING) + return self.paintString('"', 40, self.FLAG_STRING) + else if self[0] == '$' and self[1] == '{' and self[-1] != '\\': + self.paint(2, self.FLAG_NUMERAL) + self.paintBracedVariable() + return 0 + else if self[0] == '$'and self[-1] != '\\': + self.paint(1, self.FLAG_NUMERAL) + if self.specialVariable(self[0]): + self.paint(1, self.FLAG_NUMERAL) + return 0 + while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_NUMERAL) + return 0 + else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): + return 0 + else if self[0] == ';': + self.paint(1, self.FLAG_KEYWORD) + return 0 + else if self.cKeywordQualifier(self[0]): + for i = 0; self[i] != None; i++: + if self[i] == ' ': break + if self[i] == '=': + self.paint(i, self.FLAG_TYPE) + self.skip() + return 0 + for i = 0; self[i] != None; i++: + if self[i] == '(': + self.paint(i, self.FLAG_TYPE) + return 0 + if not self.cKeywordQualifier(self[i]) and self[i] != '-' and self[i] != ' ': + break + self.skip() + return 0 + else if self[0] != None: + self.skip() + return 0 + else if self.state >= 10: + let outState = self.state + while self[0] != None: + let s = (outState / 10) % 10 + if s == 1: outState = self.paintString("'", outState, self.FLAG_STRING) + else if s == 2: outState = self.paintString('`', outState, self.FLAG_ESCAPE) + else if s == 3: outState = self.paintString(')', outState, self.FLAG_TYPE) + else if s == 4: outState = self.paintString('"', outState, self.FLAG_STRING) + else if not s: return None + return outState + return None + +bind(BashHighlighter) diff --git a/base/usr/share/bim/syntax/biminfo.krk b/base/usr/share/bim/syntax/biminfo.krk new file mode 100644 index 00000000..2a827ea2 --- /dev/null +++ b/base/usr/share/bim/syntax/biminfo.krk @@ -0,0 +1,18 @@ +from syntax import Highlighter, bind + +class BiminfoHighlighter(Highlighter): + name = 'biminfo' + extensions = ('.biminfo',) + def calculate(self): + if self.i == 0: + if self[0] == '#': + self.paint(-1, self.FLAG_COMMENT) + else if self[0] == '>': + self.paint(1, self.FLAG_KEYWORD) + while self[0] != ' ': + self.paint(1, self.FLAG_TYPE) + self.skip() + self.paint(-1, self.FLAG_NUMERAL) + return None + +bind(BiminfoHighlighter) diff --git a/base/usr/share/bim/syntax/c.krk b/base/usr/share/bim/syntax/c.krk new file mode 100644 index 00000000..8efb2fe4 --- /dev/null +++ b/base/usr/share/bim/syntax/c.krk @@ -0,0 +1,236 @@ +from syntax import Highlighter, bind +from syntax.doxygen import tryDoxygenComment + +class CHighlighter(Highlighter): + name = 'c' + extensions = ('.c','.h','.cpp','.hpp','.c++','.h++','.cc','.hh') + + doxygenDocstrings = False + + keywords = [ + "while","if","for","continue","return","break","switch","case","sizeof", + "struct","union","typedef","do","default","else","goto", + "alignas","alignof","offsetof","asm","__asm__", + "public","private","class","using","namespace","virtual","override","protected", + "template","typename","static_cast","throw" + ] + + types = [ + "static","int","char","short","float","double","void","unsigned","volatile","const", + "register","long","inline","restrict","enum","auto","extern","bool","complex", + "uint8_t","uint16_t","uint32_t","uint64_t", + "int8_t","int16_t","int32_t","int64_t","FILE", + "ssize_t","size_t","uintptr_t","intptr_t","__volatile__", + "constexpr" + ] + + special = [ + "NULL", + "stdin","stdout","stderr", + "STDIN_FILENO","STDOUT_FILENO","STDERR_FILENO" + ] + + preprocessor_base_state = 5 + + def paintCString(self): + let last = None + while self[0] != None: + if last != '\\' and self[0] == '"': + self.paint(1, self.FLAG_STRING) + return 0 + else if self[0] == '\\' and not self[1]: + self.paint(1, self.FLAG_ESCAPE) + return 4 + else if self[0] == '\\' and self[1] in 'nr\\': + self.paint(2, self.FLAG_ESCAPE) + last = None + else if self[0] == '\\' and self[1] in '01234567': + self.paint(2, self.FLAG_ESCAPE) + if self[0] in '01234567': + self.paint(1, self.FLAG_ESCAPE) + if self[0] in '01234567': + self.paint(1, self.FLAG_ESCAPE) + last = None + else if self[0] == '%': + self.paint(1, self.FLAG_ESCAPE) + if self[0] == '%': + self.paint(1, self.FLAG_ESCAPE) + else: + while self[0] in '-#*0+': self.paint(1, self.FLAG_ESCAPE) + while self.isdigit(self[0]): self.paint(1, self.FLAG_ESCAPE) + if self[0] == '.': + self.paint(1, self.FLAG_ESCAPE) + if self[0] == '*': self.paint(1, self.FLAG_ESCAPE) + else: while self.isdigit(self[0]): self.paint(1, self.FLAG_ESCAPE) + while self[0] in 'lz': self.paint(1, self.FLAG_ESCAPE) + if self[0] in '"\\': continue + self.paint(1, self.FLAG_ESCAPE) + else if self[0] == '\\' and self[1] == 'x': + self.paint(2, self.FLAG_ESCAPE) + while self.isxdigit(self[0]): self.paint(1, self.FLAG_ESCAPE) + else if self.doxygenDocstrings and tryDoxygenComment(self, self.FLAG_STRING): + continue + else: + last = self[0] + self.paint(1, self.FLAG_STRING) + return 0 + + def paintCChar(self): + self.paint(1, self.FLAG_NUMERAL) + let last = None + while self[0] != None: + if last != '\\' and self[0] == "'": + self.paint(1, self.FLAG_NUMERAL) + return + else if last == '\\' and self[0] == '\\': + self.paint(1, self.FLAG_NUMERAL) + last = None + else: + last = self[0] + self.paint(1, self.FLAG_NUMERAL) + + def paintCComment(self): + let last = None + while self[0] != None: + if self.commentBuzzwords(): continue + if tryDoxygenComment(self): continue + else if last == '*' and self[0] == '/': + self.paint(1, self.FLAG_COMMENT) + return 0 + else: + last = self[0] + self.paint(1, self.FLAG_COMMENT) + return 1 + + def paintCPragma(self): + while self[0] != None: + if self[0] == '"': + self.paint(1, self.FLAG_STRING) + let result = self.paintCString() + if result != 0: return result + else if self[0] == "'": + self.paintCChar() + else if self[0] == '\\' and self[1] == None: + self.paint(1, self.FLAG_PRAGMA) + return 2 + else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): + continue + else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier): + continue + else if self[0] == '/' and self[1] == '/': + self.paintComment() + return None + else if self[0] == '/' and self[1] == '*': + if self.paintCComment() == 1: return 3 + continue + else: + self.paint(1, self.FLAG_PRAGMA) + return 0 + + def paintCNumeral(self): + if self[0] == '0' and (self[1] == 'x' or self[1] == 'X'): + self.paint(2, self.FLAG_NUMERAL) + while self.isxdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) + else if self[0] == '0' and self[1] == '.': + self.paint(2, self.FLAG_NUMERAL) + while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) + if self[0] == 'f': self.paint(1, self.FLAG_NUMERAL) + return 0 + else if self[0] == '0': + self.paint(1, self.FLAG_NUMERAL) + while self[0] in '01234567': self.paint(1, self.FLAG_NUMERAL) + else: + while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) + if self[0] == '.': + self.paint(1, self.FLAG_NUMERAL) + while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) + if self[0] == 'f': self.paint(1, self.FLAG_NUMERAL) + return 0 + while self[0] in 'uUlL': self.paint(1, self.FLAG_NUMERAL) + return 0 + + def calculate(self): + let cond = self.state + if cond <= 0: + while self[0]: + if self[0] == '#': + for i in range(self.i): + if self[-i-1] != ' ' and self[-i-1] != '\t': + self.skip() + continue + self.paint(1, self.FLAG_PRAGMA) + while self[0] == ' ': + self.paint(1, self.FLAG_PRAGMA) + if self.matchAndPaint("include", self.FLAG_PRAGMA, self.cKeywordQualifier): + while self[0] == ' ': + self.paint(1, self.FLAG_PRAGMA) + if self[0] == '<': + self.paint(1, self.FLAG_STRING) + while self[0] != '>' and self[0] != None: + self.paint(1, self.FLAG_STRING) + if self[0] != None: + self.paint(1, self.FLAG_STRING) + else if self.matchAndPaint("if", self.FLAG_PRAGMA, self.cKeywordQualifier): + if self[0] == ' ' and self[1] == '0' and self[2] == None: + self.paint(2, self.FLAG_COMMENT) + self.rewind(6) + self.paint(-1, self.FLAG_COMMENT) + return self.preprocessor_base_state + else if self.matchAndPaint("else", self.FLAG_PRAGMA, self.cKeywordQualifier): + # Do nothing? + return self.paintCPragma() + else if self[0] == '/' and self[1] == '/': + self.paintComment() + else if self[0] == '/' and self[1] == '*': + self.paint(2, self.FLAG_COMMENT) + return self.paintCComment() + else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): + continue + else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier): + continue + else if self.findKeywords(self.special, self.FLAG_NUMERAL, self.cKeywordQualifier): + continue + else if self[0] == '"': + self.paint(1, self.FLAG_STRING) + return self.paintCString() + else if self[0] == "'": + self.paintCChar() + else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): + self.paintCNumeral() + else: + self.skip() + else if cond == 1: + return self.paintCComment() + else if cond == 2: + return self.paintCPragma() + else if cond == 3: + if self.paintCComment() == 1: + return 3 + return self.paintCPragma() + else if cond == 4: + return self.paintCString() + else: + while self[0] == ' ' or self[0] == '\t': self.paint(1, self.FLAG_COMMENT) + if self[0] == '#': + self.paint(1, self.FLAG_COMMENT) + while self[0] == ' ' or self[0] == '\t': self.paint(1, self.FLAG_COMMENT) + if self.matchAndPaint("if", self.FLAG_COMMENT, self.cKeywordQualifier): + self.paint(-1, self.FLAG_COMMENT) + return self.state + 1 + else if self.matchAndPaint("else", self.FLAG_COMMENT, self.cKeywordQualifier) or self.matchAndPaint("elif", self.FLAG_COMMENT, self.cKeywordQualifier): + self.paint(-1, self.FLAG_COMMENT) + if self.state == self.preprocessor_base_state: return 0 + return self.state + else if self.matchAndPaint("endif", self.FLAG_COMMENT, self.cKeywordQualifier): + self.paint(-1, self.FLAG_COMMENT) + if self.state == self.preprocessor_base_state: return 0 + return self.state - 1 + else: + self.paint(-1, self.FLAG_COMMENT) + return self.state + else: + self.paint(-1, self.FLAG_COMMENT) + return self.state + return None + +bind(CHighlighter) diff --git a/base/usr/share/bim/syntax/conf.krk b/base/usr/share/bim/syntax/conf.krk new file mode 100644 index 00000000..3191d642 --- /dev/null +++ b/base/usr/share/bim/syntax/conf.krk @@ -0,0 +1,25 @@ +from syntax import Highlighter, bind + +class ConfigHighlighter(Highlighter): + name = "conf" + extensions = ('.conf','.ini','.git/config','.cfg','.properties') + spaces = True + + def calculate(self): + if self.i == 0: + if self[0] == ';': + self.paintComment() + else if self[0] == '#': + self.paintComment() + else if self[0] == '[': + self.paint(1, self.FLAG_KEYWORD) + while self[0] and self[0] != ']': + self.paint(1, self.FLAG_KEYWORD) + if self[0] == ']': + self.paint(1, self.FLAG_KEYWORD) + else: + while self[0] and self[0] != '=': + self.paint(1, self.FLAG_TYPE) + return None + +bind(ConfigHighlighter) diff --git a/base/usr/share/bim/syntax/css.krk b/base/usr/share/bim/syntax/css.krk new file mode 100644 index 00000000..53dab2c0 --- /dev/null +++ b/base/usr/share/bim/syntax/css.krk @@ -0,0 +1,166 @@ +from syntax import Highlighter, bind +from syntax.c import CHighlighter + +class CSSHighlighter(Highlighter): + name = 'css' + extensions = ('.css', '.scss') + elements = [ + "a","abbr","address","area","article","aside","audio", + "b","base","bdi","bdo","blockquote","body","br","button", + "canvas","cite","code","col","colgroup","data","datalist", + "dd","del","details","dfn","dialog","div","dl","dt","em", + "embed","fieldset","figcaption","figure","footer","form", + "h1","h2","h3","h4","h5","h6","head","header","hr","html", + "i","iframe","img","input","ins","kbd","label","legend", + "li","link","main","map","mark","meta","meter","nav", + "noscript","object","ol","optgroup","option","output", + "p","param","picture","pre","progress","q","rp","rt", + "ruby","s","samp","script","section","select","small", + "source","span","strong","style","sub","summary","sup", + "svg","table","tbody","td","template","textarea","tfoot", + "th","thead","time","title","tr","track","u","ul","var", + "video","wbr","hgroup","*", + ] + properties = [ + "align-content","align-items","align-self","all","animation", + "animation-delay","animation-direction","animation-duration", + "animation-fill-mode","animation-iteration-count","animation-name", + "animation-play-state","animation-timing-function","backface-visibility", + "background","background-attachment","background-blend-mode","background-clip", + "background-color","background-image","background-origin","background-position", + "background-repeat","background-size","border","border-bottom","border-bottom-color", + "border-bottom-left-radius","border-bottom-right-radius","border-bottom-style", + "border-bottom-width","border-collapse","border-color","border-image","border-image-outset", + "border-image-repeat","border-image-slice","border-image-source","border-image-width", + "border-left","border-left-color","border-left-style","border-left-width", + "border-radius","border-right","border-right-color","border-right-style","border-right-width", + "border-spacing","border-style","border-top","border-top-color","border-top-left-radius", + "border-top-right-radius","border-top-style","border-top-width","border-width", + "bottom","box-decoration-break","box-shadow","box-sizing","break-after", + "break-before","break-inside","caption-side","caret-color","@charset", + "clear","clip","color","column-count","column-fill","column-gap","column-rule","column-rule-color", + "column-rule-style","column-rule-width","column-span","column-width","columns","content", + "counter-increment","counter-reset","cursor","direction","display","empty-cells", + "filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink", + "flex-wrap","float","font","@font-face","font-family","font-feature-settings","@font-feature-values", + "font-kerning","font-language-override","font-size","font-size-adjust","font-stretch","font-style", + "font-synthesis","font-variant","font-variant-alternates","font-variant-caps","font-variant-east-asian", + "font-variant-ligatures","font-variant-numeric","font-variant-position","font-weight", + "grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column", + "grid-column-end","grid-column-gap","grid-column-start","grid-gap","grid-row","grid-row-end", + "grid-row-gap","grid-row-start","grid-template","grid-template-areas","grid-template-columns", + "grid-template-rows","hanging-punctuation","height","hyphens","image-rendering","@import", + "isolation","justify-content","@keyframes","left","letter-spacing","line-break","line-height", + "list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom", + "margin-left","margin-right","margin-top","max-height","max-width","@media","min-height", + "min-width","mix-blend-mode","object-fit","object-position","opacity","order","orphans", + "outline","outline-color","outline-offset","outline-style","outline-width","overflow", + "overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right", + "padding-top","page-break-after","page-break-before","page-break-inside","perspective", + "perspective-origin","pointer-events","position","quotes","resize","right","scroll-behavior", + "tab-size","table-layout","text-align","text-align-last","text-combine-upright","text-decoration", + "text-decoration-color","text-decoration-line","text-decoration-style","text-indent","text-justify", + "text-orientation","text-overflow","text-shadow","text-transform","text-underline-position", + "top","transform","transform-origin","transform-style","transition","transition-delay", + "transition-duration","transition-property","transition-timing-function","unicode-bidi", + "user-select","vertical-align","visibility","white-space","widows","width","word-break", + "word-spacing","word-wrap","writing-mode","font-decoration", + ] + values = [ + "inline","block","inline-block","none","oblique", + "transparent","thin","dotted","sans-serif", + "rgb","rgba","bold","italic","underline","context-box", + "monospace","serif","sans-serif","pre-wrap", + "relative","baseline","hidden","solid","inherit","normal", + "button","pointer","border-box","default","textfield", + "collapse","top","bottom","avoid","table-header-group", + "middle","absolute","rect","left","center","right", + "ellipsis","nowrap","table","both","uppercase","lowercase","help", + "static","table-cell","table-column","scroll","touch","auto", + "not-allowed","inset","url","fixed","translate","alpha","fixed","device-width", + "table-row", + ] + states = [ + "focus","active","hover","link","visited","before","after", + "left","right","root","empty","target","enabled","disabled","checked","invalid", + "first-child","nth-child","not","last-child", + ] + def propertyQualifier(c): + if isinstance(c,int): + if c <= 0: return False + c = chr(c) + return self.isalnum(c) or c in '-@*!' + suffixes = [ + 'pt','px','pc','em','cm','mm', + 'ex','in','vw','vh','ch','rem', + 'vmin','vmax','s' + ] + def matchSuffixes(suffixes): + for s in suffixes: + if self.matchPrefix(s): + self.paint(len(s), self.FLAG_NUMERAL) + return + def calculate(): + if self.state < 1: + if self[0] == '/' and self[1] == '*': + if CHighlighter.paintCComment(self) == 1: return 1 + else if self[0] == '"': + self.paintSimpleString() + return 0 + else if self[-1] != '.' and self.findKeywords(self.elements, self.FLAG_KEYWORD, self.propertyQualifier): + return 0 + else if self[-1] != '.' and self.findKeywords(self.properties, self.FLAG_TYPE, self.propertyQualifier): + return 0 + else if self.matchPrefix('-moz-'): + self.paint(5, self.FLAG_ESCAPE) + while self[0] and self.propertyQualifier(self[0]): self.paint(1, self.FLAG_TYPE) + else if self.matchPrefix('-webkit-'): + self.paint(8, self.FLAG_ESCAPE) + while self[0] and self.propertyQualifier(self[0]): self.paint(1, self.FLAG_TYPE) + else if self.matchPrefix('-ms-'): + self.paint(4, self.FLAG_ESCAPE) + while self[0] and self.propertyQualifier(self[0]): self.paint(1, self.FLAG_TYPE) + else if self.matchPrefix('-o-'): + self.paint(3, self.FLAG_ESCAPE) + while self[0] and self.propertyQualifier(self[0]): self.paint(1, self.FLAG_TYPE) + else if self[0] == ':': + self.skip() + if self.findKeywords(self.states, self.FLAG_PRAGMA, self.propertyQualifier): return 0 + while self[0] and self[0] != ';': + if self.findKeywords(self.values, self.FLAG_NUMERAL, self.propertyQualifier): + continue + else if self[0] == '"': + self.paintSimpleString() + continue + else if self[0] == "'": + self.paintSingleString() + continue + else if self[0] == '{': + self.skip() + return 0 + else if self[0] == '#': + self.paint(1, self.FLAG_NUMERAL) + while self.isxdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) + else if self.isdigit(self[0]): + while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) + if self[0] == '.': + self.paint(1, self.FLAG_NUMERAL) + while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) + if self[0] == '%': self.paint(1, self.FLAG_NUMERAL) + else: self.matchSuffixes(self.suffixes) + else if self.matchAndPaint("!important", self.FLAG_PRAGMA, self.propertyQualifier): + continue + else if self[0]: + self.skip() + return 0 + else if not self[0]: + return None + else: + self.skip() + return 0 + else if self.state == 1: + if CHighlighter.paintCComment(self) == 1: return 1 + return 0 + return None + +bind(CSSHighlighter) diff --git a/base/usr/share/bim/syntax/ctags.krk b/base/usr/share/bim/syntax/ctags.krk new file mode 100644 index 00000000..453cb483 --- /dev/null +++ b/base/usr/share/bim/syntax/ctags.krk @@ -0,0 +1,17 @@ +from syntax import Highlighter, bind + +class CtagsHighlighter(Highlighter): + name = 'ctags' + extensions = ('tags',) + def calculate(): + if self.i == 0: + if self[0] == '!': + return self.paintComment() + while self[0] and self[0] != '\t': self.paint(1, self.FLAG_TYPE) + if self[0] == '\t': self.skip() + while self[0] and self[0] != '\t': self.paint(1, self.FLAG_NUMERAL) + if self[0] == '\t': self.skip() + while self[0] and not (self[0] == ';' and self[1] == '"'): self.paint(1, self.FLAG_KEYWORD) + return None + +bind(CtagsHighlighter) diff --git a/base/usr/share/bim/syntax/diff.krk b/base/usr/share/bim/syntax/diff.krk new file mode 100644 index 00000000..2e62b310 --- /dev/null +++ b/base/usr/share/bim/syntax/diff.krk @@ -0,0 +1,18 @@ +from syntax import Highlighter, bind + +class DiffHighlighter(Highlighter): + name = 'diff' + extensions = ('.patch','.diff') + mapping = { + '+': Highlighter.FLAG_DIFFPLUS, + '-': Highlighter.FLAG_DIFFMINUS, + '@': Highlighter.FLAG_TYPE, + ' ': Highlighter.FLAG_KEYWORD, + } + def calculate(): + if self.i == 0: + let flag = self.mapping[self[0]] if self[0] in self.mapping else 0 + self.paint(-1, flag) + return None + +bind(DiffHighlighter) diff --git a/base/usr/share/bim/syntax/dirent.krk b/base/usr/share/bim/syntax/dirent.krk new file mode 100644 index 00000000..0e155071 --- /dev/null +++ b/base/usr/share/bim/syntax/dirent.krk @@ -0,0 +1,21 @@ +''' + For highlighting bim's built-in directory browser. +''' +from syntax import Highlighter, bind + +class DirentHighlighter(Highlighter): + name = 'dirent' + extensions = () + def calculate(): + if self.i == 0: + if self[0] == '#': + return self.paintComment() + else if self[0] == 'd': + self.paint(1, self.FLAG_COMMENT) + self.paint(-1, self.FLAG_KEYWORD) + else if self[0] == 'f': + self.paint(1, self.FLAG_COMMENT) + self.paint(-1, self.FLAG_NONE) + return None + +bind(DirentHighlighter) diff --git a/base/usr/share/bim/syntax/doxygen.krk b/base/usr/share/bim/syntax/doxygen.krk new file mode 100644 index 00000000..7ec8fcb7 --- /dev/null +++ b/base/usr/share/bim/syntax/doxygen.krk @@ -0,0 +1,48 @@ + +def _make_dox(): + from syntax import Highlighter + + let doxygen_keywords_at = [ + "@author","@brief","@class","@short","@retval", + "@since","@return","@returns","@throws","@bug", + "@version","@deprecated","@attention","@note", + ] + + let doxygen_word_commands = { + '@param': Highlighter.FLAG_TYPE, + '@exception': Highlighter.FLAG_PRAGMA, + '@def': Highlighter.FLAG_TYPE, + '@see': Highlighter.FLAG_LINK, + '@p': Highlighter.FLAG_TYPE, + '@c': Highlighter.FLAG_NONE, + '@file': Highlighter.FLAG_LINK, + '@memberof': Highlighter.FLAG_TYPE, + '@extends': Highlighter.FLAG_TYPE, + '@mainpage': Highlighter.FLAG_STRING, + '@section': Highlighter.FLAG_BOLD, + '@subsection': Highlighter.FLAG_BOLD, + '@package': Highlighter.FLAG_TYPE, + '@ref': Highlighter.FLAG_LINK, + } + + def doxygen_qualifier(c): + if isinstance(c,int): + if c > 0: c = chr(c) + else: return False + return Highlighter.isalnum(c) or c in '_@' + + def tryDoxygenComment(b,defaultflag=b.FLAG_COMMENT): + if b[0] == '@': + if not b.findKeywords(doxygen_keywords_at, b.FLAG_ESCAPE, doxygen_qualifier): + for keyword, flag in doxygen_word_commands.items(): + if b.matchAndPaint(keyword, b.FLAG_ESCAPE, doxygen_qualifier): + while b[0] == ' ': b.skip() + while b[0] and b[0] != ' ': b.paint(1, flag) + return True + b.paint(1, defaultflag) + return True + return False + + return tryDoxygenComment + +let tryDoxygenComment = _make_dox() diff --git a/base/usr/share/bim/syntax/esh.krk b/base/usr/share/bim/syntax/esh.krk new file mode 100644 index 00000000..9a5ea7a3 --- /dev/null +++ b/base/usr/share/bim/syntax/esh.krk @@ -0,0 +1,96 @@ +''' +Shell highlighter for ToaruOS's 'experimental shell'. +''' +from syntax import Highlighter, bind + +class EshHighlighter(Highlighter): + name = 'esh' + extensions = ('.eshrc','.yutanirc') + keywords = [ + "cd","exit","export","help","history","if","empty?", + "equals?","return","export-cmd","source","exec","not","while", + "then","else","echo", + ] + def variableQualifier(c): + return self.isalnum(c) or c == '_' + def keywordQualifier(c): + return self.isalnum(c) or c in '?_-' + def paintVariable(): + if self[0] == '{': + self.paint(1, self.FLAG_TYPE) + while self[0] != '}' and self[0]: + self.paint(1, self.FLAG_TYPE) + if self[0] == '}': + self.paint(1, self.FLAG_TYPE) + else: + if self[0] in '?$#': + self.paint(1, self.FLAG_TYPE) + else: + while self.variableQualifier(self[0]): + self.paint(1, self.FLAG_TYPE) + def paintString(): + let last = None + while self[0]: + if last != '\\' and self[0] == '"': + self.paint(1, self.FLAG_STRING) + return 0 + else if self[0] == '$': + self.paint(1, self.FLAG_TYPE) + self.paintVariable() + last = None + else if self[0]: + last = self[0] + self.paint(1, self.FLAG_STRING) + return 2 + def paintSingle(): + let last = None + while self[0]: + if last != '\\' and self[0] == "'": + self.paint(1, self.FLAG_STRING) + return 0 + else if self[0]: + last = self[0] + self.paint(1, self.FLAG_STRING) + return 1 + def calculate(): + if self.state == 1: + return self.paintSingle() + else if self.state == 2: + return self.paintString() + if self[0] == '#': + return self.paintComment() + else if self[0] == '$': + self.paint(1, self.FLAG_TYPE) + self.paintVariable() + return 0 + else if self[0] == "'": + self.paint(1, self.FLAG_STRING) + return self.paintSingle() + else if self[0] == '"': + self.paint(1, self.FLAG_STRING) + return self.paintString() + else if self.matchAndPaint('export', self.FLAG_KEYWORD, self.keywordQualifier): + while self[0] == ' ': self.skip() + while self.keywordQualifier(self[0]): self.paint(1, self.FLAG_TYPE) + return 0 + else if self.matchAndPaint('export-cmd', self.FLAG_KEYWORD, self.keywordQualifier): + while self[0] == ' ': self.skip() + while self.keywordQualifier(self[0]): self.paint(1, self.FLAG_TYPE) + return 0 + else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.keywordQualifier): + return 0 + else if self.isdigit(self[0]): + while self.isdigit(self[0]): + self.paint(1, self.FLAG_NUMERAL) + return 0 + else if self[0]: + self.skip() + return 0 + return None + +try: + import os + if os.uname['sysname'] == 'toaru': + EshHighlighter.extensions = ('.eshrc','.yutanirc','.sh') + +bind(EshHighlighter) diff --git a/base/usr/share/bim/syntax/git.krk b/base/usr/share/bim/syntax/git.krk new file mode 100644 index 00000000..fc5c09e9 --- /dev/null +++ b/base/usr/share/bim/syntax/git.krk @@ -0,0 +1,38 @@ +from syntax import Highlighter, bind + +class GitcommitHighlighter(Highlighter): + name = 'gitcommit' + extensions = ('COMMIT_EDITMSG',) + def calculate(): + if self.i == 0 and self[0] == '#': + return self.paintComment() + else if self.lineno == 0: + while self[0] and self.i < 50: + self.paint(1, self.FLAG_KEYWORD) + self.paint(-1, self.FLAG_DIFFMINUS) + else if self.lineno == 1: + self.paint(-1, self.FLAG_DIFFMINUS) + else if self[0]: + self.paint(-1, self.FLAG_NONE) + return None + +bind(GitcommitHighlighter) + +class GitrebaseHighlighter(Highlighter): + name = 'gitrebase' + extensions = ('git-rebase-todo',) + commands = [ + "p","r","e","s","f","x","d", + "pick","reword","edit","squash","fixup", + "exec","drop" + ] + def calculate(): + if self.i == 0 and self[0] == '#': + return self.paintComment() + else if self.i == 0 and self.findKeywords(self.commands, self.FLAG_KEYWORD, self.cKeywordQualifier): + while self[0] == ' ': self.skip() + while self.isxdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) + return None + return None + +bind(GitrebaseHighlighter) diff --git a/base/usr/share/bim/syntax/groovy.krk b/base/usr/share/bim/syntax/groovy.krk new file mode 100644 index 00000000..fdc36075 --- /dev/null +++ b/base/usr/share/bim/syntax/groovy.krk @@ -0,0 +1,79 @@ +from syntax import Highlighter, bind +from syntax.c import CHighlighter + +class GroovyHighlighter(Highlighter): + name = 'groovy' + extensions = ('.groovy','.jenkinsfile','.gradle') + keywords = [ + "as","assert","break","case", + "catch","class","const","continue", + "def","default","do","else","enum", + "extends","finally","for", + "goto","if","implements","import", + "in","instanceof","interface","new", + "package","return","super", + "switch","throw","throws", + "trait","try","while","final", + ] + constants = [ + 'true','false','null','this', + ] + primitives = [ + 'byte','char','short','int','long','BigInteger' + ] + def paintTriple(): + while self[0]: + if self[0] == "'": + self.paint(1, self.FLAG_STRING) + if self[0] == "'" and self[1] == "'": + self.paint(2, self.FLAG_STRING) + return 0 + else: + self.paint(1, self.FLAG_STRING) + return 2 + def calculate(): + if self.state <= 0: + if self.lineno == 0 and self.i == 0 and self[0] == '#': + self.paintComment() + return None + else if self[0] == '/' and self[1] == '/': + self.paintComment() + return None + else if self[0] == '/' and self[1] == '*': + if CHighlighter.paintCComment(self) == 1: return 1 + else if self[0] == '"': + self.paintSimpleString() + return 0 + else if self[0] == "'": + self.paintSingleString() + return 0 + else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): + return 0 + else if self.findKeywords(self.primitives, self.FLAG_TYPE, self.cKeywordQualifier): + return 0 + else if self.findKeywords(self.constants, self.FLAG_NUMERAL, self.cKeywordQualifier): + return 0 + else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): + CHighlighter.paintCNumeral(self) + return 0 + else if self[0] == '@': + self.paint(1, self.FLAG_TYPE) + while self.cKeywordQualifier(self[0]): + self.paint(1, self.FLAG_TYPE) + return 0 + else if not self.cKeywordQualifier(self[-1]) and self[0] in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': + while self.cKeywordQualifier(self[0]): + self.paint(1, self.FLAG_TYPE) + return 0 + else if self[0]: + self.skip() + return 0 + return None + else if self.state == 1: + if CHighlighter.paintCComment(self) == 1: return 1 + return 0 + else if self.state == 2: + return self.paintTriple() + return None + +bind(GroovyHighlighter) diff --git a/base/usr/share/bim/syntax/hosts.krk b/base/usr/share/bim/syntax/hosts.krk new file mode 100644 index 00000000..14598399 --- /dev/null +++ b/base/usr/share/bim/syntax/hosts.krk @@ -0,0 +1,17 @@ +from syntax import Highlighter, bind + +class HostsHighlighter(Highlighter): + name = 'hosts' + extensions = ('hosts',) + + def calculate(): + if self.i == 0: + if self[0] == '#': + return self.paintComment() + while self[0] != '\t' and self[0] != ' ' and self[0]: + self.paint(1, self.FLAG_NUMERAL) + while self[0]: + self.paint(1, self.FLAG_TYPE) + return None + +bind(HostsHighlighter) diff --git a/base/usr/share/bim/syntax/java.krk b/base/usr/share/bim/syntax/java.krk new file mode 100644 index 00000000..e962d917 --- /dev/null +++ b/base/usr/share/bim/syntax/java.krk @@ -0,0 +1,141 @@ +from syntax import Highlighter, bind +from syntax.c import CHighlighter + +class JavaHighlighter(Highlighter): + name = 'java' + extensions = ('.java',) + keywords = [ + "assert","break","case","catch","class","continue", + "default","do","else","enum","exports","extends","finally", + "for","if","implements","instanceof","interface","module","native", + "new","requires","return","throws", + "strictfp","super","switch","synchronized","this","throw","try","while", + ] + types = [ + "var","boolean","void","short","long","int","double","float","enum","char", + "private","protected","public","static","final","transient","volatile","abstract", + ] + special = [ + "true","false","import","package","null", + ] + at_comments = [ + "@author","@see","@since","@return","@throws", + "@version","@exception","@deprecated", + ] + brace_comments = [ + "{@docRoot","{@inheritDoc","{@link","{@linkplain", + "{@value","{@code","{@literal","{@serial", + "{@serialData","{@serialField", + ] + def atKeywordQualifier(c): + if isinstance(c,int) and c > 0: c = chr(c) + return self.isalnum(c) or c in '_@' + def braceKeywordQualifier(c): + if isinstance(c,int) and c > 0: c = chr(c) + return self.isalnum(c) or c in '{@_' + def keywordQualifier(c): + return self.cKeywordQualifier(c) + def paintJavaComment(): + let last + while self[0]: + if self.commentBuzzwords(): continue + if self[0] == '@': + if not self.findKeywords(self.at_comments, self.FLAG_ESCAPE, self.atKeywordQualifier): + if self.matchAndPaint('@param', self.FLAG_ESCAPE, self.atKeywordQualifier): + while self[0] == ' ': self.skip() + while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_TYPE) + else: + self.paint(1, self.FLAG_COMMENT) + else if self[0] == '{': + if self.findKeywords(self.brace_comments, self.FLAG_ESCAPE, self.braceKeywordQualifier): + while self[0] != '}' and self[0]: + self.paint(1, self.FLAG_ESCAPE) + if self[0] == '}': self.paint(1, self.FLAG_ESCAPE) + else: + self.paint(1, self.FLAG_COMMENT) + else if self[0] == '<': + let isTag = False + for i = 1; self[i]; i++: + if self[i] == '>': + isTag = True + break + if not self.isalnum(self[i]) and self[i] != '/': + isTag = 0 + break + if isTag: + self.paint(1, self.FLAG_TYPE) + while self[0] and self[0] != '>': + if self[0] == '/': self.paint(1, self.FLAG_TYPE) + else: self.paint(1, self.FLAG_KEYWORD) + if self[0] == '>': self.paint(1, self.FLAG_TYPE) + else: + self.paint(1, self.FLAG_COMMENT) + else if last == '*' and self[0] == '/': + self.paint(1, self.FLAG_COMMENT) + return 0 + else: + last = self[0] + self.paint(1, self.FLAG_COMMENT) + return 1 + def calculate(): + if self.state <= 0: + if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): + CHighlighter.paintCNumeral(self) + return 0 + else if self[0] == '/' and self[1] == '/': + self.paintComment() + else if self[0] == '/' and self[1] == '*': + if self.paintJavaComment() == 1: return 1 + else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.keywordQualifier): + return 0 + else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier): + return 0 + else if self.findKeywords(self.special, self.FLAG_NUMERAL, self.cKeywordQualifier): + return 0 + else if self[0] == '"': + self.paintSimpleString() + return 0 + else if self[0] == "'": + CHighlighter.paintCChar(self) + return 0 + else if self[0] == '@': + self.paint(1, self.FLAG_PRAGMA) + while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_PRAGMA) + return 0 + else if self[0]: + self.skip() + return 0 + else if self.state == 1: + if self.paintJavaComment() == 1: return 1 + return 0 + return None + +bind(JavaHighlighter) + +class KotlinHighlighter(JavaHighlighter): + name = 'kotlin' + extensions = ('.kt',) + keywords = [ + "as","as?","break","class","continue","do","else","false","for", + "fun","if","in","!in","interface","is","!is","null","object", + "package","return","super","this","throw","true","try","typealias", + "typeof","val","var","when","while", + "by","catch","constructor","delegate","dynamic","field","file", + "finally","get","import","init","param","property","receiver", + "set","setparam","where", + "actual","abstract","annotation","companion","const", + "crossinline","data","enum","expect","external","final", + "infix","inner","internal","lateinit","noinline","open", + "operator","out","override","private","protected","public", + "reified","sealed","suspend","tailrec","vararg", + "field","it","inline", + ] + types = [ + "Byte","Short","Int","Long", + "Float","Double","String", + ] + specials = [] + def keywordQualifier(c): + return self.isalnum(c) or c in '?!_' + +bind(KotlinHighlighter) diff --git a/base/usr/share/bim/syntax/javascript.krk b/base/usr/share/bim/syntax/javascript.krk new file mode 100644 index 00000000..70eb9720 --- /dev/null +++ b/base/usr/share/bim/syntax/javascript.krk @@ -0,0 +1,97 @@ +from syntax import Highlighter, bind +from syntax.c import CHighlighter + +class JavascriptHighlighter(Highlighter): + name = 'javascript' + extensions = ('.js','.jsx','.ts','.tsx') + keywords = [ + "abstract","arguments","from", + "await","break","case","catch","class","const", + "continue","debugger","default","delete","do","else","enum","eval", + "export","extends","final","finally","for","function","goto", + "if","implements","import","in","instanceof","interface","let","long", + "native","new","package","private","protected","public","return", + "static","super","switch","synchronized","this","throw","throws", + "transient","true","try","typeof","volatile","while","with","yield", + ] + types = [ + "int","float","double","short","var","void","byte","char","boolean", + ] + special = ['true','false','null'] + def paintJSFormatString(): + while self[0]: + if self[0] == '\\' and self[1] == '`': + self.paint(2, self.FLAG_ESCAPE) + else if self[0] == '`': + self.paint(1, self.FLAG_STRING) + return 0 + else if self[0] == '\\': + self.paint(2, self.FLAG_ESCAPE) + else if self[0] == '$' and self[1] == '{': + self.paint(2, self.FLAG_NUMERAL) + while self[0] and self[0] != '}': + self.paint(1, self.FLAG_NUMERAL) + self.paint(1, self.FLAG_NUMERAL) + else: + self.paint(1, self.FLAG_STRING) + return 1 + def calculate(): + if self.state < 1: + if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): + CHighlighter.paintCNumeral(self) + return 0 + else if not self.cKeywordQualifier(self[-1]) and self[0] in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': + while self[0] and self.cKeywordQualifier(self[0]): + self.paint(1, self.FLAG_TYPE) + return 0 + else if self[0] == '/' and self[1] == '/': + self.paintComment() + else if self[0] == '/' and self[1] == '*': + if CHighlighter.paintCComment(self) == 1: return 1 + else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): + return 0 + else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier): + return 0 + else if self.findKeywords(self.special, self.FLAG_NUMERAL, self.cKeywordQualifier): + return 0 + else if self[0] == '=' and self[1] == '>': + self.paint(2, self.FLAG_PRAGMA) + return 0 + else if self[0] == ':' and self.cKeywordQualifier(self[-1]): + let start = self.i + while self[-1] and self.cKeywordQualifier(self[-1]): + self.rewind(1) + while self[0] and self.i != start: + self.paint(1, self.FLAG_TYPE) + self.paint(1, self.FLAG_PRAGMA) + return 0 + else if self[0] == '<': + self.paint(1, self.FLAG_TYPE) + while self[0] and (self[0] == '/' or self.cKeywordQualifier(self[0])): + self.paint(1, self.FLAG_TYPE) + return 0 + else if self[0] == '>': + self.paint(1, self.FLAG_TYPE) + return 0 + else if self[0] == '"': + self.paintSimpleString() + return 0 + else if self[0] == "'": + self.paintSingleString() + return 0 + else if self[0] == '`': + self.paint(1, self.FLAG_STRING) + if self.paintJSFormatString(): return 2 + return 0 + else if self[0]: + self.skip() + return 0 + else if self.state == 1: + if CHighlighter.paintCComment(self) == 1: return 1 + return 0 + else if self.state == 2: + if self.paintJSFormatString(): return 2 + return 0 + return None + +bind(JavascriptHighlighter) diff --git a/base/usr/share/bim/syntax/json.krk b/base/usr/share/bim/syntax/json.krk new file mode 100644 index 00000000..4270e45e --- /dev/null +++ b/base/usr/share/bim/syntax/json.krk @@ -0,0 +1,43 @@ +from syntax import Highlighter, bind + +class JsonHighlighter(Highlighter): + name = 'json' + extensions = ('.json',) + keywords = ['true','false','null'] + def calculate(): + while self[0]: + if self[0] == '"': + let start = self.i + self.paintSimpleString() + let end = self.i + while self[0] == ' ': self.skip() + if self[0] == ':': + self.rewind(end-start) + self.paint(1, self.FLAG_ESCAPE) + while self.i < end-1: + self.paint(1, self.FLAG_KEYWORD) + if self[0] == '"': + self.paint(1, self.FLAG_ESCAPE) + return 0 + else if self[0] == '-' or self.isdigit(self[0]): + if self[0] == '-': self.paint(1, self.FLAG_NUMERAL) + if self[0] == '0': + self.paint(1, self.FLAG_NUMERAL) + else: + while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) + if self[0] == '.': + self.paint(1, self.FLAG_NUMERAL) + while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) + if self[0] == 'e' or self[0] == 'E': + self.paint(1, self.FLAG_NUMERAL) + if self[0] == '+' or self[0] == '-': + self.paint(1, self.FLAG_NUMERAL) + while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) + else if self.findKeywords(self.keywords, self.FLAG_NUMERAL, self.cKeywordQualifier): + continue + else: + self.skip() + return 0 + return None + +bind(JsonHighlighter) diff --git a/base/usr/share/bim/syntax/krk.krk b/base/usr/share/bim/syntax/krk.krk new file mode 100644 index 00000000..af560c56 --- /dev/null +++ b/base/usr/share/bim/syntax/krk.krk @@ -0,0 +1,174 @@ +from bim import getCommands +from syntax import Highlighter, bind +from syntax.doxygen import tryDoxygenComment + +class KrkHighlighter(Highlighter): + name = 'krk' + extensions = ('.krk',) + spaces = True + + doxygenDocstrings = False + enableChecking = False + checkKrkCode = None + + keywords = [ + 'and','class','def','else','export','for','if','in','import','let','not', + 'or','return','while','try','except','raise','continue','break','as','from', + 'elif', 'lambda', 'pass', 'with', 'is', 'del', 'assert', 'yield', 'finally', + ] + + types = [ + 'self','super','len','str','int','float','dir','repr','list','dict','range', + 'object','exception','isinstance','type','print','tuple','bool','any','all', + 'hex','ord','chr','bytes','set','getattr','setattr','input','zip','enumerate', + 'property','staticmethod','classmethod','filter','min','max','id','map','bin', + 'sum','sorted','issubclass','hasattr','delattr', + ] + + special = [ + 'True', 'False', 'None' + ] + + exceptions = [ + 'Exception', 'TypeError', 'ArgumentError', 'IndexError', 'KeyError', + 'AttributeError', 'NameError', 'ImportError', 'IOError', 'ValueError', + 'KeyboardInterrupt', 'ZeroDivisionError', 'NotImplementedError', 'SyntaxError', + 'AssertionError', + ] + + def paintString(self, strType, isTriple, isFormat=False): + while self[0] != None: + if self[0] == '\\' and self[1] == strType: + self.paint(2, self.FLAG_ESCAPE) + else if self[0] == strType: + self.paint(1, self.FLAG_STRING) + if isTriple: + if self[0] == strType and self[1] == strType: + self.paint(2, self.FLAG_STRING) + return 0 + else: + return 0 + else if self[0] == '\\': + if self[1] == 'x': + self.paint(4, self.FLAG_ESCAPE) + else if self[1] == 'u': + self.paint(6, self.FLAG_ESCAPE) + else if self[1] == 'U': + self.paint(10, self.FLAG_ESCAPE) + else if self[1] == None and not isTriple: + self.paint(1, self.FLAG_ESCAPE) + return 3 if strType == '"' else (14 if isFormat else 4) + else if self[1] in '\\\'"abfnrtv[': + self.paint(2, self.FLAG_ESCAPE) + else: + self.paint(1, self.FLAG_STRING) + else if isFormat and self[0] == '{': + if self[1] == '}': + self.paint(-1, self.FLAG_ERROR) + return None + self.paint(1, self.FLAG_ESCAPE) + let x = 0 + while self[0]: + if self[0] == '{': + x++ + else if self[0] == '}': + if x == 0: + self.paint(1, self.FLAG_ESCAPE) + break + x-- + else if self[0] == strType and not isTriple: + self.paint(-1, self.FLAG_ERROR) + return None + else if self.findKeywords(self.keywords, self.FLAG_ESCAPE, self.cKeywordQualifier): + continue + else if self[-1] != '.' and self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier): + continue + else if self.findKeywords(self.exceptions, self.FLAG_PRAGMA, self.cKeywordQualifier): + continue + self.paint(1, self.FLAG_NUMERAL) + else if self.doxygenDocstrings and isTriple and tryDoxygenComment(self, self.FLAG_STRING): + continue + else: + self.paint(1, self.FLAG_STRING) + if isTriple: return int(strType == "'") + (11 if isFormat else 1) + return 0 + + def paintNumeral(self): + if self[0] == '0' and (self[1] == 'x' or self[1] == 'X'): + self.paint(2, self.FLAG_NUMERAL) + while self.isxdigit(self[0]): + self.paint(1, self.FLAG_NUMERAL) + else if self[0] == '0' and (self[1] == 'o' or self[1] == 'O'): + self.paint(2, self.FLAG_NUMERAL) + while self[0] in '01234567': + self.paint(1, self.FLAG_NUMERAL) + else if self[0] == '0' and (self[1] == 'b' or self[1] == 'B'): + self.paint(2, self.FLAG_NUMERAL) + while self[0] == '0' or self[0] == '1': + self.paint(1, self.FLAG_NUMERAL) + else: + while self.isdigit(self[0]): + self.paint(1, self.FLAG_NUMERAL) + if self[0] == '.' and self.isdigit(self[1]): + self.paint(1, self.FLAG_NUMERAL) + while self.isdigit(self[0]): + self.paint(1, self.FLAG_NUMERAL) + return 0 + + def paintDoxyComment(self): + while self[0] is not None: + if tryDoxygenComment(self): continue + else if self.commentBuzzwords(): continue + else: self.paint(1, self.FLAG_COMMENT) + + def calculate(self): + if self.enableChecking and self.i == 0: self.checkKrkCode() + if self.state <= 0: + if self[0] == '#': + self.paintDoxyComment() + else if self[0] == '@': + self.paint(1, self.FLAG_TYPE) + while self.cKeywordQualifier(self[0]): + self.paint(1, self.FLAG_TYPE) + return 0 + else if self[0] == '"' or self[0] == "'": + let strType = self[0] + if self[-1] == 'f': + self.rewind(1) + self.paint(1, self.FLAG_KEYWORD) + if self[1] == strType and self[2] == strType: + self.paint(3, self.FLAG_STRING) + return self.paintString(strType,True,self[-4]=='f') + self.paint(1, self.FLAG_STRING) + return self.paintString(strType,False,self[-2]=='f') + else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): + return 0 + else if self[-1] != '.' and self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier): + return 0 + else if self.findKeywords(self.special, self.FLAG_NUMERAL, self.cKeywordQualifier): + return 0 + else if self.findKeywords(self.exceptions, self.FLAG_PRAGMA, self.cKeywordQualifier): + return 0 + else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): + self.paintNumeral() + return 0 + else if self[0] != None: + self.skip() + return 0 + else if self.state >= 1: + return self.paintString("'\""[self.state % 2], (self.state % 10) <= 2, self.state > 10) + return None + +bind(KrkHighlighter) + +class BimcmdHighlighter(KrkHighlighter): + name = 'bimcmd' + extensions = ('.bim3rc',) + + def calculate(self): + if self.state <= 0 and self[-1] != '.': + if self.findKeywords(getCommands(), self.FLAG_KEYWORD, self.cKeywordQualifier): + return 0 + return super().calculate() + +bind(BimcmdHighlighter) diff --git a/base/usr/share/bim/syntax/lisp.krk b/base/usr/share/bim/syntax/lisp.krk new file mode 100644 index 00000000..5561e993 --- /dev/null +++ b/base/usr/share/bim/syntax/lisp.krk @@ -0,0 +1,41 @@ +from syntax import Highlighter, bind + +class LispHighlighter(Highlighter): + name = 'lisp' + extensions = ('.lisp','.lsp','.cl') + parens = [ + Highlighter.FLAG_DIFFPLUS, + Highlighter.FLAG_TYPE, + Highlighter.FLAG_PRAGMA, + Highlighter.FLAG_KEYWORD, + ] + def parenFromState(i): + return self.parens[i % len(self.parens)] + def calculate(): + if self.state == -1: self.set_state(0) + while self[0]: + if self[0] == ';': + self.paintComment() + else if self[0] == '(': + self.paint(1, self.parenFromState(self.state)) + self.set_state(self.state+1) + while self[0] and self[0] not in ' ()': + self.paint(1, self.FLAG_KEYWORD) + else if self[0] == ')': + if self.state == 0: + self.paint(1, self.FLAG_ERROR) + else: + self.set_state(self.state-1) + self.paint(1, self.parenFromState(self.state)) + else if self[0] == ':': + while self[0] and self[0] not in ' ()': + self.paint(1, self.FLAG_PRAGMA) + else if self[0] == '"': + self.paintSimpleString() + else if self[0]: + self.skip() + if self.state == 0: return None + if not self[0]: return self.state + return None + +bind(LispHighlighter) diff --git a/base/usr/share/bim/syntax/make.krk b/base/usr/share/bim/syntax/make.krk new file mode 100644 index 00000000..ac58c09f --- /dev/null +++ b/base/usr/share/bim/syntax/make.krk @@ -0,0 +1,138 @@ +from syntax import Highlighter, bind + +class GnumakeHighlighter(Highlighter): + name = 'make' + extensions = ('Makefile','makefile','GNUmakefile','.mak') + commands = [ + "define","endef","undefine","ifdef","ifndef","ifeq","ifneq","else","endif", + "include","sinclude","override","export","unexport","private","vpath", + "-include", + ] + functions = [ + "subst","patsubst","findstring","filter","filter-out", + "sort","word","words","wordlist","firstword","lastword", + "dir","notdir","suffix","basename","addsuffix","addprefix", + "join","wildcard","realpath","abspath","error","warning", + "shell","origin","flavor","foreach","if","or","and", + "call","eval","file","value", + ] + targets = [ + "all", # Not really special, but highlight it 'cause I feel like it. + ".PHONY", ".SUFFIXES", ".DEFAULT", ".PRECIOUS", ".INTERMEDIATE", + ".SECONDARY", ".SECONDEXPANSION", ".DELETE_ON_ERROR", ".IGNORE", + ".LOW_RESOLUTION_TIME", ".SILENT", ".EXPORT_ALL_VARIABLES", + ".NOTPARALLEL", ".ONESHELL", ".POSIX", + ] + def commandQualifier(c): + if isinstance(c,int): c = chr(c) if c > 0 else '\x00' + return self.isalnum(c) or c in '_-.' + def makeCloseParen(): + self.paint(2, self.FLAG_TYPE) + self.findKeywords(self.functions, self.FLAG_KEYWORD, self.cKeywordQualifier) + let i = 1 + while self[0]: + if self[0] == '(': + i++ + if self[-1] == '$': + self.paint(1, self.FLAG_TYPE) + self.findKeywords(self.functions, self.FLAG_KEYWORD, self.cKeywordQualifier) + continue + else if self[0] == ')': + i-- + if i == 0: + self.paint(1, self.FLAG_TYPE) + return 0 + else if self[0] == '"': + self.paintSimpleString() + self.paint(1, self.FLAG_TYPE) + return 0 + def makeCloseBrace(): + self.paint(2, self.FLAG_TYPE) + while self[0]: + if self[0] == '}': + self.paint(1, self.FLAG_TYPE) + return 0 + self.paint(1, self.FLAG_TYPE) + return 0 + def makeVar(): + if self[1] == '(': + self.makeCloseParen() + else if self[1] == '{': + self.makeCloseBrace() + else: + self.paint(2, self.FLAG_TYPE) + def paintString(): + let strType = self[0] + self.paint(1, self.FLAG_STRING) + while self[0]: + if self[0] == strType and self[-1] != '\\': + self.paint(1, self.FLAG_STRING) + return + else if self[0] == '\\' and (self[1] == strType or strType == '"'): + self.paint(2, self.FLAG_ESCAPE) + else if self[0] == '$': + self.makeVar() + else: + self.paint(1, self.FLAG_STRING) + def variableOrComment(flag): + while self[0]: + if self[0] == '$': + self.makeVar() + else if self[0] == '#': + self.paintComment() + else if self[0] == "'": + self.paintString() + else if self[0] == '"': + self.paintString() + else: + self.paint(1, flag) + return 0 + def calculate(): + if self.i == 0 and self[0] == '\t': + self.skip() + if self[0] == '@': + self.paint(1, self.FLAG_KEYWORD) + self.variableOrComment(self.FLAG_NUMERAL) + else: + while self[0] == ' ': self.skip() + let whatisit + for i = 0; self[i]; i++: + if self[i] == ':' and self[i+1] != '=': + whatisit = 1 + break + else if self[i] == '=': + whatisit = 2 + break + else if self[i] == '#': + break + if not whatisit: + while self[0]: + if self[0] == '#': + self.paintComment() + else if self.findKeywords(self.commands, self.FLAG_KEYWORD, self.commandQualifier): + continue + else if self[0] == '$': + self.variableOrComment(self.FLAG_NONE) + else: + self.skip() + else if whatisit == 1: # rule + while self[0]: + if self[0] == '#': + self.paintComment() + else if self[0] == ':': + self.paint(1, self.FLAG_TYPE) + self.variableOrComment(self.FLAG_NONE) + else if self.findKeywords(self.targets, self.FLAG_KEYWORD, self.commandQualifier): + continue + else: + self.paint(1, self.FLAG_TYPE) + else if whatisit == 2: # variable + self.matchAndPaint('export', self.FLAG_KEYWORD, self.cKeywordQualifier) + while self[0] and self[0] not in '+=:?': + self.paint(1, self.FLAG_TYPE) + while self[0] and self[0] != '=': + self.skip() + self.variableOrComment(self.FLAG_NONE) + return None + +bind(GnumakeHighlighter) diff --git a/base/usr/share/bim/syntax/man.krk b/base/usr/share/bim/syntax/man.krk new file mode 100644 index 00000000..3350459c --- /dev/null +++ b/base/usr/share/bim/syntax/man.krk @@ -0,0 +1,33 @@ +from syntax import Highlighter, bind + +class ManpageHighlighter(Highlighter): + name = 'man' + extensions = ('.1','.2','.3','.4','.5','.6','.7','.8') + def calculate(): + while self[0]: + if self.i == 0 and self[0] == '.': + if self[1] == 'S' and self[2] == 'H' and self[3] == ' ': + self.paint(3, self.FLAG_KEYWORD) + self.paint(-1, self.FLAG_STRING) + else if self[1] == 'B' and self[2] == ' ': + self.paint(2, self.FLAG_KEYWORD) + self.paint(-1, self.FLAG_BOLD) + else if self.isalpha(self[1]): + self.paint(1, self.FLAG_KEYWORD) + while self[0] and self.isalpha(self[0]): + self.paint(1, self.FLAG_KEYWORD) + else if self[1] == '\\' and self[2] == '"': + self.paint(1, self.FLAG_COMMENT) + else: + self.skip() + else if self[0] == '\\': + if self[1] == 'f': + self.paint(2, self.FLAG_NUMERAL) + self.paint(1, self.FLAG_PRAGMA) + else: + self.paint(2, self.FLAG_ESCAPE) + else: + self.skip() + return None + +bind(ManpageHighlighter) diff --git a/base/usr/share/bim/syntax/markdown.krk b/base/usr/share/bim/syntax/markdown.krk new file mode 100644 index 00000000..0dd56f0e --- /dev/null +++ b/base/usr/share/bim/syntax/markdown.krk @@ -0,0 +1,90 @@ +from syntax import Highlighter, bind + +from syntax.c import CHighlighter +from syntax.py import PythonHighlighter +from syntax.krk import KrkHighlighter +from syntax.java import JavaHighlighter +from syntax.json import JsonHighlighter +from syntax.xml import XMLHighlighter, HTMLHighlighter +from syntax.diff import DiffHighlighter +from syntax.bash import BashHighlighter +from syntax.make import GnumakeHighlighter + +class MarkdownHighlighter(Highlighter): + name = 'markdown' + extensions = ('.md',) + nestables = [ + ('c', 100, CHighlighter), + ('py', 200, PythonHighlighter), + ('java', 300, JavaHighlighter), + ('json', 400, JsonHighlighter), + ('xml', 500, XMLHighlighter), + ('html', 600, HTMLHighlighter), + ('make', 700, GnumakeHighlighter), + ('diff', 800, DiffHighlighter), + ('bash', 900, BashHighlighter), + ('krk', 1000, KrkHighlighter), + ] + def nest(name, state, highlighter): + self.set_state(0 if self.state < 1 else self.state - state) + let sub = highlighter(self) + while True: + sub.set_state(sub.calculate()) + if sub.state != 0: break + if not sub.state or sub.state == -1: return state + return sub.state + state + def calculate(): + if self.state < 1: + if self.i == 0 and self[0] == '#': + while self[0] == '#': self.paint(1, self.FLAG_KEYWORD) + self.paint(-1, self.FLAG_BOLD) + return None + else if self.i == 0: + while self[0] == ' ': self.skip() + if self[0] == '`' and self[1] == '`' and self[2] == '`': + self.paint(3, self.FLAG_STRING) + for k,s,h in self.nestables: + if self.matchPrefix(k): + self.paint(len(k), self.FLAG_NUMERAL) + return s + return 1 + if self[0] == '`': + self.paint(1, self.FLAG_STRING) + while self[0]: + if self[0] == '`': + self.paint(1, self.FLAG_STRING) + return 0 + self.paint(1, self.FLAG_STRING) + return 0 + else if self[0] == '[': + self.skip() + while self[0] and self[0] != ']': + self.paint(1, self.FLAG_LINK) + if self[0] == ']': + self.skip() + if self[0] == '(': + self.skip() + while self[0] and self[0] != ')': + self.paint(1, self.FLAG_NUMERAL) + return 0 + else if self[0]: + self.skip() + return 0 + return None + else if self.state >= 1: + if self.i == 0: + for j=0;self[j];j++: + if self[j] == '`' and self[j+1] == '`' and self[j+2] == '`': + self.paint(j, self.FLAG_NONE) + self.paint(3, self.FLAG_STRING) + return None + if self.state == 1: + self.paint(-1, self.FLAG_STRING) + return 1 + else: + for k,state,highlighter in self.nestables: + if self.state < state + 99: + return self.nest(k, state, highlighter) + return None + +bind(MarkdownHighlighter) diff --git a/base/usr/share/bim/syntax/protobuf.krk b/base/usr/share/bim/syntax/protobuf.krk new file mode 100644 index 00000000..1bf1e7dc --- /dev/null +++ b/base/usr/share/bim/syntax/protobuf.krk @@ -0,0 +1,45 @@ +from syntax import Highlighter, bind +from syntax.c import CHighlighter + +class ProtobufHighlighter(Highlighter): + name = 'protobuf' + extensions = ('.proto',) + keywords = [ + "syntax","import","option","package","message","group","oneof", + "optional","required","repeated","default","extend","extensions","to","max","reserved", + "service","rpc","returns","stream", + ] + types = [ + "int32","int64","uint32","uint64","sint32","sint64", + "fixed32","fixed64","sfixed32","sfixed64", + "float","double","bool","string","bytes", + "enum", + ] + special = ['true','false'] + def calculate(): + if self.state < 1: + if self[0] == '/' and self[1] == '/': + self.paintComment() + else if self[0] == '/' and self[1] == '*': + if CHighlighter.paintCComment(self) == 1: return 1 + else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): + return 0 + else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier): + return 0 + else if self.findKeywords(self.special, self.FLAG_NUMERAL, self.cKeywordQualifier): + return 0 + else if self[0] == '"': + self.paintSimpleString() + return 0 + else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): + CHighlighter.paintCNumeral(self) + return 0 + else if self[0]: + self.skip() + return 0 + return None + else: + if CHighlighter.paintCComment(self) == 1: return 1 + return 0 + +bind(ProtobufHighlighter) diff --git a/base/usr/share/bim/syntax/py.krk b/base/usr/share/bim/syntax/py.krk new file mode 100644 index 00000000..3b0aff73 --- /dev/null +++ b/base/usr/share/bim/syntax/py.krk @@ -0,0 +1,157 @@ +from syntax import Highlighter, bind + +class PythonHighlighter(Highlighter): + name = 'python' + extensions = ('.py',) + spaces = True + keywords = [ + "class","def","return","del","if","else","elif","for","while","continue", + "break","assert","as","and","or","except","finally","from","global", + "import","in","is","lambda","with","nonlocal","not","pass","raise","try","yield", + ] + types = [ + "abs","all","any","ascii","bin","bool","breakpoint","bytes", + "bytearray","callable","compile","complex","delattr","chr", + "dict","dir","divmod","enumerate","eval","exec","filter","float", + "format","frozenset","getattr","globals","hasattr","hash","help", + "hex","id","input","int","isinstance","issubclass","iter","len", + "list","locals","map","max","memoryview","min","next","object", + "oct","open","ord","pow","print","property","range","repr","reverse", + "round","set","setattr","slice","sorted","staticmethod","str","sum", + "super","tuple","type","vars","zip", + 'self', + ] + special = ['True','False','None'] + def paintPyTriple(quote): + while self[0]: + if self[0] == quote: + self.paint(1, self.FLAG_STRING) + if self[0] == quote and self[1] == quote: + self.paint(2, self.FLAG_STRING) + return 0 + else: + self.paint(1, self.FLAG_STRING) + return 1 if quote == '"' else 2 + def paintPyString(quote): + self.paint(1, self.FLAG_STRING) + while self[0]: + if self[0] == '\\' and self[1] == quote: + self.paint(2, self.FLAG_ESCAPE) + else if self[0] == quote: + self.paint(1, self.FLAG_STRING) + return 0 + else if self[0] == '\\': + self.paint(2, self.FLAG_ESCAPE) + else: + self.paint(1, self.FLAG_STRING) + return 0 + def paintPyNumeral(): + if self[0] == '0' and self[1] == 'x' or self[1] == 'X': + self.paint(2, self.FLAG_NUMERAL) + while self.isxdigit(self[0]) or self[0] == '_': + self.paint(1, self.FLAG_NUMERAL) + else if self[0] == '0' and self[1] == '.': + self.paint(2, self.FLAG_NUMERAL) + while self.isdigit(self[0]) or self[0] == '_': + self.paint(1, self.FLAG_NUMERAL) + if (self[0] == '+' or self[0] == '-') and (self[1] == 'e' or self[1] == 'E'): + self.paint(2, self.FLAG_NUMERAL) + while self.isdigit(self[0]) or self[0] == '_': + self.paint(1, self.FLAG_NUMERAL) + else if self[0] == 'e' or self[0] == 'E': + self.paint(1, self.FLAG_NUMERAL) + while self.isdigit(self[0]) or self[0] == '_': + self.paint(1, self.FLAG_NUMERAL) + if self[0] == 'j': self.paint(1, self.FLAG_NUMERAL) + return 0 + else: + while self.isdigit(self[0]) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL) + if self[0] == '.': + self.paint(1, self.FLAG_NUMERAL) + while self.isdigit(self[0]) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL) + if (self[0] == '+' or self[0] == '-') and (self[1] == 'e' or self[1] == 'E'): + self.paint(2, self.FLAG_NUMERAL) + while self.isdigit(self[0]) or self[0] == '_': + self.paint(1, self.FLAG_NUMERAL) + else if self[0] == 'e' or self[0] == 'E': + self.paint(1, self.FLAG_NUMERAL) + while self.isdigit(self[0]) or self[0] == '_': + self.paint(1, self.FLAG_NUMERAL) + if self[0] == 'j': self.paint(1, self.FLAG_NUMERAL) + return 0 + while self[0] == 'l' or self[0] == 'L': self.paint(1, self.FLAG_NUMERAL) + return 0 + def paintPyFString(quote): + self.paint(1, self.FLAG_STRING) + while self[0]: + if self[0] == '\\' and self[1] == quote: + self.paint(2, self.FLAG_ESCAPE) + else if self[0] == quote: + self.paint(1, self.FLAG_STRING) + return + else if self[0] == '\\': + self.paint(2, self.FLAG_ESCAPE) + else if self[0] == '{': + self.paint(1, self.FLAG_NUMERAL) + if self[0] == '}': + self.rewind(1) + self.paint(2, self.FLAG_ERROR) + else: + while self[0] and self[0] != '}': + self.paint(1, self.FLAG_NUMERAL) + self.paint(1, self.FLAG_NUMERAL) + else: + self.paint(1, self.FLAG_STRING) + def calculate(): + if self.state < 1: + if self[0] == '#': + self.paintComment() + else if self.i == 0 and self.matchAndPaint('import', self.FLAG_PRAGMA, self.cKeywordQualifier): + return 0 + else if self[0] == '@': + self.paint(1, self.FLAG_PRAGMA) + while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_PRAGMA) + return 0 + else if self[0] == '"': + if self[1] == '"' and self[2] == '"': + self.paint(3, self.FLAG_STRING) + return self.paintPyTriple('"') + else if self[-1] == 'f': + self.rewind(1) + self.paint(1, self.FLAG_TYPE) + self.paintPyFString('"') + return 0 + else: + self.paintPyString('"') + return 0 + else if self[0] == "'": + if self[1] == "'" and self[2] == "'": + self.paint(3, self.FLAG_STRING) + return self.paintPyTriple("'") + else if self[-1] == 'f': + self.rewind(1) + self.paint(1, self.FLAG_TYPE) + self.paintPyFString("'") + return 0 + else: + self.paintPyString("'") + return 0 + else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): + return 0 + else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier): + return 0 + else if self.findKeywords(self.special, self.FLAG_NUMERAL, self.cKeywordQualifier): + return 0 + else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): + self.paintPyNumeral() + return 0 + else if self[0]: + self.skip() + return 0 + else if self.state == 1: + return self.paintPyTriple('"') + else if self.state == 2: + return self.paintPyTriple("'") + return None + +bind(PythonHighlighter) diff --git a/base/usr/share/bim/syntax/xml.krk b/base/usr/share/bim/syntax/xml.krk new file mode 100644 index 00000000..bbcdc9a9 --- /dev/null +++ b/base/usr/share/bim/syntax/xml.krk @@ -0,0 +1,120 @@ +from syntax import Highlighter, bind +from syntax.css import CSSHighlighter + +class XMLHighlighter(Highlighter): + name = 'xml' + extensions = ('.xml','.iml') + def paintXmlComment(): + while self[0]: + if self[0] == '-' and self[1] == '-' and self[2] == '>': + self.paint(3, self.FLAG_COMMENT) + return 0 + else: + if self.commentBuzzwords(): continue + self.paint(1, self.FLAG_COMMENT) + return 4 + def paintTagString(): + if self[0] == '"': + self.paint(1, self.FLAG_STRING) + return 2 + else: + self.paintSimpleString() + if not self[0] and self[-1] != '"': + return 3 + return None + def paintInsideTag(): + while self[0]: + if self[0] == '>': + self.paint(1, self.FLAG_TYPE) + return 0 + else if self[0] == '"': + self.paintSimpleString() + if not self[0] and self[-1] != '"': + return 3 + else: + self.paint(1, self.FLAG_TYPE) + return 2 + def paintTag(): + while self[0]: + if self[0] == '/': self.paint(1, self.FLAG_TYPE) + if self[0] == '?': self.paint(1, self.FLAG_TYPE) + if self[0] == ' ' or self[0] == '\t': self.skip() + if self.isalnum(self[0]): + while self.isalnum(self[0]) or self[0] == '-': + self.paint(1, self.FLAG_KEYWORD) + if not self[0]: return 2 + return self.paintInsideTag() + else: + self.paint(1, self.FLAG_TYPE) + return None + def calculate(): + if self.state < 1: + while self[0] and self[0] != '<': + self.skip() + if not self[0]: return None + if self[0] == '<' and self[1] == '!' and self[2] == '-' and self[3] == '-': + self.paint(4, self.FLAG_COMMENT) + return self.paintXmlComment() + self.paint(1, self.FLAG_TYPE) + return self.paintTag() + else if self.state == 1: + return self.paintTag() + else if self.state == 2: + return self.paintInsideTag() + else if self.state == 3: + return self.paintTagString() + else if self.state == 4: + return self.paintXmlComment() + return None + + +bind(XMLHighlighter) + +''' +General proof of concept for an HTML highlighter that does nested CSS. +Could be extended to do nested JS, probably. +Doesn't actually do either right now, but demonstrates how it could be done. +''' +class HTMLHighlighter(XMLHighlighter): + name = 'html' + extensions = ('.html','.htm') + def paintTag(): + if self.matchAndPaint('style', self.FLAG_KEYWORD, self.cKeywordQualifier): + return self.paintInsideStyleTag() + return super().paintTag() + def paintInsideStyleTag(): + let r = self.paintInsideTag() + if r == 2: + return 5 + else: + return self.paintCss() + def paintCss(): + # TODO actually paint CSS + while self[0] and not (self[0] == '<' and self[1] == '/' and self[2] == 's'): + self.set_state(0) + let css = CSSHighlighter(self) + let result = css.calculate() + if result == 0: + self.__init__(css) + continue + else if result == None: + return 6 + if (self[0] == '<' and self[1] == '/' and self[2] == 's'): + return self.paintTag() + return 6 + def calculate(): + if self.state < 5: + return super().calculate() + else if self.state == 5: + return self.paintInsideStyleTag() + else if self.state == 6: + return self.paintCss() + else if self.state == 7: + self.set_state(1) + let css = CSSHighlighter(self) + if css.calculate() == 1: return 7 + self.__init__(css) + return 6 + +bind(HTMLHighlighter) + diff --git a/base/usr/share/bim/themes/__init__.krk b/base/usr/share/bim/themes/__init__.krk new file mode 100644 index 00000000..e69de29b diff --git a/base/usr/share/bim/themes/ansi.bimscript b/base/usr/share/bim/themes/ansi.bimscript deleted file mode 100644 index 9dc216d1..00000000 --- a/base/usr/share/bim/themes/ansi.bimscript +++ /dev/null @@ -1,51 +0,0 @@ -function theme:ansi - # First set regular colors - setcolor text-fg @7 - setcolor text-bg @0 - setcolor alternate-fg @5 - setcolor alternate-bg @9 - setcolor number-fg @3 - setcolor number-bg @9 - setcolor status-fg @7 - setcolor status-bg @4 - setcolor status-alt @4 - setcolor tabbar-bg @4 - setcolor tab-bg @4 - setcolor keyword @4 - setcolor string @2 - setcolor comment @5 - setcolor type @3 - setcolor pragma @1 - setcolor numeral @1 - setcolor error-fg @7 - setcolor error-bg @1 - setcolor search-fg @0 - setcolor search-bg @3 - setcolor select-bg @7 - setcolor select-fg @0 - setcolor red @1 - setcolor green @2 - setcolor bold @7 - setcolor link @4 - setcolor escape @2 - - trycall _ansi_brights - - action redraw_all -end - -function _ansi_brights - checkprop can_bright - setcolor text-fg @17 - setcolor text-bg @9 - setcolor alternate-fg @10 - setcolor status-fg @17 - setcolor status-alt @14 - setcolor keyword @14 - setcolor comment @10 - setcolor error-fg @17 - setcolor search-bg @13 - setcolor select-bg @17 - setcolor link @14 - setcolor escape @12 -end diff --git a/base/usr/share/bim/themes/ansi.krk b/base/usr/share/bim/themes/ansi.krk new file mode 100644 index 00000000..c84ac208 --- /dev/null +++ b/base/usr/share/bim/themes/ansi.krk @@ -0,0 +1,46 @@ +from bim import defineTheme + +@defineTheme +def ansi(): + setcolor("text-fg","@7") + setcolor("text-bg","@0") + setcolor("alternate-fg","@5") + setcolor("alternate-bg","@9") + setcolor("number-fg","@3") + setcolor("number-bg","@9") + setcolor("status-fg","@7") + setcolor("status-bg","@4") + setcolor("status-alt","@4") + setcolor("tabbar-bg","@4") + setcolor("tab-bg","@4") + setcolor("keyword","@4") + setcolor("string","@2") + setcolor("comment","@5") + setcolor("type","@3") + setcolor("pragma","@1") + setcolor("numeral","@1") + setcolor("error-fg","@7") + setcolor("error-bg","@1") + setcolor("search-fg","@0") + setcolor("search-bg","@3") + setcolor("select-bg","@7") + setcolor("select-fg","@0") + setcolor("red","@1") + setcolor("green","@2") + setcolor("bold","@7") + setcolor("link","@4") + setcolor("escape","@2") + if checkprop("can_bright") == 0: # TODO These should return True/False, not 0 for success + setcolor("text-fg","@17") + setcolor("text-bg","@9") + setcolor("alternate-fg","@10") + setcolor("status-fg","@17") + setcolor("status-alt","@14") + setcolor("keyword","@14") + setcolor("comment","@10") + setcolor("error-fg","@17") + setcolor("search-bg","@13") + setcolor("select-bg","@17") + setcolor("link","@14") + setcolor("escape","@12") + diff --git a/base/usr/share/bim/themes/citylights.bimscript b/base/usr/share/bim/themes/citylights.bimscript deleted file mode 100644 index 0eb5dade..00000000 --- a/base/usr/share/bim/themes/citylights.bimscript +++ /dev/null @@ -1,32 +0,0 @@ -function theme:citylights - checkprop can_24bit - setcolor text-fg 2;151;178;198 - setcolor text-bg 2;29;37;44 - setcolor alternate-fg 2;45;55;65 - setcolor alternate-bg 2;33;42;50 - setcolor number-fg 2;71;89;103 - setcolor number-bg 2;37;47;56 - setcolor status-fg 2;151;178;198 - setcolor status-bg 2;53;67;78 - setcolor status-alt 2;116;144;166 - setcolor tabbar-bg 2;37;47;56 - setcolor tab-bg 2;29;37;44 - setcolor keyword 2;94;196;255 - setcolor string 2;83;154;252 - setcolor comment 2;107;133;153;3 - setcolor type 2;139;212;156 - setcolor pragma 2;0;139;148 - setcolor numeral 2;207;118;132 - setcolor error-fg 5;15 - setcolor error-bg 5;196 - setcolor search-fg 5;234 - setcolor search-bg 5;226 - setcolor select-fg 2;29;37;44 - setcolor select-bg 2;151;178;198 - setcolor red 2;222;53;53 - setcolor green 2;55;167;0 - setcolor bold 2;151;178;198;1 - setcolor link 2;94;196;255;4 - setcolor escape 2;133;182;249 - action redraw_all -end diff --git a/base/usr/share/bim/themes/citylights.krk b/base/usr/share/bim/themes/citylights.krk new file mode 100644 index 00000000..859048ce --- /dev/null +++ b/base/usr/share/bim/themes/citylights.krk @@ -0,0 +1,35 @@ +from bim import defineTheme +from themes import ansi + +@defineTheme +def citylights(): + if checkprop('can_24bit'): + return ansi() + setcolor("text-fg","2;151;178;198") + setcolor("text-bg","2;29;37;44") + setcolor("alternate-fg","2;45;55;65") + setcolor("alternate-bg","2;33;42;50") + setcolor("number-fg","2;71;89;103") + setcolor("number-bg","2;37;47;56") + setcolor("status-fg","2;151;178;198") + setcolor("status-bg","2;53;67;78") + setcolor("status-alt","2;116;144;166") + setcolor("tabbar-bg","2;37;47;56") + setcolor("tab-bg","2;29;37;44") + setcolor("keyword","2;94;196;255") + setcolor("string","2;83;154;252") + setcolor("comment","2;107;133;153;3") + setcolor("type","2;139;212;156") + setcolor("pragma","2;0;139;148") + setcolor("numeral","2;207;118;132") + setcolor("error-fg","5;15") + setcolor("error-bg","5;196") + setcolor("search-fg","5;234") + setcolor("search-bg","5;226") + setcolor("select-fg","2;29;37;44") + setcolor("select-bg","2;151;178;198") + setcolor("red","2;222;53;53") + setcolor("green","2;55;167;0") + setcolor("bold","2;151;178;198;1") + setcolor("link","2;94;196;255;4") + setcolor("escape","2;133;182;249") diff --git a/base/usr/share/bim/themes/light.bimscript b/base/usr/share/bim/themes/light.bimscript deleted file mode 100644 index 30d64ddb..00000000 --- a/base/usr/share/bim/themes/light.bimscript +++ /dev/null @@ -1,34 +0,0 @@ -# A light color scheme -# Based on selenized by Jan Warchoł -function theme:light - checkprop can_24bit - - setcolor text-fg 2;57;76;82 - setcolor text-bg 2;250;242;218 - setcolor alternate-fg 2;144;153;149 - setcolor alternate-bg 2;236;227;204 - setcolor number-fg 2;57;76;82 - setcolor number-bg 2;212;204;181 - setcolor status-fg 2;82;102;109 - setcolor status-bg 2;212;204;181 - setcolor status-alt 2;129;61;19 - setcolor tab-bg 2;212;204;181 - setcolor tabbar-bg 2;236;227;204 - setcolor error-fg 5;15 - setcolor error-bg 5;196 - setcolor search-fg 5;234 - setcolor search-bg 5;226 - setcolor keyword 2;0;114;212;1 - setcolor string 2;72;145;0 - setcolor comment 2;144;153;149;3 - setcolor type 2;193;92;29 - setcolor pragma 2;210;33;45;1 - setcolor numeral 2;202;72;152;1 - setcolor select-fg 2;213;205;182 - setcolor select-bg 2;144;153;149 - setcolor red 2;222;53;53 - setcolor green 2;55;167;0 - setcolor bold 2;57;76;82;1 - setcolor link 2;0;114;212;4 - setcolor escape 2;0;156;143 -end diff --git a/base/usr/share/bim/themes/light.krk b/base/usr/share/bim/themes/light.krk new file mode 100644 index 00000000..5c8fea4c --- /dev/null +++ b/base/usr/share/bim/themes/light.krk @@ -0,0 +1,38 @@ +from bim import defineTheme +from themes import ansi + +# A light color scheme +# Based on selenized by Jan Warchoł +@defineTheme +def light(): + if checkprop('can_24bit'): + return ansi() + setcolor("text-fg","2;57;76;82") + setcolor("text-bg","2;250;242;218") + setcolor("alternate-fg","2;144;153;149") + setcolor("alternate-bg","2;236;227;204") + setcolor("number-fg","2;57;76;82") + setcolor("number-bg","2;212;204;181") + setcolor("status-fg","2;82;102;109") + setcolor("status-bg","2;212;204;181") + setcolor("status-alt","2;129;61;19") + setcolor("tab-bg","2;212;204;181") + setcolor("tabbar-bg","2;236;227;204") + setcolor("error-fg","5;15") + setcolor("error-bg","5;196") + setcolor("search-fg","5;234") + setcolor("search-bg","5;226") + setcolor("keyword","2;0;114;212;1") + setcolor("string","2;72;145;0") + setcolor("comment","2;144;153;149;3") + setcolor("type","2;193;92;29") + setcolor("pragma","2;210;33;45;1") + setcolor("numeral","2;202;72;152;1") + setcolor("select-fg","2;213;205;182") + setcolor("select-bg","2;144;153;149") + setcolor("red","2;222;53;53") + setcolor("green","2;55;167;0") + setcolor("bold","2;57;76;82;1") + setcolor("link","2;0;114;212;4") + setcolor("escape","2;0;156;143") + diff --git a/base/usr/share/bim/themes/solarized.bimscript b/base/usr/share/bim/themes/solarized.bimscript deleted file mode 100644 index bea12254..00000000 --- a/base/usr/share/bim/themes/solarized.bimscript +++ /dev/null @@ -1,32 +0,0 @@ -function theme:solarized_dark - checkprop can_24bit - setcolor text-fg 2;147;161;161 - setcolor text-bg 2;0;43;54 - setcolor alternate-fg 2;147;161;161 - setcolor alternate-bg 2;7;54;66 - setcolor number-fg 2;131;148;149 - setcolor number-bg 2;7;54;66 - setcolor status-fg 2;131;148;150 - setcolor status-bg 2;7;54;66 - setcolor status-alt 2;133;153;0 - setcolor tabbar-bg 2;7;54;66 - setcolor tab-bg 2;131;148;150 - setcolor keyword 2;133;153;0 - setcolor string 2;42;161;152 - setcolor comment 2;101;123;131 - setcolor type 2;181;137;0 - setcolor pragma 2;203;75;22 - setcolor numeral 2;220;50;47 - setcolor error-fg 5;15 - setcolor error-bg 5;196 - setcolor search-fg 5;234 - setcolor search-bg 5;226 - setcolor select-fg 2;0;43;54 - setcolor select-bg 2;147;161;161 - setcolor red 2;222;53;53 - setcolor green 2;55;167;0 - setcolor bold 2;147;161;161;1 - setcolor link 2;42;161;152;4 - setcolor escape 2;133;153;0 - action redraw_all -end diff --git a/base/usr/share/bim/themes/solarized.krk b/base/usr/share/bim/themes/solarized.krk new file mode 100644 index 00000000..5bc1e27f --- /dev/null +++ b/base/usr/share/bim/themes/solarized.krk @@ -0,0 +1,36 @@ +from bim import defineTheme +from themes import ansi + +@defineTheme +def solarized_dark(): + if checkprop('can_24bit'): + return ansi() + setcolor("text-fg","2;147;161;161") + setcolor("text-bg","2;0;43;54") + setcolor("alternate-fg","2;147;161;161") + setcolor("alternate-bg","2;7;54;66") + setcolor("number-fg","2;131;148;149") + setcolor("number-bg","2;7;54;66") + setcolor("status-fg","2;131;148;150") + setcolor("status-bg","2;7;54;66") + setcolor("status-alt","2;133;153;0") + setcolor("tabbar-bg","2;7;54;66") + setcolor("tab-bg","2;131;148;150") + setcolor("keyword","2;133;153;0") + setcolor("string","2;42;161;152") + setcolor("comment","2;101;123;131") + setcolor("type","2;181;137;0") + setcolor("pragma","2;203;75;22") + setcolor("numeral","2;220;50;47") + setcolor("error-fg","5;15") + setcolor("error-bg","5;196") + setcolor("search-fg","5;234") + setcolor("search-bg","5;226") + setcolor("select-fg","2;0;43;54") + setcolor("select-bg","2;147;161;161") + setcolor("red","2;222;53;53") + setcolor("green","2;55;167;0") + setcolor("bold","2;147;161;161;1") + setcolor("link","2;42;161;152;4") + setcolor("escape","2;133;153;0") + diff --git a/base/usr/share/bim/themes/sunsmoke.bimscript b/base/usr/share/bim/themes/sunsmoke.bimscript deleted file mode 100644 index 1eb9bddb..00000000 --- a/base/usr/share/bim/themes/sunsmoke.bimscript +++ /dev/null @@ -1,68 +0,0 @@ -# Sunsmoke Color Scheme -function theme:sunsmoke - checkprop can_24bit - - setcolor text-fg 2;230;230;230 - setcolor text-bg 2;31;31;31 - setcolor alternate-fg 2;122;122;122 - setcolor alternate-bg 2;46;43;46 - setcolor number-fg 2;150;139;57 - setcolor number-bg 2;0;0;0 - setcolor status-fg 2;230;230;230 - setcolor status-bg 2;71;64;58 - setcolor status-alt 2;122;122;122 - setcolor tabbar-bg 2;71;64;58 - setcolor tab-bg 2;71;64;58 - setcolor keyword 2;51;162;230 - setcolor string 2;72;176;72 - setcolor comment 2;158;153;129;3 - setcolor type 2;230;206;110 - setcolor pragma 2;194;70;54 - setcolor numeral 2;230;43;127 - setcolor error-fg 5;15 - setcolor error-bg 5;196 - setcolor search-fg 5;234 - setcolor search-bg 5;226 - setcolor select-fg 2;0;43;54 - setcolor select-bg 2;147;161;161 - setcolor red 2;222;53;53 - setcolor green 2;55;167;0 - setcolor bold 2;230;230;230;1 - setcolor link 2;51;162;230;4 - setcolor escape 2;113;203;173 -end - -function theme:sunsmoke256 - checkprop can_256color - setcolor text-fg 5;188 - setcolor text-bg 5;234 - setcolor alternate-fg 5;244 - setcolor alternate-bg 5;236 - setcolor number-fg 5;101 - setcolor number-bg 5;232 - setcolor status-fg 5;188 - setcolor status-bg 5;59 - setcolor status-alt 5;244 - setcolor tabbar-bg 5;59 - setcolor tab-bg 5;59 - setcolor keyword 5;74 - setcolor string 5;71 - setcolor comment 5;102 - setcolor type 5;221 - setcolor pragma 5;160 - setcolor numeral 5;161 - setcolor error-fg 5;15 - setcolor error-bg 5;196 - setcolor search-fg 5;234 - setcolor search-bg 5;226 - setcolor select-fg 5;17 - setcolor select-bg 5;109 - setcolor red @1 - setcolor green @2 - setcolor bold 5;188;1 - setcolor link 5;74;4 - setcolor escape 5;79 - - checkprop can_italic - setcolor comment 5;102;3 -end diff --git a/base/usr/share/bim/themes/sunsmoke.krk b/base/usr/share/bim/themes/sunsmoke.krk new file mode 100644 index 00000000..5de58dfe --- /dev/null +++ b/base/usr/share/bim/themes/sunsmoke.krk @@ -0,0 +1,69 @@ +from bim import defineTheme +from themes import ansi + +@defineTheme +def sunsmoke256(): + if checkprop('can_256color'): + return ansi() + setcolor("text-fg","5;188") + setcolor("text-bg","5;234") + setcolor("alternate-fg","5;244") + setcolor("alternate-bg","5;236") + setcolor("number-fg","5;101") + setcolor("number-bg","5;232") + setcolor("status-fg","5;188") + setcolor("status-bg","5;59") + setcolor("status-alt","5;244") + setcolor("tabbar-bg","5;59") + setcolor("tab-bg","5;59") + setcolor("keyword","5;74") + setcolor("string","5;71") + setcolor("comment","5;102") + setcolor("type","5;221") + setcolor("pragma","5;160") + setcolor("numeral","5;161") + setcolor("error-fg","5;15") + setcolor("error-bg","5;196") + setcolor("search-fg","5;234") + setcolor("search-bg","5;226") + setcolor("select-fg","5;17") + setcolor("select-bg","5;109") + setcolor("red","@1") + setcolor("green","@2") + setcolor("bold","5;188;1") + setcolor("link","5;74;4") + setcolor("escape","5;79") + +@defineTheme +def sunsmoke(): + if checkprop('can_24bit'): + return sunsmoke256() + setcolor("text-fg", "2;230;230;230") + setcolor("text-bg", "2;31;31;31") + setcolor("alternate-fg", "2;122;122;122") + setcolor("alternate-bg", "2;46;43;46") + setcolor("number-fg", "2;150;139;57") + setcolor("number-bg", "2;0;0;0") + setcolor("status-fg", "2;230;230;230") + setcolor("status-bg", "2;71;64;58") + setcolor("status-alt", "2;122;122;122") + setcolor("tabbar-bg", "2;71;64;58") + setcolor("tab-bg", "2;71;64;58") + setcolor("keyword", "2;51;162;230") + setcolor("string", "2;72;176;72") + setcolor("comment", "2;158;153;129;3") + setcolor("type", "2;230;206;110") + setcolor("pragma", "2;194;70;54") + setcolor("numeral", "2;230;43;127") + setcolor("error-fg", "5;15") + setcolor("error-bg", "5;196") + setcolor("search-fg", "5;234") + setcolor("search-bg", "5;226") + setcolor("select-fg", "2;0;43;54") + setcolor("select-bg", "2;147;161;161") + setcolor("red", "2;222;53;53") + setcolor("green", "2;55;167;0") + setcolor("bold", "2;230;230;230;1") + setcolor("link", "2;51;162;230;4") + setcolor("escape", "2;113;203;173") + diff --git a/base/usr/share/bim/themes/wombat.bimscript b/base/usr/share/bim/themes/wombat.bimscript deleted file mode 100644 index 66c91c9f..00000000 --- a/base/usr/share/bim/themes/wombat.bimscript +++ /dev/null @@ -1,39 +0,0 @@ -# Wombat 256-color theme -function theme:wombat - checkprop can_256color - setcolor text-fg 5;230 - setcolor text-bg 5;235 - setcolor alternate-fg 5;244 - setcolor alternate-bg 5;236 - setcolor number-bg 5;232 - setcolor number-fg 5;101 - setcolor status-fg 5;230 - setcolor status-bg 5;238 - setcolor status-alt 5;186 - setcolor tabbar-bg 5;230 - setcolor tab-bg 5;248 - setcolor keyword 5;117 - setcolor string 5;113 - setcolor comment 5;102 - setcolor type 5;186 - setcolor pragma 5;173 - setcolor numeral 5;173 - setcolor error-fg 5;15 - setcolor error-bg 5;196 - setcolor search-fg 5;234 - setcolor search-bg 5;226 - setcolor select-fg 5;235 - setcolor select-bg 5;230 - setcolor red @1 - setcolor green @2 - setcolor bold 5;230;1 - setcolor link 5;117;4 - setcolor escape 5;194 - trycall _sunsmoke_italics - action redraw_all -end - -function _sunsmoke_italics - checkprop can_italic - setcolor comment 5;102;3 -end diff --git a/base/usr/share/bim/themes/wombat.krk b/base/usr/share/bim/themes/wombat.krk new file mode 100644 index 00000000..f8fbbfb1 --- /dev/null +++ b/base/usr/share/bim/themes/wombat.krk @@ -0,0 +1,39 @@ +from bim import defineTheme +from themes import ansi + +# Wombat 256-color theme +@defineTheme +def wombat(): + if checkprop('can_256color'): + return ansi() + setcolor("text-fg","5;230") + setcolor("text-bg","5;235") + setcolor("alternate-fg","5;244") + setcolor("alternate-bg","5;236") + setcolor("number-bg","5;232") + setcolor("number-fg","5;101") + setcolor("status-fg","5;230") + setcolor("status-bg","5;238") + setcolor("status-alt","5;186") + setcolor("tabbar-bg","5;230") + setcolor("tab-bg","5;248") + setcolor("keyword","5;117") + setcolor("string","5;113") + setcolor("comment","5;102") + setcolor("type","5;186") + setcolor("pragma","5;173") + setcolor("numeral","5;173") + setcolor("error-fg","5;15") + setcolor("error-bg","5;196") + setcolor("search-fg","5;234") + setcolor("search-bg","5;226") + setcolor("select-fg","5;235") + setcolor("select-bg","5;230") + setcolor("red","@1") + setcolor("green","@2") + setcolor("bold","5;230;1") + setcolor("link","5;117;4") + setcolor("escape","5;194") + if not checkprop('can_italic'): + setcolor('comment','5;102;3') + diff --git a/base/usr/share/cursor/drag.bmp b/base/usr/share/cursor/drag.bmp deleted file mode 100644 index 93e97b35..00000000 Binary files a/base/usr/share/cursor/drag.bmp and /dev/null differ diff --git a/base/usr/share/cursor/drag.png b/base/usr/share/cursor/drag.png new file mode 100644 index 00000000..54c05076 Binary files /dev/null and b/base/usr/share/cursor/drag.png differ diff --git a/base/usr/share/cursor/mouse.bmp b/base/usr/share/cursor/mouse.bmp deleted file mode 100644 index c38156af..00000000 Binary files a/base/usr/share/cursor/mouse.bmp and /dev/null differ diff --git a/base/usr/share/cursor/normal.png b/base/usr/share/cursor/normal.png new file mode 100644 index 00000000..813e2f31 Binary files /dev/null and b/base/usr/share/cursor/normal.png differ diff --git a/base/usr/share/cursor/resize-dlur.bmp b/base/usr/share/cursor/resize-dlur.bmp deleted file mode 100644 index be2b49ee..00000000 Binary files a/base/usr/share/cursor/resize-dlur.bmp and /dev/null differ diff --git a/base/usr/share/cursor/resize-dlur.png b/base/usr/share/cursor/resize-dlur.png new file mode 100644 index 00000000..785e6fc7 Binary files /dev/null and b/base/usr/share/cursor/resize-dlur.png differ diff --git a/base/usr/share/cursor/resize-horizontal.bmp b/base/usr/share/cursor/resize-horizontal.bmp deleted file mode 100644 index 0ad21df4..00000000 Binary files a/base/usr/share/cursor/resize-horizontal.bmp and /dev/null differ diff --git a/base/usr/share/cursor/resize-horizontal.png b/base/usr/share/cursor/resize-horizontal.png new file mode 100644 index 00000000..20888504 Binary files /dev/null and b/base/usr/share/cursor/resize-horizontal.png differ diff --git a/base/usr/share/cursor/resize-uldr.bmp b/base/usr/share/cursor/resize-uldr.bmp deleted file mode 100644 index a97f35ea..00000000 Binary files a/base/usr/share/cursor/resize-uldr.bmp and /dev/null differ diff --git a/base/usr/share/cursor/resize-uldr.png b/base/usr/share/cursor/resize-uldr.png new file mode 100644 index 00000000..884c910f Binary files /dev/null and b/base/usr/share/cursor/resize-uldr.png differ diff --git a/base/usr/share/cursor/resize-vertical.bmp b/base/usr/share/cursor/resize-vertical.bmp deleted file mode 100644 index 0f233e1e..00000000 Binary files a/base/usr/share/cursor/resize-vertical.bmp and /dev/null differ diff --git a/base/usr/share/cursor/resize-vertical.png b/base/usr/share/cursor/resize-vertical.png new file mode 100644 index 00000000..eab8421f Binary files /dev/null and b/base/usr/share/cursor/resize-vertical.png differ diff --git a/base/usr/share/fonts/sdf_bold.sdf b/base/usr/share/fonts/sdf_bold.sdf index 30f7b33f..fd60c43f 100644 Binary files a/base/usr/share/fonts/sdf_bold.sdf and b/base/usr/share/fonts/sdf_bold.sdf differ diff --git a/base/usr/share/fonts/sdf_bold_oblique.sdf b/base/usr/share/fonts/sdf_bold_oblique.sdf index 89013007..1ad9fadc 100644 Binary files a/base/usr/share/fonts/sdf_bold_oblique.sdf and b/base/usr/share/fonts/sdf_bold_oblique.sdf differ diff --git a/base/usr/share/fonts/sdf_mono.sdf b/base/usr/share/fonts/sdf_mono.sdf index 3e896b76..dc9c7aa4 100644 Binary files a/base/usr/share/fonts/sdf_mono.sdf and b/base/usr/share/fonts/sdf_mono.sdf differ diff --git a/base/usr/share/fonts/sdf_mono_bold.sdf b/base/usr/share/fonts/sdf_mono_bold.sdf index 651c043c..cd2957af 100644 Binary files a/base/usr/share/fonts/sdf_mono_bold.sdf and b/base/usr/share/fonts/sdf_mono_bold.sdf differ diff --git a/base/usr/share/fonts/sdf_mono_bold_oblique.sdf b/base/usr/share/fonts/sdf_mono_bold_oblique.sdf index 69513828..bf5c9355 100644 Binary files a/base/usr/share/fonts/sdf_mono_bold_oblique.sdf and b/base/usr/share/fonts/sdf_mono_bold_oblique.sdf differ diff --git a/base/usr/share/fonts/sdf_mono_oblique.sdf b/base/usr/share/fonts/sdf_mono_oblique.sdf index 87080dbe..699936aa 100644 Binary files a/base/usr/share/fonts/sdf_mono_oblique.sdf and b/base/usr/share/fonts/sdf_mono_oblique.sdf differ diff --git a/base/usr/share/fonts/sdf_oblique.sdf b/base/usr/share/fonts/sdf_oblique.sdf index e5eed8bc..3e7cc98c 100644 Binary files a/base/usr/share/fonts/sdf_oblique.sdf and b/base/usr/share/fonts/sdf_oblique.sdf differ diff --git a/base/usr/share/fonts/sdf_thin.sdf b/base/usr/share/fonts/sdf_thin.sdf index 9fa057bf..23926ac3 100644 Binary files a/base/usr/share/fonts/sdf_thin.sdf and b/base/usr/share/fonts/sdf_thin.sdf differ diff --git a/base/usr/share/help/0_index.trt b/base/usr/share/help/0_index.trt new file mode 100644 index 00000000..44775624 --- /dev/null +++ b/base/usr/share/help/0_index.trt @@ -0,0 +1,5 @@ +

Welcome!

+ +This is a demo file. It contains text with markup!
+ +It is XML-formatted, I guess? diff --git a/base/usr/share/help/file-browser.trt b/base/usr/share/help/file-browser.trt new file mode 100644 index 00000000..20ca655b --- /dev/null +++ b/base/usr/share/help/file-browser.trt @@ -0,0 +1,7 @@ +

File Browser

+ +Provides a graphical representation of the file system.
+ +Double click to navigate through directories or open files.
+ +A right-click context menu provides additional options. diff --git a/base/usr/share/help/help-browser.trt b/base/usr/share/help/help-browser.trt new file mode 100644 index 00000000..df4cb8a9 --- /dev/null +++ b/base/usr/share/help/help-browser.trt @@ -0,0 +1,3 @@ +

Help Browser

+ +Displays HTML-escape markup files describing applications. diff --git a/base/usr/share/help/package-manager.trt b/base/usr/share/help/package-manager.trt new file mode 100644 index 00000000..8911599f --- /dev/null +++ b/base/usr/share/help/package-manager.trt @@ -0,0 +1,5 @@ +

Package Manager

+ +Graphical interface to the msk command-line utility.
+ +Double click packages to install them. Dependencies will be installed automatically. diff --git a/base/usr/share/help/terminal.trt b/base/usr/share/help/terminal.trt new file mode 100644 index 00000000..e36c4352 --- /dev/null +++ b/base/usr/share/help/terminal.trt @@ -0,0 +1,8 @@ +

Terminal

+ +Terminal emulator providing extensive ANSI escape sequence support.
+ +Supports most xterm-compatible escape squences, as well as 24-bit color.
+ +Text display supports anti-aliased (using either the native SDF renderer, or +the Freetype backend, if available) as well as bitmap fonts. diff --git a/base/usr/share/icons/16/applications-generic.bmp b/base/usr/share/icons/16/applications-generic.bmp deleted file mode 100644 index a2c249df..00000000 Binary files a/base/usr/share/icons/16/applications-generic.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/applications-generic.png b/base/usr/share/icons/16/applications-generic.png new file mode 100644 index 00000000..f1798d1b Binary files /dev/null and b/base/usr/share/icons/16/applications-generic.png differ diff --git a/base/usr/share/icons/16/back.bmp b/base/usr/share/icons/16/back.bmp deleted file mode 100644 index c38ee4c5..00000000 Binary files a/base/usr/share/icons/16/back.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/back.png b/base/usr/share/icons/16/back.png new file mode 100644 index 00000000..3f59ff58 Binary files /dev/null and b/base/usr/share/icons/16/back.png differ diff --git a/base/usr/share/icons/16/bookmark.bmp b/base/usr/share/icons/16/bookmark.bmp deleted file mode 100644 index acae97e1..00000000 Binary files a/base/usr/share/icons/16/bookmark.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/bookmark.png b/base/usr/share/icons/16/bookmark.png new file mode 100644 index 00000000..ba17127a Binary files /dev/null and b/base/usr/share/icons/16/bookmark.png differ diff --git a/base/usr/share/icons/16/calculator.bmp b/base/usr/share/icons/16/calculator.bmp deleted file mode 100644 index b7b7ec65..00000000 Binary files a/base/usr/share/icons/16/calculator.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/calculator.png b/base/usr/share/icons/16/calculator.png new file mode 100644 index 00000000..3f10c931 Binary files /dev/null and b/base/usr/share/icons/16/calculator.png differ diff --git a/base/usr/share/icons/16/clock.bmp b/base/usr/share/icons/16/clock.bmp deleted file mode 100644 index a320c9dd..00000000 Binary files a/base/usr/share/icons/16/clock.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/clock.png b/base/usr/share/icons/16/clock.png new file mode 100644 index 00000000..170ea713 Binary files /dev/null and b/base/usr/share/icons/16/clock.png differ diff --git a/base/usr/share/icons/16/config.bmp b/base/usr/share/icons/16/config.bmp deleted file mode 100644 index c9292484..00000000 Binary files a/base/usr/share/icons/16/config.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/config.png b/base/usr/share/icons/16/config.png new file mode 100644 index 00000000..2147880a Binary files /dev/null and b/base/usr/share/icons/16/config.png differ diff --git a/base/usr/share/icons/16/exit.bmp b/base/usr/share/icons/16/exit.bmp deleted file mode 100644 index 66204512..00000000 Binary files a/base/usr/share/icons/16/exit.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/exit.png b/base/usr/share/icons/16/exit.png new file mode 100644 index 00000000..b9500e33 Binary files /dev/null and b/base/usr/share/icons/16/exit.png differ diff --git a/base/usr/share/icons/16/file.bmp b/base/usr/share/icons/16/file.bmp deleted file mode 100644 index 8ce4635b..00000000 Binary files a/base/usr/share/icons/16/file.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/file.png b/base/usr/share/icons/16/file.png new file mode 100644 index 00000000..19c58240 Binary files /dev/null and b/base/usr/share/icons/16/file.png differ diff --git a/base/usr/share/icons/16/folder.bmp b/base/usr/share/icons/16/folder.bmp deleted file mode 100644 index 9bed35f1..00000000 Binary files a/base/usr/share/icons/16/folder.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/folder.png b/base/usr/share/icons/16/folder.png new file mode 100644 index 00000000..beabe391 Binary files /dev/null and b/base/usr/share/icons/16/folder.png differ diff --git a/base/usr/share/icons/16/forward.bmp b/base/usr/share/icons/16/forward.bmp deleted file mode 100644 index fb4cbeb8..00000000 Binary files a/base/usr/share/icons/16/forward.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/forward.png b/base/usr/share/icons/16/forward.png new file mode 100644 index 00000000..1294d245 Binary files /dev/null and b/base/usr/share/icons/16/forward.png differ diff --git a/base/usr/share/icons/16/help.bmp b/base/usr/share/icons/16/help.bmp deleted file mode 100644 index 405311fa..00000000 Binary files a/base/usr/share/icons/16/help.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/help.png b/base/usr/share/icons/16/help.png new file mode 100644 index 00000000..e3d02955 Binary files /dev/null and b/base/usr/share/icons/16/help.png differ diff --git a/base/usr/share/icons/16/home.bmp b/base/usr/share/icons/16/home.bmp deleted file mode 100644 index 34fc5d3f..00000000 Binary files a/base/usr/share/icons/16/home.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/home.png b/base/usr/share/icons/16/home.png new file mode 100644 index 00000000..505cbd51 Binary files /dev/null and b/base/usr/share/icons/16/home.png differ diff --git a/base/usr/share/icons/16/menu-tick.bmp b/base/usr/share/icons/16/menu-tick.bmp deleted file mode 100644 index 51767cdf..00000000 Binary files a/base/usr/share/icons/16/menu-tick.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/menu-tick.png b/base/usr/share/icons/16/menu-tick.png new file mode 100644 index 00000000..cba85a95 Binary files /dev/null and b/base/usr/share/icons/16/menu-tick.png differ diff --git a/base/usr/share/icons/16/mines.bmp b/base/usr/share/icons/16/mines.bmp deleted file mode 100644 index a4bd03fd..00000000 Binary files a/base/usr/share/icons/16/mines.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/mines.png b/base/usr/share/icons/16/mines.png new file mode 100644 index 00000000..a264bcf2 Binary files /dev/null and b/base/usr/share/icons/16/mines.png differ diff --git a/base/usr/share/icons/16/missing.bmp b/base/usr/share/icons/16/missing.bmp deleted file mode 100644 index a8cde2ba..00000000 Binary files a/base/usr/share/icons/16/missing.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/missing.png b/base/usr/share/icons/16/missing.png new file mode 100644 index 00000000..4fb81e47 Binary files /dev/null and b/base/usr/share/icons/16/missing.png differ diff --git a/base/usr/share/icons/16/new.bmp b/base/usr/share/icons/16/new.bmp deleted file mode 100644 index ce4d11f9..00000000 Binary files a/base/usr/share/icons/16/new.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/new.png b/base/usr/share/icons/16/new.png new file mode 100644 index 00000000..8e4fe8a0 Binary files /dev/null and b/base/usr/share/icons/16/new.png differ diff --git a/base/usr/share/icons/16/open.bmp b/base/usr/share/icons/16/open.bmp deleted file mode 100644 index a0c6de5c..00000000 Binary files a/base/usr/share/icons/16/open.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/open.png b/base/usr/share/icons/16/open.png new file mode 100644 index 00000000..bbb2ef0a Binary files /dev/null and b/base/usr/share/icons/16/open.png differ diff --git a/base/usr/share/icons/16/package.bmp b/base/usr/share/icons/16/package.bmp deleted file mode 100644 index 0f2fb4a0..00000000 Binary files a/base/usr/share/icons/16/package.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/package.png b/base/usr/share/icons/16/package.png new file mode 100644 index 00000000..63b6c89a Binary files /dev/null and b/base/usr/share/icons/16/package.png differ diff --git a/base/usr/share/icons/16/refresh.bmp b/base/usr/share/icons/16/refresh.bmp deleted file mode 100644 index 29bd92e3..00000000 Binary files a/base/usr/share/icons/16/refresh.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/refresh.png b/base/usr/share/icons/16/refresh.png new file mode 100644 index 00000000..3fa66f8b Binary files /dev/null and b/base/usr/share/icons/16/refresh.png differ diff --git a/base/usr/share/icons/16/save.bmp b/base/usr/share/icons/16/save.bmp deleted file mode 100644 index f195e036..00000000 Binary files a/base/usr/share/icons/16/save.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/save.png b/base/usr/share/icons/16/save.png new file mode 100644 index 00000000..c5e27af9 Binary files /dev/null and b/base/usr/share/icons/16/save.png differ diff --git a/base/usr/share/icons/16/star.bmp b/base/usr/share/icons/16/star.bmp deleted file mode 100644 index 7c0a0980..00000000 Binary files a/base/usr/share/icons/16/star.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/star.png b/base/usr/share/icons/16/star.png new file mode 100644 index 00000000..03801823 Binary files /dev/null and b/base/usr/share/icons/16/star.png differ diff --git a/base/usr/share/icons/16/up.bmp b/base/usr/share/icons/16/up.bmp deleted file mode 100644 index 2d7794fe..00000000 Binary files a/base/usr/share/icons/16/up.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/up.png b/base/usr/share/icons/16/up.png new file mode 100644 index 00000000..d4db34b6 Binary files /dev/null and b/base/usr/share/icons/16/up.png differ diff --git a/base/usr/share/icons/16/utilities-terminal.bmp b/base/usr/share/icons/16/utilities-terminal.bmp deleted file mode 100644 index e7048689..00000000 Binary files a/base/usr/share/icons/16/utilities-terminal.bmp and /dev/null differ diff --git a/base/usr/share/icons/16/utilities-terminal.png b/base/usr/share/icons/16/utilities-terminal.png new file mode 100644 index 00000000..3d013b41 Binary files /dev/null and b/base/usr/share/icons/16/utilities-terminal.png differ diff --git a/base/usr/share/icons/24/applications-generic.bmp b/base/usr/share/icons/24/applications-generic.bmp deleted file mode 100644 index 550015b7..00000000 Binary files a/base/usr/share/icons/24/applications-generic.bmp and /dev/null differ diff --git a/base/usr/share/icons/24/applications-generic.png b/base/usr/share/icons/24/applications-generic.png new file mode 100644 index 00000000..8d2956dd Binary files /dev/null and b/base/usr/share/icons/24/applications-generic.png differ diff --git a/base/usr/share/icons/24/mouse-relative.bmp b/base/usr/share/icons/24/mouse-relative.bmp deleted file mode 100644 index f21979a4..00000000 Binary files a/base/usr/share/icons/24/mouse-relative.bmp and /dev/null differ diff --git a/base/usr/share/icons/24/mouse-relative.png b/base/usr/share/icons/24/mouse-relative.png new file mode 100644 index 00000000..04e90354 Binary files /dev/null and b/base/usr/share/icons/24/mouse-relative.png differ diff --git a/base/usr/share/icons/24/mouse-status.bmp b/base/usr/share/icons/24/mouse-status.bmp deleted file mode 100644 index ae78dc55..00000000 Binary files a/base/usr/share/icons/24/mouse-status.bmp and /dev/null differ diff --git a/base/usr/share/icons/24/mouse-status.png b/base/usr/share/icons/24/mouse-status.png new file mode 100644 index 00000000..7163c841 Binary files /dev/null and b/base/usr/share/icons/24/mouse-status.png differ diff --git a/base/usr/share/icons/24/net-active.bmp b/base/usr/share/icons/24/net-active.bmp deleted file mode 100644 index 03b70a48..00000000 Binary files a/base/usr/share/icons/24/net-active.bmp and /dev/null differ diff --git a/base/usr/share/icons/24/net-active.png b/base/usr/share/icons/24/net-active.png new file mode 100644 index 00000000..16855daa Binary files /dev/null and b/base/usr/share/icons/24/net-active.png differ diff --git a/base/usr/share/icons/24/net-disconnected.bmp b/base/usr/share/icons/24/net-disconnected.bmp deleted file mode 100644 index cfa5b149..00000000 Binary files a/base/usr/share/icons/24/net-disconnected.bmp and /dev/null differ diff --git a/base/usr/share/icons/24/net-disconnected.png b/base/usr/share/icons/24/net-disconnected.png new file mode 100644 index 00000000..195f37fb Binary files /dev/null and b/base/usr/share/icons/24/net-disconnected.png differ diff --git a/base/usr/share/icons/24/utilities-terminal.bmp b/base/usr/share/icons/24/utilities-terminal.bmp deleted file mode 100644 index 70dd5e49..00000000 Binary files a/base/usr/share/icons/24/utilities-terminal.bmp and /dev/null differ diff --git a/base/usr/share/icons/24/utilities-terminal.png b/base/usr/share/icons/24/utilities-terminal.png new file mode 100644 index 00000000..2b91a66a Binary files /dev/null and b/base/usr/share/icons/24/utilities-terminal.png differ diff --git a/base/usr/share/icons/24/volume-full.bmp b/base/usr/share/icons/24/volume-full.bmp deleted file mode 100644 index b5c5d51c..00000000 Binary files a/base/usr/share/icons/24/volume-full.bmp and /dev/null differ diff --git a/base/usr/share/icons/24/volume-full.png b/base/usr/share/icons/24/volume-full.png new file mode 100644 index 00000000..7b3e87d5 Binary files /dev/null and b/base/usr/share/icons/24/volume-full.png differ diff --git a/base/usr/share/icons/24/volume-low.bmp b/base/usr/share/icons/24/volume-low.bmp deleted file mode 100644 index a788d5a5..00000000 Binary files a/base/usr/share/icons/24/volume-low.bmp and /dev/null differ diff --git a/base/usr/share/icons/24/volume-low.png b/base/usr/share/icons/24/volume-low.png new file mode 100644 index 00000000..67f2de40 Binary files /dev/null and b/base/usr/share/icons/24/volume-low.png differ diff --git a/base/usr/share/icons/24/volume-medium.bmp b/base/usr/share/icons/24/volume-medium.bmp deleted file mode 100644 index a4a1dd79..00000000 Binary files a/base/usr/share/icons/24/volume-medium.bmp and /dev/null differ diff --git a/base/usr/share/icons/24/volume-medium.png b/base/usr/share/icons/24/volume-medium.png new file mode 100644 index 00000000..ca609505 Binary files /dev/null and b/base/usr/share/icons/24/volume-medium.png differ diff --git a/base/usr/share/icons/24/volume-mute.bmp b/base/usr/share/icons/24/volume-mute.bmp deleted file mode 100644 index 486e5bdf..00000000 Binary files a/base/usr/share/icons/24/volume-mute.bmp and /dev/null differ diff --git a/base/usr/share/icons/24/volume-mute.png b/base/usr/share/icons/24/volume-mute.png new file mode 100644 index 00000000..f2e4c73e Binary files /dev/null and b/base/usr/share/icons/24/volume-mute.png differ diff --git a/base/usr/share/icons/48/applications-generic.bmp b/base/usr/share/icons/48/applications-generic.bmp deleted file mode 100644 index 26c69498..00000000 Binary files a/base/usr/share/icons/48/applications-generic.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/applications-generic.png b/base/usr/share/icons/48/applications-generic.png new file mode 100644 index 00000000..ea185b43 Binary files /dev/null and b/base/usr/share/icons/48/applications-generic.png differ diff --git a/base/usr/share/icons/48/applications-painting.bmp b/base/usr/share/icons/48/applications-painting.bmp deleted file mode 100644 index 61d630c5..00000000 Binary files a/base/usr/share/icons/48/applications-painting.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/applications-painting.png b/base/usr/share/icons/48/applications-painting.png new file mode 100644 index 00000000..883c2c66 Binary files /dev/null and b/base/usr/share/icons/48/applications-painting.png differ diff --git a/base/usr/share/icons/48/c.bmp b/base/usr/share/icons/48/c.bmp deleted file mode 100644 index 0344d6a7..00000000 Binary files a/base/usr/share/icons/48/c.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/c.png b/base/usr/share/icons/48/c.png new file mode 100644 index 00000000..8b978ff9 Binary files /dev/null and b/base/usr/share/icons/48/c.png differ diff --git a/base/usr/share/icons/48/calculator.bmp b/base/usr/share/icons/48/calculator.bmp deleted file mode 100644 index f173e801..00000000 Binary files a/base/usr/share/icons/48/calculator.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/calculator.png b/base/usr/share/icons/48/calculator.png new file mode 100644 index 00000000..e13eba48 Binary files /dev/null and b/base/usr/share/icons/48/calculator.png differ diff --git a/base/usr/share/icons/48/clock.bmp b/base/usr/share/icons/48/clock.bmp deleted file mode 100644 index b2a66bc7..00000000 Binary files a/base/usr/share/icons/48/clock.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/clock.png b/base/usr/share/icons/48/clock.png new file mode 100644 index 00000000..3cc9e69e Binary files /dev/null and b/base/usr/share/icons/48/clock.png differ diff --git a/base/usr/share/icons/48/drawlines.bmp b/base/usr/share/icons/48/drawlines.bmp deleted file mode 100644 index 99d40509..00000000 Binary files a/base/usr/share/icons/48/drawlines.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/drawlines.png b/base/usr/share/icons/48/drawlines.png new file mode 100644 index 00000000..0bf2591c Binary files /dev/null and b/base/usr/share/icons/48/drawlines.png differ diff --git a/base/usr/share/icons/48/exit.bmp b/base/usr/share/icons/48/exit.bmp deleted file mode 100644 index 36c65bc2..00000000 Binary files a/base/usr/share/icons/48/exit.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/exit.png b/base/usr/share/icons/48/exit.png new file mode 100644 index 00000000..3a048209 Binary files /dev/null and b/base/usr/share/icons/48/exit.png differ diff --git a/base/usr/share/icons/48/file.bmp b/base/usr/share/icons/48/file.bmp deleted file mode 100644 index f18d6333..00000000 Binary files a/base/usr/share/icons/48/file.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/file.png b/base/usr/share/icons/48/file.png new file mode 100644 index 00000000..cc123337 Binary files /dev/null and b/base/usr/share/icons/48/file.png differ diff --git a/base/usr/share/icons/48/folder.bmp b/base/usr/share/icons/48/folder.bmp deleted file mode 100644 index 68f0f51d..00000000 Binary files a/base/usr/share/icons/48/folder.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/folder.png b/base/usr/share/icons/48/folder.png new file mode 100644 index 00000000..a3789688 Binary files /dev/null and b/base/usr/share/icons/48/folder.png differ diff --git a/base/usr/share/icons/48/font.bmp b/base/usr/share/icons/48/font.bmp deleted file mode 100644 index 9ce675cb..00000000 Binary files a/base/usr/share/icons/48/font.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/font.png b/base/usr/share/icons/48/font.png new file mode 100644 index 00000000..43b63964 Binary files /dev/null and b/base/usr/share/icons/48/font.png differ diff --git a/base/usr/share/icons/48/gears.bmp b/base/usr/share/icons/48/gears.bmp deleted file mode 100644 index 5775e3db..00000000 Binary files a/base/usr/share/icons/48/gears.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/gears.png b/base/usr/share/icons/48/gears.png new file mode 100644 index 00000000..1deccfa4 Binary files /dev/null and b/base/usr/share/icons/48/gears.png differ diff --git a/base/usr/share/icons/48/h.bmp b/base/usr/share/icons/48/h.bmp deleted file mode 100644 index 0e97abbb..00000000 Binary files a/base/usr/share/icons/48/h.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/h.png b/base/usr/share/icons/48/h.png new file mode 100644 index 00000000..c2b8f618 Binary files /dev/null and b/base/usr/share/icons/48/h.png differ diff --git a/base/usr/share/icons/48/help.bmp b/base/usr/share/icons/48/help.bmp deleted file mode 100644 index ed5e9c66..00000000 Binary files a/base/usr/share/icons/48/help.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/help.png b/base/usr/share/icons/48/help.png new file mode 100644 index 00000000..98c7e066 Binary files /dev/null and b/base/usr/share/icons/48/help.png differ diff --git a/base/usr/share/icons/48/image.bmp b/base/usr/share/icons/48/image.bmp deleted file mode 100644 index 6e525701..00000000 Binary files a/base/usr/share/icons/48/image.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/image.png b/base/usr/share/icons/48/image.png new file mode 100644 index 00000000..109c1b5a Binary files /dev/null and b/base/usr/share/icons/48/image.png differ diff --git a/base/usr/share/icons/48/julia.bmp b/base/usr/share/icons/48/julia.bmp deleted file mode 100644 index be4d0231..00000000 Binary files a/base/usr/share/icons/48/julia.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/julia.png b/base/usr/share/icons/48/julia.png new file mode 100644 index 00000000..6348a874 Binary files /dev/null and b/base/usr/share/icons/48/julia.png differ diff --git a/base/usr/share/icons/48/mines.bmp b/base/usr/share/icons/48/mines.bmp deleted file mode 100644 index f8aac1da..00000000 Binary files a/base/usr/share/icons/48/mines.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/mines.png b/base/usr/share/icons/48/mines.png new file mode 100644 index 00000000..ab84fcb5 Binary files /dev/null and b/base/usr/share/icons/48/mines.png differ diff --git a/base/usr/share/icons/48/package-uninstalled.bmp b/base/usr/share/icons/48/package-uninstalled.bmp deleted file mode 100644 index 91b3a2b6..00000000 Binary files a/base/usr/share/icons/48/package-uninstalled.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/package-uninstalled.png b/base/usr/share/icons/48/package-uninstalled.png new file mode 100644 index 00000000..d86df19f Binary files /dev/null and b/base/usr/share/icons/48/package-uninstalled.png differ diff --git a/base/usr/share/icons/48/package.bmp b/base/usr/share/icons/48/package.bmp deleted file mode 100644 index 0d76c7fe..00000000 Binary files a/base/usr/share/icons/48/package.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/package.png b/base/usr/share/icons/48/package.png new file mode 100644 index 00000000..1527e60f Binary files /dev/null and b/base/usr/share/icons/48/package.png differ diff --git a/base/usr/share/icons/48/plasma.bmp b/base/usr/share/icons/48/plasma.bmp deleted file mode 100644 index bfcea84b..00000000 Binary files a/base/usr/share/icons/48/plasma.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/plasma.png b/base/usr/share/icons/48/plasma.png new file mode 100644 index 00000000..4564bd72 Binary files /dev/null and b/base/usr/share/icons/48/plasma.png differ diff --git a/base/usr/share/icons/48/py.bmp b/base/usr/share/icons/48/py.bmp deleted file mode 100644 index f820f514..00000000 Binary files a/base/usr/share/icons/48/py.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/py.png b/base/usr/share/icons/48/py.png new file mode 100644 index 00000000..98680ce6 Binary files /dev/null and b/base/usr/share/icons/48/py.png differ diff --git a/base/usr/share/icons/48/sh.bmp b/base/usr/share/icons/48/sh.bmp deleted file mode 100644 index cf08ccbf..00000000 Binary files a/base/usr/share/icons/48/sh.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/sh.png b/base/usr/share/icons/48/sh.png new file mode 100644 index 00000000..1a721620 Binary files /dev/null and b/base/usr/share/icons/48/sh.png differ diff --git a/base/usr/share/icons/48/star.bmp b/base/usr/share/icons/48/star.bmp deleted file mode 100644 index 53cc6838..00000000 Binary files a/base/usr/share/icons/48/star.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/star.png b/base/usr/share/icons/48/star.png new file mode 100644 index 00000000..0a265710 Binary files /dev/null and b/base/usr/share/icons/48/star.png differ diff --git a/base/usr/share/icons/48/utilities-terminal.bmp b/base/usr/share/icons/48/utilities-terminal.bmp deleted file mode 100644 index 83cb6e77..00000000 Binary files a/base/usr/share/icons/48/utilities-terminal.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/utilities-terminal.png b/base/usr/share/icons/48/utilities-terminal.png new file mode 100644 index 00000000..c7b1165a Binary files /dev/null and b/base/usr/share/icons/48/utilities-terminal.png differ diff --git a/base/usr/share/icons/48/wallpaper-picker.bmp b/base/usr/share/icons/48/wallpaper-picker.bmp deleted file mode 100644 index 38e99ee0..00000000 Binary files a/base/usr/share/icons/48/wallpaper-picker.bmp and /dev/null differ diff --git a/base/usr/share/icons/48/wallpaper-picker.png b/base/usr/share/icons/48/wallpaper-picker.png new file mode 100644 index 00000000..155d4de3 Binary files /dev/null and b/base/usr/share/icons/48/wallpaper-picker.png differ diff --git a/base/usr/share/icons/panel-shutdown.bmp b/base/usr/share/icons/panel-shutdown.bmp deleted file mode 100644 index 3e983e40..00000000 Binary files a/base/usr/share/icons/panel-shutdown.bmp and /dev/null differ diff --git a/base/usr/share/icons/panel-shutdown.png b/base/usr/share/icons/panel-shutdown.png new file mode 100644 index 00000000..7561d48b Binary files /dev/null and b/base/usr/share/icons/panel-shutdown.png differ diff --git a/base/usr/share/icons/watchface.bmp b/base/usr/share/icons/watchface.bmp deleted file mode 100644 index e7947393..00000000 Binary files a/base/usr/share/icons/watchface.bmp and /dev/null differ diff --git a/base/usr/share/icons/watchface.png b/base/usr/share/icons/watchface.png new file mode 100644 index 00000000..3b6223c8 Binary files /dev/null and b/base/usr/share/icons/watchface.png differ diff --git a/base/usr/share/icons/weather/01d.bmp b/base/usr/share/icons/weather/01d.bmp deleted file mode 100644 index 67aa3f49..00000000 Binary files a/base/usr/share/icons/weather/01d.bmp and /dev/null differ diff --git a/base/usr/share/icons/weather/01d.png b/base/usr/share/icons/weather/01d.png new file mode 100644 index 00000000..28d41a02 Binary files /dev/null and b/base/usr/share/icons/weather/01d.png differ diff --git a/base/usr/share/icons/weather/01n.bmp b/base/usr/share/icons/weather/01n.bmp deleted file mode 100644 index 22f1cd36..00000000 Binary files a/base/usr/share/icons/weather/01n.bmp and /dev/null differ diff --git a/base/usr/share/icons/weather/01n.png b/base/usr/share/icons/weather/01n.png new file mode 100644 index 00000000..f085d083 Binary files /dev/null and b/base/usr/share/icons/weather/01n.png differ diff --git a/base/usr/share/icons/weather/02d.bmp b/base/usr/share/icons/weather/02d.bmp deleted file mode 100644 index 3be2a47a..00000000 Binary files a/base/usr/share/icons/weather/02d.bmp and /dev/null differ diff --git a/base/usr/share/icons/weather/02d.png b/base/usr/share/icons/weather/02d.png new file mode 100644 index 00000000..071687f1 Binary files /dev/null and b/base/usr/share/icons/weather/02d.png differ diff --git a/base/usr/share/icons/weather/02n.bmp b/base/usr/share/icons/weather/02n.bmp deleted file mode 100644 index 46563ff1..00000000 Binary files a/base/usr/share/icons/weather/02n.bmp and /dev/null differ diff --git a/base/usr/share/icons/weather/02n.png b/base/usr/share/icons/weather/02n.png new file mode 100644 index 00000000..0184b5ac Binary files /dev/null and b/base/usr/share/icons/weather/02n.png differ diff --git a/base/usr/share/icons/weather/03d.bmp b/base/usr/share/icons/weather/03d.bmp deleted file mode 120000 index 2802226f..00000000 --- a/base/usr/share/icons/weather/03d.bmp +++ /dev/null @@ -1 +0,0 @@ -02d.bmp \ No newline at end of file diff --git a/base/usr/share/icons/weather/03d.png b/base/usr/share/icons/weather/03d.png new file mode 120000 index 00000000..4568cc8a --- /dev/null +++ b/base/usr/share/icons/weather/03d.png @@ -0,0 +1 @@ +02d.png \ No newline at end of file diff --git a/base/usr/share/icons/weather/03n.bmp b/base/usr/share/icons/weather/03n.bmp deleted file mode 120000 index ba14749e..00000000 --- a/base/usr/share/icons/weather/03n.bmp +++ /dev/null @@ -1 +0,0 @@ -02n.bmp \ No newline at end of file diff --git a/base/usr/share/icons/weather/03n.png b/base/usr/share/icons/weather/03n.png new file mode 120000 index 00000000..d3dfe499 --- /dev/null +++ b/base/usr/share/icons/weather/03n.png @@ -0,0 +1 @@ +02n.png \ No newline at end of file diff --git a/base/usr/share/icons/weather/04d.bmp b/base/usr/share/icons/weather/04d.bmp deleted file mode 100644 index 1cc47a0d..00000000 Binary files a/base/usr/share/icons/weather/04d.bmp and /dev/null differ diff --git a/base/usr/share/icons/weather/04d.png b/base/usr/share/icons/weather/04d.png new file mode 100644 index 00000000..3e30e504 Binary files /dev/null and b/base/usr/share/icons/weather/04d.png differ diff --git a/base/usr/share/icons/weather/04n.bmp b/base/usr/share/icons/weather/04n.bmp deleted file mode 100644 index d99f1a15..00000000 Binary files a/base/usr/share/icons/weather/04n.bmp and /dev/null differ diff --git a/base/usr/share/icons/weather/04n.png b/base/usr/share/icons/weather/04n.png new file mode 100644 index 00000000..72483433 Binary files /dev/null and b/base/usr/share/icons/weather/04n.png differ diff --git a/base/usr/share/icons/weather/09d.bmp b/base/usr/share/icons/weather/09d.bmp deleted file mode 100644 index 5a388ae6..00000000 Binary files a/base/usr/share/icons/weather/09d.bmp and /dev/null differ diff --git a/base/usr/share/icons/weather/09d.png b/base/usr/share/icons/weather/09d.png new file mode 100644 index 00000000..7548977c Binary files /dev/null and b/base/usr/share/icons/weather/09d.png differ diff --git a/base/usr/share/icons/weather/09n.bmp b/base/usr/share/icons/weather/09n.bmp deleted file mode 120000 index 9cd57e20..00000000 --- a/base/usr/share/icons/weather/09n.bmp +++ /dev/null @@ -1 +0,0 @@ -09d.bmp \ No newline at end of file diff --git a/base/usr/share/icons/weather/09n.png b/base/usr/share/icons/weather/09n.png new file mode 120000 index 00000000..cca1f5de --- /dev/null +++ b/base/usr/share/icons/weather/09n.png @@ -0,0 +1 @@ +09d.png \ No newline at end of file diff --git a/base/usr/share/icons/weather/10d.bmp b/base/usr/share/icons/weather/10d.bmp deleted file mode 100644 index 2ab7599d..00000000 Binary files a/base/usr/share/icons/weather/10d.bmp and /dev/null differ diff --git a/base/usr/share/icons/weather/10d.png b/base/usr/share/icons/weather/10d.png new file mode 100644 index 00000000..a4b1725c Binary files /dev/null and b/base/usr/share/icons/weather/10d.png differ diff --git a/base/usr/share/icons/weather/10n.bmp b/base/usr/share/icons/weather/10n.bmp deleted file mode 120000 index 275ca5e5..00000000 --- a/base/usr/share/icons/weather/10n.bmp +++ /dev/null @@ -1 +0,0 @@ -10d.bmp \ No newline at end of file diff --git a/base/usr/share/icons/weather/10n.png b/base/usr/share/icons/weather/10n.png new file mode 120000 index 00000000..6e012271 --- /dev/null +++ b/base/usr/share/icons/weather/10n.png @@ -0,0 +1 @@ +10d.png \ No newline at end of file diff --git a/base/usr/share/icons/weather/11d.bmp b/base/usr/share/icons/weather/11d.bmp deleted file mode 100644 index 0a9cb3c2..00000000 Binary files a/base/usr/share/icons/weather/11d.bmp and /dev/null differ diff --git a/base/usr/share/icons/weather/11d.png b/base/usr/share/icons/weather/11d.png new file mode 100644 index 00000000..4a6767d5 Binary files /dev/null and b/base/usr/share/icons/weather/11d.png differ diff --git a/base/usr/share/icons/weather/11n.bmp b/base/usr/share/icons/weather/11n.bmp deleted file mode 120000 index f459c00f..00000000 --- a/base/usr/share/icons/weather/11n.bmp +++ /dev/null @@ -1 +0,0 @@ -11d.bmp \ No newline at end of file diff --git a/base/usr/share/icons/weather/11n.png b/base/usr/share/icons/weather/11n.png new file mode 120000 index 00000000..b227917d --- /dev/null +++ b/base/usr/share/icons/weather/11n.png @@ -0,0 +1 @@ +11d.png \ No newline at end of file diff --git a/base/usr/share/icons/weather/13d.bmp b/base/usr/share/icons/weather/13d.bmp deleted file mode 100644 index 3fdeed06..00000000 Binary files a/base/usr/share/icons/weather/13d.bmp and /dev/null differ diff --git a/base/usr/share/icons/weather/13d.png b/base/usr/share/icons/weather/13d.png new file mode 100644 index 00000000..f0dd8c08 Binary files /dev/null and b/base/usr/share/icons/weather/13d.png differ diff --git a/base/usr/share/icons/weather/13n.bmp b/base/usr/share/icons/weather/13n.bmp deleted file mode 120000 index 19049049..00000000 --- a/base/usr/share/icons/weather/13n.bmp +++ /dev/null @@ -1 +0,0 @@ -13d.bmp \ No newline at end of file diff --git a/base/usr/share/icons/weather/13n.png b/base/usr/share/icons/weather/13n.png new file mode 120000 index 00000000..94e5a525 --- /dev/null +++ b/base/usr/share/icons/weather/13n.png @@ -0,0 +1 @@ +13d.png \ No newline at end of file diff --git a/base/usr/share/icons/weather/50d.bmp b/base/usr/share/icons/weather/50d.bmp deleted file mode 100644 index 18c8550c..00000000 Binary files a/base/usr/share/icons/weather/50d.bmp and /dev/null differ diff --git a/base/usr/share/icons/weather/50d.png b/base/usr/share/icons/weather/50d.png new file mode 100644 index 00000000..b3b4e5b8 Binary files /dev/null and b/base/usr/share/icons/weather/50d.png differ diff --git a/base/usr/share/icons/weather/50n.bmp b/base/usr/share/icons/weather/50n.bmp deleted file mode 120000 index 33f9c80b..00000000 --- a/base/usr/share/icons/weather/50n.bmp +++ /dev/null @@ -1 +0,0 @@ -50d.bmp \ No newline at end of file diff --git a/base/usr/share/icons/weather/50n.png b/base/usr/share/icons/weather/50n.png new file mode 120000 index 00000000..e3ba9612 --- /dev/null +++ b/base/usr/share/icons/weather/50n.png @@ -0,0 +1 @@ +50d.png \ No newline at end of file diff --git a/base/usr/share/logo_login.png b/base/usr/share/logo_login.png new file mode 100644 index 00000000..409e2f60 Binary files /dev/null and b/base/usr/share/logo_login.png differ diff --git a/base/usr/share/logo_small.png b/base/usr/share/logo_small.png new file mode 100644 index 00000000..1917e6ee Binary files /dev/null and b/base/usr/share/logo_small.png differ diff --git a/base/usr/share/panel.png b/base/usr/share/panel.png new file mode 100644 index 00000000..6c11b0e8 Binary files /dev/null and b/base/usr/share/panel.png differ diff --git a/base/usr/share/pong/ball.bmp b/base/usr/share/pong/ball.bmp deleted file mode 100644 index 9d9a10a7..00000000 Binary files a/base/usr/share/pong/ball.bmp and /dev/null differ diff --git a/base/usr/share/pong/ball.png b/base/usr/share/pong/ball.png new file mode 100644 index 00000000..90e31596 Binary files /dev/null and b/base/usr/share/pong/ball.png differ diff --git a/base/usr/share/pong/paddle-blue.bmp b/base/usr/share/pong/paddle-blue.bmp deleted file mode 100644 index a0bc3faf..00000000 Binary files a/base/usr/share/pong/paddle-blue.bmp and /dev/null differ diff --git a/base/usr/share/pong/paddle-blue.png b/base/usr/share/pong/paddle-blue.png new file mode 100644 index 00000000..517c94ef Binary files /dev/null and b/base/usr/share/pong/paddle-blue.png differ diff --git a/base/usr/share/pong/paddle-red.bmp b/base/usr/share/pong/paddle-red.bmp deleted file mode 100644 index ba08e317..00000000 Binary files a/base/usr/share/pong/paddle-red.bmp and /dev/null differ diff --git a/base/usr/share/pong/paddle-red.png b/base/usr/share/pong/paddle-red.png new file mode 100644 index 00000000..67ca88bc Binary files /dev/null and b/base/usr/share/pong/paddle-red.png differ diff --git a/base/usr/share/ttk/active/button-close.bmp b/base/usr/share/ttk/active/button-close.bmp deleted file mode 100644 index db1ae5eb..00000000 Binary files a/base/usr/share/ttk/active/button-close.bmp and /dev/null differ diff --git a/base/usr/share/ttk/active/button-close.png b/base/usr/share/ttk/active/button-close.png new file mode 100644 index 00000000..4cc268b1 Binary files /dev/null and b/base/usr/share/ttk/active/button-close.png differ diff --git a/base/usr/share/ttk/active/button-maximize.bmp b/base/usr/share/ttk/active/button-maximize.bmp deleted file mode 100644 index ef149485..00000000 Binary files a/base/usr/share/ttk/active/button-maximize.bmp and /dev/null differ diff --git a/base/usr/share/ttk/active/button-maximize.png b/base/usr/share/ttk/active/button-maximize.png new file mode 100644 index 00000000..ed759029 Binary files /dev/null and b/base/usr/share/ttk/active/button-maximize.png differ diff --git a/base/usr/share/ttk/active/ll.bmp b/base/usr/share/ttk/active/ll.bmp deleted file mode 100644 index 054f155f..00000000 Binary files a/base/usr/share/ttk/active/ll.bmp and /dev/null differ diff --git a/base/usr/share/ttk/active/ll.png b/base/usr/share/ttk/active/ll.png new file mode 100644 index 00000000..9319753f Binary files /dev/null and b/base/usr/share/ttk/active/ll.png differ diff --git a/base/usr/share/ttk/active/lm.bmp b/base/usr/share/ttk/active/lm.bmp deleted file mode 100644 index d7a2ea4b..00000000 Binary files a/base/usr/share/ttk/active/lm.bmp and /dev/null differ diff --git a/base/usr/share/ttk/active/lm.png b/base/usr/share/ttk/active/lm.png new file mode 100644 index 00000000..064f151b Binary files /dev/null and b/base/usr/share/ttk/active/lm.png differ diff --git a/base/usr/share/ttk/active/lr.bmp b/base/usr/share/ttk/active/lr.bmp deleted file mode 100644 index 7376bc3c..00000000 Binary files a/base/usr/share/ttk/active/lr.bmp and /dev/null differ diff --git a/base/usr/share/ttk/active/lr.png b/base/usr/share/ttk/active/lr.png new file mode 100644 index 00000000..02dfc986 Binary files /dev/null and b/base/usr/share/ttk/active/lr.png differ diff --git a/base/usr/share/ttk/active/ml.bmp b/base/usr/share/ttk/active/ml.bmp deleted file mode 100644 index c8cab9a9..00000000 Binary files a/base/usr/share/ttk/active/ml.bmp and /dev/null differ diff --git a/base/usr/share/ttk/active/ml.png b/base/usr/share/ttk/active/ml.png new file mode 100644 index 00000000..1a628276 Binary files /dev/null and b/base/usr/share/ttk/active/ml.png differ diff --git a/base/usr/share/ttk/active/mr.bmp b/base/usr/share/ttk/active/mr.bmp deleted file mode 100644 index a853e180..00000000 Binary files a/base/usr/share/ttk/active/mr.bmp and /dev/null differ diff --git a/base/usr/share/ttk/active/mr.png b/base/usr/share/ttk/active/mr.png new file mode 100644 index 00000000..44f02ec7 Binary files /dev/null and b/base/usr/share/ttk/active/mr.png differ diff --git a/base/usr/share/ttk/active/ul.bmp b/base/usr/share/ttk/active/ul.bmp deleted file mode 100644 index 71d5b885..00000000 Binary files a/base/usr/share/ttk/active/ul.bmp and /dev/null differ diff --git a/base/usr/share/ttk/active/ul.png b/base/usr/share/ttk/active/ul.png new file mode 100644 index 00000000..12632ecb Binary files /dev/null and b/base/usr/share/ttk/active/ul.png differ diff --git a/base/usr/share/ttk/active/um.bmp b/base/usr/share/ttk/active/um.bmp deleted file mode 100644 index 6ed9d427..00000000 Binary files a/base/usr/share/ttk/active/um.bmp and /dev/null differ diff --git a/base/usr/share/ttk/active/um.png b/base/usr/share/ttk/active/um.png new file mode 100644 index 00000000..301789af Binary files /dev/null and b/base/usr/share/ttk/active/um.png differ diff --git a/base/usr/share/ttk/active/ur.bmp b/base/usr/share/ttk/active/ur.bmp deleted file mode 100644 index ccd0aed6..00000000 Binary files a/base/usr/share/ttk/active/ur.bmp and /dev/null differ diff --git a/base/usr/share/ttk/active/ur.png b/base/usr/share/ttk/active/ur.png new file mode 100644 index 00000000..12fe9951 Binary files /dev/null and b/base/usr/share/ttk/active/ur.png differ diff --git a/base/usr/share/ttk/inactive/button-close.bmp b/base/usr/share/ttk/inactive/button-close.bmp deleted file mode 100644 index b406ab9a..00000000 Binary files a/base/usr/share/ttk/inactive/button-close.bmp and /dev/null differ diff --git a/base/usr/share/ttk/inactive/button-close.png b/base/usr/share/ttk/inactive/button-close.png new file mode 100644 index 00000000..2b4e087f Binary files /dev/null and b/base/usr/share/ttk/inactive/button-close.png differ diff --git a/base/usr/share/ttk/inactive/button-maximize.bmp b/base/usr/share/ttk/inactive/button-maximize.bmp deleted file mode 100644 index 64595454..00000000 Binary files a/base/usr/share/ttk/inactive/button-maximize.bmp and /dev/null differ diff --git a/base/usr/share/ttk/inactive/button-maximize.png b/base/usr/share/ttk/inactive/button-maximize.png new file mode 100644 index 00000000..415e5353 Binary files /dev/null and b/base/usr/share/ttk/inactive/button-maximize.png differ diff --git a/base/usr/share/ttk/inactive/ll.bmp b/base/usr/share/ttk/inactive/ll.bmp deleted file mode 100644 index 37d17bd2..00000000 Binary files a/base/usr/share/ttk/inactive/ll.bmp and /dev/null differ diff --git a/base/usr/share/ttk/inactive/ll.png b/base/usr/share/ttk/inactive/ll.png new file mode 100644 index 00000000..978083fd Binary files /dev/null and b/base/usr/share/ttk/inactive/ll.png differ diff --git a/base/usr/share/ttk/inactive/lm.bmp b/base/usr/share/ttk/inactive/lm.bmp deleted file mode 100644 index 81dd77df..00000000 Binary files a/base/usr/share/ttk/inactive/lm.bmp and /dev/null differ diff --git a/base/usr/share/ttk/inactive/lm.png b/base/usr/share/ttk/inactive/lm.png new file mode 100644 index 00000000..37d760f0 Binary files /dev/null and b/base/usr/share/ttk/inactive/lm.png differ diff --git a/base/usr/share/ttk/inactive/lr.bmp b/base/usr/share/ttk/inactive/lr.bmp deleted file mode 100644 index a8268191..00000000 Binary files a/base/usr/share/ttk/inactive/lr.bmp and /dev/null differ diff --git a/base/usr/share/ttk/inactive/lr.png b/base/usr/share/ttk/inactive/lr.png new file mode 100644 index 00000000..c19fc718 Binary files /dev/null and b/base/usr/share/ttk/inactive/lr.png differ diff --git a/base/usr/share/ttk/inactive/ml.bmp b/base/usr/share/ttk/inactive/ml.bmp deleted file mode 100644 index 91556d31..00000000 Binary files a/base/usr/share/ttk/inactive/ml.bmp and /dev/null differ diff --git a/base/usr/share/ttk/inactive/ml.png b/base/usr/share/ttk/inactive/ml.png new file mode 100644 index 00000000..205377b1 Binary files /dev/null and b/base/usr/share/ttk/inactive/ml.png differ diff --git a/base/usr/share/ttk/inactive/mr.bmp b/base/usr/share/ttk/inactive/mr.bmp deleted file mode 100644 index 259e94b5..00000000 Binary files a/base/usr/share/ttk/inactive/mr.bmp and /dev/null differ diff --git a/base/usr/share/ttk/inactive/mr.png b/base/usr/share/ttk/inactive/mr.png new file mode 100644 index 00000000..c08e8d38 Binary files /dev/null and b/base/usr/share/ttk/inactive/mr.png differ diff --git a/base/usr/share/ttk/inactive/ul.bmp b/base/usr/share/ttk/inactive/ul.bmp deleted file mode 100644 index 56d05b20..00000000 Binary files a/base/usr/share/ttk/inactive/ul.bmp and /dev/null differ diff --git a/base/usr/share/ttk/inactive/ul.png b/base/usr/share/ttk/inactive/ul.png new file mode 100644 index 00000000..2afcba2b Binary files /dev/null and b/base/usr/share/ttk/inactive/ul.png differ diff --git a/base/usr/share/ttk/inactive/um.bmp b/base/usr/share/ttk/inactive/um.bmp deleted file mode 100644 index 2cdb7424..00000000 Binary files a/base/usr/share/ttk/inactive/um.bmp and /dev/null differ diff --git a/base/usr/share/ttk/inactive/um.png b/base/usr/share/ttk/inactive/um.png new file mode 100644 index 00000000..5a110d9f Binary files /dev/null and b/base/usr/share/ttk/inactive/um.png differ diff --git a/base/usr/share/ttk/inactive/ur.bmp b/base/usr/share/ttk/inactive/ur.bmp deleted file mode 100644 index 08d8edd9..00000000 Binary files a/base/usr/share/ttk/inactive/ur.bmp and /dev/null differ diff --git a/base/usr/share/ttk/inactive/ur.png b/base/usr/share/ttk/inactive/ur.png new file mode 100644 index 00000000..5f35c5d3 Binary files /dev/null and b/base/usr/share/ttk/inactive/ur.png differ diff --git a/base/usr/share/ttk/toast/default.png b/base/usr/share/ttk/toast/default.png new file mode 100644 index 00000000..a7ec4a62 Binary files /dev/null and b/base/usr/share/ttk/toast/default.png differ diff --git a/base/usr/share/wallpapers/miyajima.jpg b/base/usr/share/wallpapers/miyajima.jpg new file mode 100644 index 00000000..8b5b3d1e Binary files /dev/null and b/base/usr/share/wallpapers/miyajima.jpg differ diff --git a/boot/moremultiboot.h b/boot/moremultiboot.h index f06cb13b..41c749f2 100644 --- a/boot/moremultiboot.h +++ b/boot/moremultiboot.h @@ -916,6 +916,9 @@ static void boot(void) { do_it(&ata_secondary_slave); } + print_("Unable to find boot drive, can not continue.\n"); + print_("Please try GRUB or the EFI loader instead.\n"); + while (1); } #endif diff --git a/kernel/cpu/gdt.c b/kernel/cpu/gdt.c index aec13deb..3f090707 100644 --- a/kernel/cpu/gdt.c +++ b/kernel/cpu/gdt.c @@ -30,9 +30,9 @@ typedef struct { /* In the future we may need to put a lock on the access of this */ static struct { - gdt_entry_t entries[6]; - gdt_pointer_t pointer; - tss_entry_t tss; + gdt_entry_t entries[7]; + gdt_pointer_t pointer; + tss_entry_t tss; } gdt __attribute__((used)); extern void gdt_flush(uintptr_t); @@ -53,6 +53,17 @@ void gdt_set_gate(uint8_t num, uint64_t base, uint64_t limit, uint8_t access, ui ENTRY(num).access = access; } +void gdt_set_gsbase(uintptr_t base) { + ENTRY(6).base_low = (base & 0xFFFF); + ENTRY(6).base_middle = (base >> 16) & 0xFF; + ENTRY(6).base_high = (base >> 24) & 0xFF; + asm volatile ("mov %0, %%gs" :: "r"((6 << 3) | 0x3)); +} + +uintptr_t gdt_get_gsbase(void) { + return (ENTRY(6).base_low) | (ENTRY(6).base_middle << 16) | (ENTRY(6).base_high << 24); +} + static void write_tss(int32_t num, uint16_t ss0, uint32_t esp0); void gdt_install(void) { @@ -65,8 +76,8 @@ void gdt_install(void) { gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF); /* Data segment */ gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF); /* User code */ gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF); /* User data */ - write_tss(5, 0x10, 0x0); + gdt_set_gate(6, 0, 0xFFFFFFFF, 0xF2, 0xCF); /* Go go go */ gdt_flush((uintptr_t)gdtp); diff --git a/kernel/sys/signal.c b/kernel/sys/signal.c index f788eec9..05aab4a8 100644 --- a/kernel/sys/signal.c +++ b/kernel/sys/signal.c @@ -24,6 +24,7 @@ void enter_signal_handler(uintptr_t location, int signum, uintptr_t stack) { "mov %%ax, %%ds\n" "mov %%ax, %%es\n" "mov %%ax, %%fs\n" + "mov $0x33, %%ax\n" /* Segment selector */ "mov %%ax, %%gs\n" "mov %%esp, %%eax\n" /* Stack -> EAX */ "pushl $0x23\n" /* Segment selector again */ diff --git a/kernel/sys/syscall.c b/kernel/sys/syscall.c index 72cd3614..d7069d2a 100644 --- a/kernel/sys/syscall.c +++ b/kernel/sys/syscall.c @@ -784,6 +784,12 @@ static int sys_sysfunc(int fn, char ** args) { return 0; + case 14: + PTR_VALIDATE(args); + current_process->thread.gsbase = (uintptr_t)args[0]; + gdt_set_gsbase(current_process->thread.gsbase); + return 0; + default: debug_print(ERROR, "Bad system function %d", fn); break; diff --git a/kernel/sys/task.c b/kernel/sys/task.c index 9921e66b..b0008120 100644 --- a/kernel/sys/task.c +++ b/kernel/sys/task.c @@ -218,6 +218,7 @@ uint32_t fork(void) { new_proc->thread.esp = esp; new_proc->thread.ebp = ebp; + new_proc->thread.gsbase = current_process->thread.gsbase; new_proc->is_tasklet = parent->is_tasklet; @@ -272,6 +273,7 @@ int create_kernel_tasklet(tasklet_t tasklet, char * name, void * argp) { new_proc->thread.esp = esp; new_proc->thread.ebp = ebp; + new_proc->thread.gsbase = current_process->thread.gsbase; new_proc->thread.eip = (uintptr_t)tasklet; @@ -339,6 +341,7 @@ clone(uintptr_t new_stack, uintptr_t thread_func, uintptr_t arg) { new_proc->thread.esp = esp; new_proc->thread.ebp = ebp; + new_proc->thread.gsbase = current_process->thread.gsbase; new_proc->is_tasklet = parent->is_tasklet; @@ -405,6 +408,7 @@ void switch_task(uint8_t reschedule) { current_process->thread.eip = eip; current_process->thread.esp = esp; current_process->thread.ebp = ebp; + current_process->thread.gsbase = gdt_get_gsbase(); current_process->running = 0; /* Save floating point state */ @@ -432,6 +436,7 @@ void switch_next(void) { eip = current_process->thread.eip; esp = current_process->thread.esp; ebp = current_process->thread.ebp; + gdt_set_gsbase(current_process->thread.gsbase); unswitch_fpu(); /* Validate */ diff --git a/kernel/sys/version.c b/kernel/sys/version.c index 09e86151..9f02efed 100644 --- a/kernel/sys/version.c +++ b/kernel/sys/version.c @@ -16,7 +16,7 @@ char * __kernel_name = "ponyos"; char * __kernel_version_format = "%d.%d.%d-%s"; /* Version numbers X.Y.Z */ -int __kernel_version_major = 6; +int __kernel_version_major = 7; int __kernel_version_minor = 0; int __kernel_version_lower = 0; @@ -27,7 +27,7 @@ int __kernel_version_lower = 0; char * __kernel_version_suffix = "mlp"; /* The release codename. */ -char * __kernel_version_codename = "discord"; +char * __kernel_version_codename = "spitfire"; /* Build architecture (should probably not be * here as a string, but rather some sort of diff --git a/kernel/user.S b/kernel/user.S index df8a27d3..52f3e5c3 100644 --- a/kernel/user.S +++ b/kernel/user.S @@ -32,6 +32,8 @@ enter_userspace: mov %eax, %ds mov %eax, %es mov %eax, %fs + + mov $0x33, %ax mov %eax, %gs /* %ss is handled by iret */ diff --git a/kuroko b/kuroko new file mode 160000 index 00000000..ed5d03b0 --- /dev/null +++ b/kuroko @@ -0,0 +1 @@ +Subproject commit ed5d03b001a6d3b1154999312c233700af0d3e16 diff --git a/lib/decor-fancy.c b/lib/decor-fancy.c index 051d97e7..11d31070 100644 --- a/lib/decor-fancy.c +++ b/lib/decor-fancy.c @@ -42,7 +42,6 @@ static int (*freetype_draw_string_width)(char * s) = NULL; static void init_sprite(int id, char * path) { sprites[id] = malloc(sizeof(sprite_t)); load_sprite(sprites[id], path); - sprites[id]->alpha = ALPHA_EMBEDDED; } static int get_bounds_fancy(yutani_window_t * window, struct decor_bounds * bounds) { @@ -201,27 +200,27 @@ static int check_button_press_fancy(yutani_window_t * window, int x, int y) { } void decor_init() { - init_sprite(0, TTK_FANCY_PATH "active/ul.bmp"); - init_sprite(1, TTK_FANCY_PATH "active/um.bmp"); - init_sprite(2, TTK_FANCY_PATH "active/ur.bmp"); - init_sprite(3, TTK_FANCY_PATH "active/ml.bmp"); - init_sprite(4, TTK_FANCY_PATH "active/mr.bmp"); - init_sprite(5, TTK_FANCY_PATH "active/ll.bmp"); - init_sprite(6, TTK_FANCY_PATH "active/lm.bmp"); - init_sprite(7, TTK_FANCY_PATH "active/lr.bmp"); - init_sprite(8, TTK_FANCY_PATH "active/button-close.bmp"); - init_sprite(9, TTK_FANCY_PATH "active/button-maximize.bmp"); - - init_sprite(INACTIVE + 0, TTK_FANCY_PATH "inactive/ul.bmp"); - init_sprite(INACTIVE + 1, TTK_FANCY_PATH "inactive/um.bmp"); - init_sprite(INACTIVE + 2, TTK_FANCY_PATH "inactive/ur.bmp"); - init_sprite(INACTIVE + 3, TTK_FANCY_PATH "inactive/ml.bmp"); - init_sprite(INACTIVE + 4, TTK_FANCY_PATH "inactive/mr.bmp"); - init_sprite(INACTIVE + 5, TTK_FANCY_PATH "inactive/ll.bmp"); - init_sprite(INACTIVE + 6, TTK_FANCY_PATH "inactive/lm.bmp"); - init_sprite(INACTIVE + 7, TTK_FANCY_PATH "inactive/lr.bmp"); - init_sprite(INACTIVE + 8, TTK_FANCY_PATH "inactive/button-close.bmp"); - init_sprite(INACTIVE + 9, TTK_FANCY_PATH "inactive/button-maximize.bmp"); + init_sprite(0, TTK_FANCY_PATH "active/ul.png"); + init_sprite(1, TTK_FANCY_PATH "active/um.png"); + init_sprite(2, TTK_FANCY_PATH "active/ur.png"); + init_sprite(3, TTK_FANCY_PATH "active/ml.png"); + init_sprite(4, TTK_FANCY_PATH "active/mr.png"); + init_sprite(5, TTK_FANCY_PATH "active/ll.png"); + init_sprite(6, TTK_FANCY_PATH "active/lm.png"); + init_sprite(7, TTK_FANCY_PATH "active/lr.png"); + init_sprite(8, TTK_FANCY_PATH "active/button-close.png"); + init_sprite(9, TTK_FANCY_PATH "active/button-maximize.png"); + + init_sprite(INACTIVE + 0, TTK_FANCY_PATH "inactive/ul.png"); + init_sprite(INACTIVE + 1, TTK_FANCY_PATH "inactive/um.png"); + init_sprite(INACTIVE + 2, TTK_FANCY_PATH "inactive/ur.png"); + init_sprite(INACTIVE + 3, TTK_FANCY_PATH "inactive/ml.png"); + init_sprite(INACTIVE + 4, TTK_FANCY_PATH "inactive/mr.png"); + init_sprite(INACTIVE + 5, TTK_FANCY_PATH "inactive/ll.png"); + init_sprite(INACTIVE + 6, TTK_FANCY_PATH "inactive/lm.png"); + init_sprite(INACTIVE + 7, TTK_FANCY_PATH "inactive/lr.png"); + init_sprite(INACTIVE + 8, TTK_FANCY_PATH "inactive/button-close.png"); + init_sprite(INACTIVE + 9, TTK_FANCY_PATH "inactive/button-maximize.png"); decor_render_decorations = render_decorations_fancy; decor_check_button_press = check_button_press_fancy; diff --git a/lib/graphics.c b/lib/graphics.c index a4d19a2f..ed1ac95a 100644 --- a/lib/graphics.c +++ b/lib/graphics.c @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -395,7 +396,38 @@ void blur_context_box(gfx_context_t * _src, int radius) { _box_blur_vertical(_src,radius); } +static int (*load_sprite_jpg)(sprite_t *, char *) = NULL; +static int (*load_sprite_png)(sprite_t *, char *) = NULL; + +static void _load_format_libraries() { + void * _lib_jpeg = dlopen("libtoaru_jpeg.so", 0); + if (_lib_jpeg) load_sprite_jpg = dlsym(_lib_jpeg, "load_sprite_jpg"); + void * _lib_png = dlopen("libtoaru_png.so", 0); + if (_lib_png) load_sprite_png = dlsym(_lib_png, "load_sprite_png"); +} + +static char * extension_from_filename(char * filename) { + char * ext = strrchr(filename, '.'); + if (ext && *ext == '.') return ext + 1; + return ""; +} + int load_sprite(sprite_t * sprite, char * filename) { + static int librariesLoaded = 0; + if (!librariesLoaded) { + _load_format_libraries(); + } + + char * ext = extension_from_filename(filename); + + if (!strcmp(ext,"png") || !strcmp(ext,"sdf")) return load_sprite_png(sprite, filename); + if (!strcmp(ext,"jpg") || !strcmp(ext,"jpeg")) return load_sprite_jpg(sprite, filename); + + /* Fall back to bitmap */ + return load_sprite_bmp(sprite, filename); +} + +int load_sprite_bmp(sprite_t * sprite, char * filename) { /* Open the requested binary */ FILE * image = fopen(filename, "r"); @@ -436,6 +468,10 @@ int load_sprite(sprite_t * sprite, char * filename) { #define _BMP_G 0x100 #define _BMP_B 0x10000 + if (bpp == 32) { + sprite->alpha = ALPHA_EMBEDDED; + } + for (y = 0; y < height; ++y) { for (x = 0; x < width; ++x) { if (i > image_size) goto _cleanup_sprite; diff --git a/lib/icon_cache.c b/lib/icon_cache.c index 82286f6d..abfdd2ac 100644 --- a/lib/icon_cache.c +++ b/lib/icon_cache.c @@ -37,21 +37,25 @@ static char * icon_directories_48[] = { NULL }; +static char * prefixes[] = { + "png", + "bmp", + NULL +}; + __attribute__((constructor)) static void _init_caches(void) { icon_cache_16 = hashmap_create(10); { /* Generic fallback icon */ sprite_t * app_icon = malloc(sizeof(sprite_t)); - load_sprite(app_icon, "/usr/share/icons/16/applications-generic.bmp"); - app_icon->alpha = ALPHA_EMBEDDED; + load_sprite(app_icon, "/usr/share/icons/16/applications-generic.png"); hashmap_set(icon_cache_16, "generic", app_icon); } icon_cache_48 = hashmap_create(10); { /* Generic fallback icon */ sprite_t * app_icon = malloc(sizeof(sprite_t)); - load_sprite(app_icon, "/usr/share/icons/48/applications-generic.bmp"); - app_icon->alpha = ALPHA_EMBEDDED; + load_sprite(app_icon, "/usr/share/icons/48/applications-generic.png"); hashmap_set(icon_cache_48, "generic", app_icon); } } @@ -73,14 +77,17 @@ static sprite_t * icon_get_int(const char * name, hashmap_t * icon_cache, char * char path[100]; while (icon_directories[i]) { /* Check each path... */ - sprintf(path, "%s/%s.bmp", icon_directories[i], name); - if (access(path, R_OK) == 0) { - /* And if we find one, cache it */ - icon = malloc(sizeof(sprite_t)); - load_sprite(icon, path); - icon->alpha = ALPHA_EMBEDDED; - hashmap_set(icon_cache, (void*)name, icon); - return icon; + char ** prefix = prefixes; + while (*prefix) { + sprintf(path, "%s/%s.%s", icon_directories[i], name, *prefix); + if (access(path, R_OK) == 0) { + /* And if we find one, cache it */ + icon = malloc(sizeof(sprite_t)); + load_sprite(icon, path); + hashmap_set(icon_cache, (void*)name, icon); + return icon; + } + prefix++; } i++; } diff --git a/lib/inflate.c b/lib/inflate.c new file mode 100644 index 00000000..6d0348e0 --- /dev/null +++ b/lib/inflate.c @@ -0,0 +1,481 @@ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * This file is part of ToaruOS and is released under the terms + * of the NCSA / University of Illinois License - see LICENSE.md + * Copyright (C) 2020 K. Lange + * + * libtoaru_inflate: Methods for decompressing DEFLATE and gzip payloads. + */ +#include +#include + +#ifndef _BOOT_LOADER +#include +#endif + +/** + * Decoded Huffman table + */ +struct huff { + uint16_t counts[16]; /* Number of symbols of each length */ + uint16_t symbols[288]; /* Ordered symbols */ +}; + +/** + * 32K ringbuffer for backwards lookup + */ +struct huff_ring { + size_t pointer; + uint8_t data[32768]; +}; + +/** + * Fixed Huffman code tables, generated later. + */ +struct huff fixed_lengths; +struct huff fixed_dists; + +/** + * Read a little-endian short from the input. + */ +static uint16_t read_16le(struct inflate_context * ctx) { + uint16_t a, b; + a = ctx->get_input(ctx); + b = ctx->get_input(ctx); + return (a << 0) | (b << 8); +} + +/** + * Read a single bit from the source. + * Fills the byte buffer with one byte when it runs out. + */ +static uint8_t read_bit(struct inflate_context * ctx) { + + /* When we run out of bits... */ + if (ctx->buffer_size == 0) { + /* Refill from the next input byte */ + ctx->bit_buffer = ctx->get_input(ctx); + /* And restore bit buffer size to 8 bits */ + ctx->buffer_size = 8; + } + + /* Get the next available bit */ + int out = ctx->bit_buffer & 1; + + /* Shift the bit buffer forward */ + ctx->bit_buffer >>= 1; + + /* There is now one less bit available */ + ctx->buffer_size--; + + return out; +} + +/** + * Read multible bits, in bit order, from the source. + */ +static uint32_t read_bits(struct inflate_context * ctx, unsigned int count) { + uint32_t out = 0; + for (unsigned int bit = 0; bit < count; bit++) { + /* Read one bit at a time, from least to most significant */ + out |= (read_bit(ctx) << bit); + } + return out; +} + +/** + * Build a Huffman table from an array of lengths. + */ +static void build_huffman(uint8_t * lengths, size_t size, struct huff * out) { + + uint16_t offsets[16]; + unsigned int count = 0; + + /* Zero symbol counts */ + for (unsigned int i = 0; i < 16; ++i) out->counts[i] = 0; + + /* Count symbols */ + for (unsigned int i = 0; i < size; ++i) out->counts[lengths[i]]++; + + /* Special case... */ + out->counts[0] = 0; + + /* Figure out offsets */ + for (unsigned int i = 0; i < 16; ++i) { + offsets[i] = count; + count += out->counts[i]; + } + + /* Build symbol ordering */ + for (unsigned int i = 0; i < size; ++i) { + if (lengths[i]) out->symbols[offsets[lengths[i]]++] = i; + } +} + +/** + * Build the fixed Huffman tables + */ +static void build_fixed(void) { + /* From 3.2.6: + * Lit Value Bits Codes + * --------- ---- ----- + * 0 - 143 8 00110000 through + * 10111111 + * 144 - 255 9 110010000 through + * 111111111 + * 256 - 279 7 0000000 through + * 0010111 + * 280 - 287 8 11000000 through + * 11000111 + */ + uint8_t lengths[288]; + for (int i = 0; i < 144; ++i) lengths[i] = 8; + for (int i = 144; i < 256; ++i) lengths[i] = 9; + for (int i = 256; i < 280; ++i) lengths[i] = 7; + for (int i = 280; i < 288; ++i) lengths[i] = 8; + build_huffman(lengths, 288, &fixed_lengths); + + /* Continued from 3.2.6: + * Distance codes 0-31 are represented by (fixed-length) 5-bit + * codes, with possible additional bits as shown in the table + * shown in Paragraph 3.2.5, above. Note that distance codes 30- + * 31 will never actually occur in the compressed data. + */ + for (int i = 0; i < 30; ++i) lengths[i] = 5; + build_huffman(lengths, 30, &fixed_dists); +} + + +/** + * Decode a symbol from the source using a Huffman table. + */ +static int decode(struct inflate_context * ctx, struct huff * huff) { + int count = 0, cur = 0; + for (int i = 1; cur >= 0; i++) { + cur = (cur << 1) | read_bit(ctx); /* Shift */ + count += huff->counts[i]; + cur -= huff->counts[i]; + } + return huff->symbols[count + cur]; +} + +/** + * Emit one byte to the output, maintaining the ringbuffer. + * The ringbuffer ensures we can always look back 32K bytes + * while keeping output streaming. + */ +static void emit(struct inflate_context * ctx, unsigned char byte) { + if (ctx->ring->pointer == 32768) { + ctx->ring->pointer = 0; + } + + ctx->ring->data[ctx->ring->pointer] = byte; + ctx->write_output(ctx, byte); + ctx->ring->pointer++; +} + +/** + * Look backwards in the output ring buffer. + */ +static uint8_t peek(struct inflate_context * ctx, int offset) { + return ctx->ring->data[(ctx->ring->pointer - offset) % 32768]; +} + +/** + * Decompress a block of Huffman-encoded data. + */ +static int inflate(struct inflate_context * ctx, struct huff * huff_len, struct huff * huff_dist) { + + /* These are the extra bits for lengths from the tables in section 3.2.5 + * Extra Extra Extra + * Code Bits Length(s) Code Bits Lengths Code Bits Length(s) + * ---- ---- ------ ---- ---- ------- ---- ---- ------- + * 257 0 3 267 1 15,16 277 4 67-82 + * 258 0 4 268 1 17,18 278 4 83-98 + * 259 0 5 269 2 19-22 279 4 99-114 + * 260 0 6 270 2 23-26 280 4 115-130 + * 261 0 7 271 2 27-30 281 5 131-162 + * 262 0 8 272 2 31-34 282 5 163-194 + * 263 0 9 273 3 35-42 283 5 195-226 + * 264 0 10 274 3 43-50 284 5 227-257 + * 265 1 11,12 275 3 51-58 285 0 258 + * 266 1 13,14 276 3 59-66 + */ + static const uint16_t lens[] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, + 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 + }; + static const uint16_t lext[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, + 4, 5, 5, 5, 5, 0 + }; + + /* Extra bits for distances.... + * Extra Extra Extra + * Code Bits Dist Code Bits Dist Code Bits Distance + * ---- ---- ---- ---- ---- ------ ---- ---- -------- + * 0 0 1 10 4 33-48 20 9 1025-1536 + * 1 0 2 11 4 49-64 21 9 1537-2048 + * 2 0 3 12 5 65-96 22 10 2049-3072 + * 3 0 4 13 5 97-128 23 10 3073-4096 + * 4 1 5,6 14 6 129-192 24 11 4097-6144 + * 5 1 7,8 15 6 193-256 25 11 6145-8192 + * 6 2 9-12 16 7 257-384 26 12 8193-12288 + * 7 2 13-16 17 7 385-512 27 12 12289-16384 + * 8 3 17-24 18 8 513-768 28 13 16385-24576 + * 9 3 25-32 19 8 769-1024 29 13 24577-32768 + */ + static const uint16_t dists[] = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, + 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 + }; + static const uint16_t dext[] = { + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, + 10, 11, 11, 12, 12, 13, 13 + }; + + while (1) { + int symbol = decode(ctx, huff_len); + + if (symbol == 256) { + break; + } + + if (symbol < 256) { + emit(ctx, symbol); + } else if (symbol == 256) { + /* "The literal/length symbol 256 (end of data), ..." */ + break; + } else { + int length, distance, offset; + + symbol -= 257; + length = read_bits(ctx, lext[symbol]) + lens[symbol]; + distance = decode(ctx, huff_dist); + offset = read_bits(ctx, dext[distance]) + dists[distance]; + + for (int i = 0; i < length; ++i) { + uint8_t b = peek(ctx, offset); + emit(ctx, b); + } + } + } + + return 0; +} + +/** + * Decode a dynamic Huffman block. + */ +static void decode_huffman(struct inflate_context * ctx) { + + /* Ordering of code length codes: + * (HCLEN + 4) x 3 bits: code lengths for the code length + * alphabet given just above, in the order: ... + */ + static const uint8_t clens[] = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + }; + + unsigned int literals, distances, clengths; + uint8_t lengths[320] = {0}; + + literals = 257 + read_bits(ctx, 5); /* 5 Bits: HLIT ... 257 */ + distances = 1 + read_bits(ctx, 5); /* 5 Bits: HDIST ... 1 */ + clengths = 4 + read_bits(ctx, 4); /* 4 Bits: HCLEN ... 4 */ + + /* (HCLEN + 4) x 3 bits... */ + for (unsigned int i = 0; i < clengths; ++i) { + lengths[clens[i]] = read_bits(ctx, 3); + } + + struct huff codes; + build_huffman(lengths, 19, &codes); + + /* Decode symbols: + * HLIT + 257 code lengths for the literal/length alphabet... + * HDIST + 1 code lengths for the distance alphabet... + */ + unsigned int count = 0; + while (count < literals + distances) { + int symbol = decode(ctx, &codes); + + if (symbol < 16) { + /* 0 - 15: Represent code lengths of 0-15 */ + lengths[count++] = symbol; + } else if (symbol < 19) { + int rep = 0, length; + if (symbol == 16) { + /* 16: Copy the previous code length 3-6 times */ + rep = lengths[count-1]; + length = read_bits(ctx, 2) + 3; /* The next 2 bits indicate repeat length */ + } else if (symbol == 17) { + /* Repeat a code length of 0 for 3 - 10 times */ + length = read_bits(ctx, 3) + 3; /* 3 bits of length */ + } else if (symbol == 18) { + /* Repeat a code length of 0 for 11 - 138 times */ + length = read_bits(ctx, 7) + 11; /* 7 bits of length */ + } + do { + lengths[count++] = rep; + length--; + } while (length); + } else { + break; + } + } + + /* Build tables from lenghts decoded above */ + struct huff huff_len; + build_huffman(lengths, literals, &huff_len); + struct huff huff_dist; + build_huffman(lengths + literals, distances, &huff_dist); + + inflate(ctx, &huff_len, &huff_dist); +} + +/** + * Decode an uncompressed block. + */ +static int uncompressed(struct inflate_context * ctx) { + /* Reset byte alignment */ + ctx->bit_buffer = 0; + ctx->buffer_size = 0; + + /* "The rest of the block consists of the following information:" + * 0 1 2 3 4... + * +---+---+---+---+================================+ + * | LEN | NLEN |... LEN bytes of literal data...| + * +---+---+---+---+================================+ + */ + uint16_t len = read_16le(ctx); /* "the number of data bytes in the block" */ + uint16_t nlen = read_16le(ctx); /* "the one's complement of LEN */ + + /* Sanity check - does the ones-complement length actually match? */ + if ((nlen & 0xFFFF) != (~len & 0xFFFF)) { + return 1; + } + + /* Emit LEN bytes from the source to the output */ + for (int i = 0; i < len; ++i) { + emit(ctx, ctx->get_input(ctx)); + } + + return 0; +} + +static struct huff_ring data = {0, {0}}; + +/** + * Decompress DEFLATE-compressed data. + */ +int deflate_decompress(struct inflate_context * ctx) { + ctx->bit_buffer = 0; + ctx->buffer_size = 0; + + build_fixed(); + + if (!ctx->ring) { + ctx->ring = &data; + } + + /* read compressed data */ + while (1) { + /* Read bit */ + + int is_final = read_bit(ctx); + int type = read_bits(ctx, 2); + + switch (type) { + case 0x00: /* BTYPE=00 Non-compressed blocks */ + uncompressed(ctx); + break; + case 0x01: /* BYTPE=01 Compressed with fixed Huffman codes */ + inflate(ctx, &fixed_lengths, &fixed_dists); + break; + case 0x02: /* BTYPE=02 Compression with dynamic Huffman codes */ + decode_huffman(ctx); + break; + case 0x03: + return 1; + } + + if (is_final) { + break; + } + } + + return 0; +} + +#define GZIP_FLAG_TEXT (1 << 0) +#define GZIP_FLAG_HCRC (1 << 1) +#define GZIP_FLAG_EXTR (1 << 2) +#define GZIP_FLAG_NAME (1 << 3) +#define GZIP_FLAG_COMM (1 << 4) + +static unsigned int read_32le(struct inflate_context * ctx) { + unsigned int a, b, c, d; + a = ctx->get_input(ctx); + b = ctx->get_input(ctx); + c = ctx->get_input(ctx); + d = ctx->get_input(ctx); + + return (d << 24) | (c << 16) | (b << 8) | (a << 0); +} + +int gzip_decompress(struct inflate_context * ctx) { + + /* Read gzip headers */ + if (ctx->get_input(ctx) != 0x1F) return 1; + if (ctx->get_input(ctx) != 0x8B) return 1; + + unsigned int cm = ctx->get_input(ctx); + if (cm != 8) return 1; + + unsigned int flags = ctx->get_input(ctx); + + /* Read mtime */ + unsigned int mtime = read_32le(ctx); + (void)mtime; + + /* Read extra flags */ + unsigned int xflags = ctx->get_input(ctx); + (void)xflags; + + /* Read and discord OS flag */ + unsigned int os = ctx->get_input(ctx); + (void)os; + + /* Extra bytes */ + if (flags & GZIP_FLAG_EXTR) { + unsigned short size = read_16le(ctx); + for (unsigned int i = 0; i < size; ++i) ctx->get_input(ctx); + } + + if (flags & GZIP_FLAG_NAME) { + unsigned int c; + while ((c = ctx->get_input(ctx)) != 0); + } + + if (flags & GZIP_FLAG_COMM) { + unsigned int c; + while ((c = ctx->get_input(ctx)) != 0); + } + + unsigned int crc16 = 0; + if (flags & GZIP_FLAG_HCRC) { + crc16 = read_16le(ctx); + } + (void)crc16; + + int status = deflate_decompress(ctx); + + /* Read CRC and decompressed size from end of input */ + unsigned int crc32 = read_32le(ctx); + unsigned int dsize = read_32le(ctx); + + (void)crc32; + (void)dsize; + + return status; +} diff --git a/lib/kuroko/_yutani.c b/lib/kuroko/_yutani.c new file mode 100644 index 00000000..48897973 --- /dev/null +++ b/lib/kuroko/_yutani.c @@ -0,0 +1,1417 @@ +/* Kuroko bindings for Yutani */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static KrkInstance * module; +static KrkInstance * yctxInstance = NULL; + +#define S(c) (krk_copyString(c,sizeof(c)-1)) + +static KrkClass * Message; +struct MessageClass { + KrkInstance inst; + yutani_msg_t * msg; +}; + +static KrkClass * Yutani; +struct YutaniClass { + KrkInstance inst; + yutani_t * yctx; +}; + +static KrkClass * GraphicsContext; +struct GraphicsContext { + KrkInstance inst; + gfx_context_t * ctx; + int doubleBuffered; +}; + +static KrkClass * YutaniWindow; +struct WindowClass { + KrkInstance inst; + gfx_context_t * ctx; + int doubleBuffered; + yutani_window_t * window; +}; + +static KrkClass * YutaniSprite; +struct YutaniSprite { + KrkInstance inst; + gfx_context_t * ctx; + int doubleBuffered; + sprite_t sprite; +}; + +static KrkClass * YutaniColor; +struct YutaniColor { + KrkInstance inst; + uint32_t color; +}; + +static KrkClass * YutaniFont; +struct YutaniFont { + KrkInstance inst; + int fontType; + int fontSize; + double fontGamma; + double fontStroke; + uint32_t fontColor; +}; + +static KrkClass * MenuBarClass; +struct MenuBarClass { + KrkInstance inst; + struct menu_bar menuBar; +}; + +static KrkClass * MenuListClass; +struct MenuListClass { + KrkInstance inst; + struct MenuList * menuList; +}; + +static KrkClass * MenuEntryClass; +struct MenuEntryClass { + KrkInstance inst; + struct MenuEntry * menuEntry; +}; + +static KrkClass * MenuEntrySubmenuClass; +static KrkClass * MenuEntrySeparatorClass; + +/** + * Convenience wrapper to make a class and attach it to the module, while + * handling stack push/pop to keep things from being prematurely GC'd. + */ +KrkClass * krk_createClass(KrkInstance * inModule, const char * name, KrkClass * base) { + if (!base) base = vm.baseClasses->objectClass; + KrkString * str_Name = krk_copyString(name, strlen(name)); + krk_push(OBJECT_VAL(str_Name)); + KrkClass * obj_Class = krk_newClass(str_Name, base); + krk_push(OBJECT_VAL(obj_Class)); + krk_attachNamedObject(&inModule->fields, name, (KrkObj *)obj_Class); + krk_pop(); /* obj_Class */ + krk_pop(); /* str_Name */ + + return obj_Class; +} + +#define DO_FIELD(name, body) \ + { krk_push(OBJECT_VAL(S(name))); \ + if (krk_valuesEqual(argv[1], krk_peek(0))) { \ + krk_pop(); \ + body \ + } krk_pop(); } + +#define TO_INT_(name) { return INTEGER_VAL((msg-> name)); } +#define TO_INT(name) { return INTEGER_VAL((me-> name)); } +#define WID() DO_FIELD("wid", TO_INT(wid)) +#define STRUCT(type) type * me = (void*)msg->data + +static void _message_sweep(KrkInstance * self) { + free(((struct MessageClass*)self)->msg); +} + +static KrkValue _message_getattr(int argc, KrkValue argv[], int hasKw) { + assert(argc == 2); + KrkInstance * self = AS_INSTANCE(argv[0]); + + yutani_msg_t * msg = ((struct MessageClass*)self)->msg; + if (!msg) return NONE_VAL(); + + DO_FIELD("magic", TO_INT_(magic)); + DO_FIELD("type", TO_INT_(type)); + DO_FIELD("size", TO_INT_(size)); + + switch (msg->type) { + case YUTANI_MSG_WELCOME: { + STRUCT(struct yutani_msg_welcome); + DO_FIELD("display_width", TO_INT(display_width)); + DO_FIELD("display_height", TO_INT(display_height)); + } break; + case YUTANI_MSG_WINDOW_MOUSE_EVENT: { + STRUCT(struct yutani_msg_window_mouse_event); + WID(); + DO_FIELD("new_x", TO_INT(new_x)); + DO_FIELD("new_y", TO_INT(new_y)); + DO_FIELD("old_x", TO_INT(old_x)); + DO_FIELD("old_y", TO_INT(old_y)); + DO_FIELD("buttons", TO_INT(buttons)); + DO_FIELD("command", TO_INT(command)); + DO_FIELD("modifiers", TO_INT(modifiers)); + } break; + case YUTANI_MSG_WINDOW_FOCUS_CHANGE: { + STRUCT(struct yutani_msg_window_focus_change); + WID(); + DO_FIELD("focused", TO_INT(focused)); + } break; + case YUTANI_MSG_RESIZE_OFFER: { + STRUCT(struct yutani_msg_window_resize); + WID(); + DO_FIELD("width", TO_INT(width)); + DO_FIELD("height", TO_INT(height)); + DO_FIELD("bufid", TO_INT(bufid)); + } break; + case YUTANI_MSG_WINDOW_ADVERTISE: { + STRUCT(struct yutani_msg_window_advertise); + WID(); + DO_FIELD("flags", TO_INT(flags)); + DO_FIELD("size", TO_INT(size)); + DO_FIELD("name", { char * s = me->strings + me->offsets[0]; size_t l = strlen(s); return OBJECT_VAL(krk_copyString(s,l)); }); + DO_FIELD("icon", { char * s = me->strings + me->offsets[1]; size_t l = strlen(s); return OBJECT_VAL(krk_copyString(s,l)); }); + } break; + case YUTANI_MSG_WINDOW_MOVE: { + STRUCT(struct yutani_msg_window_move); + WID(); + DO_FIELD("x", TO_INT(x)); + DO_FIELD("y", TO_INT(y)); + } break; + case YUTANI_MSG_KEY_EVENT: { + STRUCT(struct yutani_msg_key_event); + WID(); + + DO_FIELD("keycode", TO_INT(event.keycode)); + DO_FIELD("modifiers", TO_INT(event.modifiers)); + DO_FIELD("action", TO_INT(event.action)); + DO_FIELD("key", TO_INT(event.key)); + + DO_FIELD("kbd_state", TO_INT(state.kbd_state)); + DO_FIELD("kbd_s_state", TO_INT(state.kbd_s_state)); + DO_FIELD("k_ctrl", TO_INT(state.k_ctrl)); + DO_FIELD("k_shift", TO_INT(state.k_shift)); + DO_FIELD("k_alt", TO_INT(state.k_alt)); + DO_FIELD("k_super", TO_INT(state.k_super)); + DO_FIELD("kl_ctrl", TO_INT(state.kl_ctrl)); + DO_FIELD("kl_shift", TO_INT(state.kl_shift)); + DO_FIELD("kl_alt", TO_INT(state.kl_alt)); + DO_FIELD("kl_super", TO_INT(state.kl_super)); + DO_FIELD("kr_ctrl", TO_INT(state.kr_ctrl)); + DO_FIELD("kr_shift", TO_INT(state.kr_shift)); + DO_FIELD("kr_alt", TO_INT(state.kr_alt)); + DO_FIELD("kr_super", TO_INT(state.kr_super)); + DO_FIELD("kbd_esc_buf", TO_INT(state.kbd_esc_buf)); + } break; + } + + krk_runtimeError(vm.exceptions->attributeError, "no attribute '%s'", AS_CSTRING(argv[1])); + return NONE_VAL(); +} +#undef DO_FIELD +#undef TO_INT_ +#undef GET +#undef TO_INT +#undef WID + +static KrkValue _yutani_repr(int argc, KrkValue argv[], int hasKw) { + struct YutaniClass * self = (struct YutaniClass*)AS_INSTANCE(argv[0]); + char out[500]; + size_t len = sprintf(out, "Yutani(fd=%d,server=%s,display_width=%d,display_height=%d)", + fileno(self->yctx->sock), + self->yctx->server_ident, + (int)self->yctx->display_width, + (int)self->yctx->display_height); + return OBJECT_VAL(krk_copyString(out,len)); +} + +static KrkValue _yutani_init(int argc, KrkValue argv[], int hasKw) { + if (yctxInstance) { + krk_runtimeError(vm.exceptions->valueError, "class 'Yutani' is a singleton and has already been initialized."); + return NONE_VAL(); + } + + KrkInstance * self = AS_INSTANCE(argv[0]); + + /* Connect and let's go. */ + yutani_t * yctx = yutani_init(); + if (!yctx) { + krk_runtimeError(vm.exceptions->ioError, "Failed to connect to compositor."); + return NONE_VAL(); + } + + init_decorations(); + + ((struct YutaniClass*)self)->yctx = yctx; + yctxInstance = self; + krk_attachNamedObject(&module->fields, "_yutani_t", (KrkObj*)self); + + return argv[0]; +} + +#define CHECK_YUTANI() \ + if (argc < 1 || !krk_isInstanceOf(argv[0], Yutani)) \ + return krk_runtimeError(vm.exceptions->typeError, "expected Yutani"); \ + struct YutaniClass * self = (struct YutaniClass*)AS_INSTANCE(argv[0]) + +static KrkValue _yutani_display_width(int argc, KrkValue argv[], int hasKw) { + CHECK_YUTANI(); + return INTEGER_VAL(self->yctx->display_width); +} + +static KrkValue _yutani_display_height(int argc, KrkValue argv[], int hasKw) { + CHECK_YUTANI(); + return INTEGER_VAL(self->yctx->display_height); +} + +static KrkValue _yutani_poll(int argc, KrkValue argv[], int hasKw) { + CHECK_YUTANI(); + + int sync = (argc > 1 && IS_BOOLEAN(argv[1])) ? AS_BOOLEAN(argv[1]) : 1; + yutani_msg_t * result; + if (sync) { + result = yutani_poll(self->yctx); + } else { + result = yutani_poll_async(self->yctx); + } + + if (!result) return NONE_VAL(); + + KrkInstance * out = krk_newInstance(Message); + krk_push(OBJECT_VAL(out)); + ((struct MessageClass*)out)->msg = result; + krk_attachNamedValue(&out->fields, "type", INTEGER_VAL(result->type)); + + return krk_pop(); +} + +static KrkValue _yutani_wait_for(int argc, KrkValue argv[], int hasKw) { + CHECK_YUTANI(); + if (argc != 2 || !IS_INTEGER(argv[1])) { krk_runtimeError(vm.exceptions->argumentError, "expected int for msgtype"); return NONE_VAL(); } + yutani_msg_t * result = yutani_wait_for(self->yctx, AS_INTEGER(argv[1])); + KrkInstance * out = krk_newInstance(Message); + krk_push(OBJECT_VAL(out)); + ((struct MessageClass*)out)->msg = result; + krk_attachNamedValue(&out->fields, "type", INTEGER_VAL(result->type)); + + return krk_pop(); +} + +static KrkValue _yutani_subscribe(int argc, KrkValue argv[], int hasKw) { + CHECK_YUTANI(); + yutani_subscribe_windows(self->yctx); + return NONE_VAL(); +} + +static KrkValue _yutani_unsubscribe(int argc, KrkValue argv[], int hasKw) { + CHECK_YUTANI(); + yutani_unsubscribe_windows(self->yctx); + return NONE_VAL(); +} + +static KrkValue _yutani_query_windows(int argc, KrkValue argv[], int hasKw) { + CHECK_YUTANI(); + yutani_query_windows(self->yctx); + return NONE_VAL(); +} + +static KrkValue _yutani_fileno(int argc, KrkValue argv[], int hasKw) { + CHECK_YUTANI(); + return INTEGER_VAL(fileno(self->yctx->sock)); +} + +static KrkValue _yutani_query(int argc, KrkValue argv[], int hasKw) { + CHECK_YUTANI(); + return INTEGER_VAL(yutani_query(self->yctx)); +} + +static KrkValue _yutani_menu_process_event(int argc, KrkValue argv[], int hasKw) { + CHECK_YUTANI(); + if (argc < 2 || !krk_isInstanceOf(argv[1],Message)) + return krk_runtimeError(vm.exceptions->typeError, "expected Message"); + struct MessageClass* msg = (struct MessageClass*)AS_INSTANCE(argv[1]); + + return INTEGER_VAL(menu_process_event(self->yctx, msg->msg)); +} + +#define GET_ARG(p,name,type) do { \ + if (hasKw && krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S(#name)), &name)) { \ + if (!krk_isInstanceOf(name,type)) \ + return krk_runtimeError(vm.exceptions->typeError, #name " argument should be " #type ", not '%s'", krk_typeName(name)); \ + } else if (argc > p) { \ + name = argv[p]; \ + if (!krk_isInstanceOf(name,type)) \ + return krk_runtimeError(vm.exceptions->typeError, #name " argument should be " #type ", not '%s'", krk_typeName(name)); \ + } \ +} while (0) + +#define GFX_PROPERTY(name) \ +static KrkValue _gfx_ ## name (int argc, KrkValue argv[], int hasKw) { \ + if (argc != 1 || !krk_isInstanceOf(argv[0], GraphicsContext)) \ + return krk_runtimeError(vm.exceptions->typeError, "Expected GraphicsContext"); \ + struct GraphicsContext * self = (struct GraphicsContext *)AS_INSTANCE(argv[0]); \ + return INTEGER_VAL(self->ctx-> name); \ +} + +GFX_PROPERTY(width); +GFX_PROPERTY(height); + +#define CHECK_GFX() \ + if (argc < 1 || !krk_isInstanceOf(argv[0], GraphicsContext)) \ + return krk_runtimeError(vm.exceptions->typeError, "expected GraphicsContext"); \ + struct GraphicsContext * self = (struct GraphicsContext*)AS_INSTANCE(argv[0]) + +static KrkValue _gfx_fill(int argc, KrkValue argv[], int hasKw) { + CHECK_GFX(); + if (argc < 2 || !krk_isInstanceOf(argv[1], YutaniColor)) + return krk_runtimeError(vm.exceptions->typeError, "fill() takes one color() argument"); + struct YutaniColor * color = (struct YutaniColor*)AS_INSTANCE(argv[1]); + draw_fill(self->ctx, color->color); + return NONE_VAL(); +} + +static KrkValue _gfx_flip(int argc, KrkValue argv[], int hasKw) { + CHECK_GFX(); + if (self->doubleBuffered) { + flip(self->ctx); + } + return NONE_VAL(); +} + +static KrkValue _gfx_blur(int argc, KrkValue argv[], int hasKw) { + CHECK_GFX(); + int radius = 2; + if (argc > 1 && IS_INTEGER(argv[1])) radius = AS_INTEGER(argv[1]); + else if (argc > 1) return krk_runtimeError(vm.exceptions->typeError, "expected int"); + blur_context_box(self->ctx, radius); + return NONE_VAL(); +} + +static KrkValue _gfx_line(int argc, KrkValue argv[], int hasKw) { + CHECK_GFX(); + if (argc < 6 || + !IS_INTEGER(argv[1]) || + !IS_INTEGER(argv[2]) || + !IS_INTEGER(argv[3]) || + !IS_INTEGER(argv[4]) || + !krk_isInstanceOf(argv[5], YutaniColor)) { + return krk_runtimeError(vm.exceptions->typeError, "line() expects 4 ints and a color"); + } + + int32_t x0 = AS_INTEGER(argv[1]); + int32_t x1 = AS_INTEGER(argv[2]); + int32_t y0 = AS_INTEGER(argv[3]); + int32_t y1 = AS_INTEGER(argv[4]); + struct YutaniColor * color = (struct YutaniColor*)AS_INSTANCE(argv[5]); + + if (argc > 6) { + if (IS_INTEGER(argv[6])) { + draw_line_thick(self->ctx,x0,x1,y0,y1,color->color,AS_INTEGER(argv[6])); + } else if (IS_FLOATING(argv[6])) { + draw_line_aa(self->ctx,x0,x1,y0,y1,color->color,AS_FLOATING(argv[6])); + } else { + return krk_runtimeError(vm.exceptions->typeError, "thickness must be int or float, not '%s'", krk_typeName(argv[6])); + } + } else { + draw_line(self->ctx,x0,x1,y0,y1,color->color); + } + + return NONE_VAL(); +} + +static KrkValue _gfx_rect(int argc, KrkValue argv[], int hasKw) { + CHECK_GFX(); + + if (argc != 6 || + !IS_INTEGER(argv[1]) || + !IS_INTEGER(argv[2]) || + !IS_INTEGER(argv[3]) || + !IS_INTEGER(argv[4]) || + !krk_isInstanceOf(argv[5], YutaniColor)) { + return krk_runtimeError(vm.exceptions->typeError, "rect() expects 4 ints and a color"); + } + + int32_t x = AS_INTEGER(argv[1]); + int32_t y = AS_INTEGER(argv[2]); + uint16_t width = AS_INTEGER(argv[3]); + uint16_t height = AS_INTEGER(argv[4]); + struct YutaniColor * color = (struct YutaniColor*)AS_INSTANCE(argv[5]); + + KrkValue solid = BOOLEAN_VAL(0), radius = NONE_VAL(); + if (hasKw) { + krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("solid")), &solid); + krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("radius")), &radius); + } + + if (!IS_BOOLEAN(solid)) + return krk_runtimeError(vm.exceptions->typeError, "solid must be bool"); + if (!IS_NONE(radius) && !IS_INTEGER(radius)) + return krk_runtimeError(vm.exceptions->typeError, "radius must be int"); + if (!IS_NONE(radius) && AS_BOOLEAN(solid)) + return krk_runtimeError(vm.exceptions->typeError, "radius and solid can not be used together"); + + if (AS_BOOLEAN(solid)) { + draw_rectangle_solid(self->ctx, x, y, width, height, color->color); + } else if (IS_INTEGER(radius)) { + draw_rounded_rectangle(self->ctx, x, y, width, height, AS_INTEGER(radius), color->color); + } else { + draw_rectangle(self->ctx, x, y, width, height, color->color); + } + + return NONE_VAL(); +} + +static KrkValue _gfx_draw_sprite(int argc, KrkValue argv[], int hasKw) { + CHECK_GFX(); + + if (argc < 2 || !krk_isInstanceOf(argv[1], YutaniSprite)) + return krk_runtimeError(vm.exceptions->typeError, "expected Sprite"); + + if (argc < 4 || !IS_INTEGER(argv[2]) || !IS_INTEGER(argv[3])) + return krk_runtimeError(vm.exceptions->typeError, "expected integer coordinate pair"); + + /* Potential kwargs: rotation:float, alpha:float, scale:(int,int)... */ + KrkValue rotation = NONE_VAL(), alpha = NONE_VAL(), scale=NONE_VAL(), color=NONE_VAL(); + if (hasKw) { + krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("alpha")), &alpha); + krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("rotation")), &rotation); + krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("scale")), &scale); + krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("color")), &color); + } + + if (!IS_NONE(alpha) && !IS_FLOATING(alpha)) + return krk_runtimeError(vm.exceptions->typeError, "alpha must be float"); + if (!IS_NONE(rotation) && !IS_FLOATING(rotation)) + return krk_runtimeError(vm.exceptions->typeError, "rotation must be float"); + if (!IS_NONE(color) && !krk_isInstanceOf(color,YutaniColor)) + return krk_runtimeError(vm.exceptions->typeError, "color must be color"); + if (!IS_NONE(scale) && (!IS_TUPLE(scale) || AS_TUPLE(scale)->values.count != 2 || + !IS_INTEGER(AS_TUPLE(scale)->values.values[0]) || + !IS_INTEGER(AS_TUPLE(scale)->values.values[1]))) + return krk_runtimeError(vm.exceptions->typeError, "scale must be 2-tuple of ints"); + if (!IS_NONE(rotation) + !IS_NONE(scale) + !IS_NONE(color) > 1) + return krk_runtimeError(vm.exceptions->typeError, "can not combine rotation / scale / color"); + + if ((!IS_NONE(rotation) || !IS_NONE(color)) && IS_NONE(alpha)) + alpha = FLOATING_VAL(1.0); + + struct YutaniSprite * sprite = (struct YutaniSprite*)AS_INSTANCE(argv[1]); + int32_t x = AS_INTEGER(argv[2]); + int32_t y = AS_INTEGER(argv[3]); + + if (!IS_NONE(scale)) { + int32_t width = AS_INTEGER(AS_TUPLE(scale)->values.values[0]); + int32_t height = AS_INTEGER(AS_TUPLE(scale)->values.values[1]); + if (IS_NONE(alpha)) { + draw_sprite_scaled(self->ctx, &sprite->sprite, x, y, width, height); + } else { + draw_sprite_scaled_alpha(self->ctx, &sprite->sprite, x, y, width, height, AS_FLOATING(alpha)); + } + } else if (IS_NONE(alpha)) { + draw_sprite(self->ctx, &sprite->sprite, x, y); + } else if (!IS_NONE(color)) { + draw_sprite_alpha_paint(self->ctx, &sprite->sprite, x, y, AS_FLOATING(alpha), ((struct YutaniColor*)AS_INSTANCE(color))->color); + } else if (!IS_NONE(rotation)) { + draw_sprite_rotate(self->ctx, &sprite->sprite, x, y, AS_FLOATING(rotation), AS_FLOATING(alpha)); + } else { + draw_sprite_alpha(self->ctx, &sprite->sprite, x, y, AS_FLOATING(alpha)); + } + + return NONE_VAL(); +} + +static void _sprite_sweep(KrkInstance * self) { + struct YutaniSprite * sprite = (struct YutaniSprite*)self; + + if (sprite->sprite.masks) free(sprite->sprite.masks); + if (sprite->sprite.bitmap) free(sprite->sprite.bitmap); + if (sprite->ctx) free(sprite->ctx); +} + +static KrkValue _sprite_repr(int argc, KrkValue argv[], int hasKw) { + struct YutaniSprite * self = (struct YutaniSprite *)AS_INSTANCE(argv[0]); + + KrkValue file; + krk_tableGet(&self->inst.fields, OBJECT_VAL(S("file")), &file); + + char out[500]; + size_t len = sprintf(out, "Sprite('%s',width=%d,height=%d)", + !IS_STRING(file) ? "" : AS_CSTRING(file), + (int)self->sprite.width, + (int)self->sprite.height); + return OBJECT_VAL(krk_copyString(out,len)); +} + +static KrkValue _sprite_init(int argc, KrkValue argv[], int hasKw) { + if (argc < 1 || !krk_isInstanceOf(argv[0], YutaniSprite)) + return krk_runtimeError(vm.exceptions->typeError, "expected sprite"); + + if (argc < 2 || !IS_STRING(argv[1])) + return krk_runtimeError(vm.exceptions->typeError, "Sprite() takes one str argument"); + + struct YutaniSprite * self = (struct YutaniSprite*)AS_INSTANCE(argv[0]); + + int result = load_sprite(&self->sprite, AS_CSTRING(argv[1])); + if (result) { + return krk_runtimeError(vm.exceptions->ioError, "Sprite() could not be initialized"); + } + + self->ctx = init_graphics_sprite(&self->sprite); + krk_attachNamedValue(&self->inst.fields, "file", argv[1]); + + return argv[0]; +} + +#define CHECK_WINDOW() \ + if (argc < 1 || !krk_isInstanceOf(argv[0], YutaniWindow)) \ + return krk_runtimeError(vm.exceptions->typeError, "expected Window"); \ + struct WindowClass * self = (struct WindowClass*)AS_INSTANCE(argv[0]); \ + if (!self->window) return krk_runtimeError(vm.exceptions->valueError, "Window is closed") + +static KrkValue _window_repr(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + KrkValue title; + krk_tableGet(&self->inst.fields, OBJECT_VAL(S("title")), &title); + char out[500]; + size_t len = sprintf(out, "Window(wid=%d,title=%s,width=%d,height=%d)", + self->window->wid, + IS_NONE(title) ? "" : AS_CSTRING(title), + (int)self->window->width, + (int)self->window->height); + return OBJECT_VAL(krk_copyString(out,len)); +} + +static KrkValue _window_init(int argc, KrkValue argv[], int hasKw) { + if (!yctxInstance) return krk_runtimeError(vm.exceptions->valueError, "Compositor is not initialized"); + if (argc < 1 || !krk_isInstanceOf(argv[0], YutaniWindow)) + return krk_runtimeError(vm.exceptions->typeError, "Failed to initialize window"); + + if (argc < 3 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2])) + return krk_runtimeError(vm.exceptions->argumentError, "Expected at least two (integer) arguments (width, height)"); + + KrkInstance * _self = AS_INSTANCE(argv[0]); + struct WindowClass * self = (struct WindowClass*)_self; + krk_integer_type width = AS_INTEGER(argv[1]); + krk_integer_type height = AS_INTEGER(argv[2]); + + KrkValue flags = INTEGER_VAL(0), title = NONE_VAL(), icon = NONE_VAL(), doublebuffer = BOOLEAN_VAL(0); + GET_ARG(3, flags, vm.baseClasses->intClass); + GET_ARG(4, title, vm.baseClasses->strClass); + GET_ARG(5, icon, vm.baseClasses->strClass); + GET_ARG(6, doublebuffer, vm.baseClasses->boolClass); + + self->window = yutani_window_create_flags(((struct YutaniClass*)yctxInstance)->yctx, + width, height, AS_INTEGER(flags)); + + self->doubleBuffered = AS_BOOLEAN(doublebuffer); + + if (self->doubleBuffered) { + self->ctx = init_graphics_yutani_double_buffer(self->window); + } else { + self->ctx = init_graphics_yutani(self->window); + } + + if (!IS_NONE(title)) { + if (!IS_NONE(icon)) { + yutani_window_advertise_icon(((struct YutaniClass*)yctxInstance)->yctx, self->window, AS_CSTRING(title), AS_CSTRING(icon)); + } else { + yutani_window_advertise(((struct YutaniClass*)yctxInstance)->yctx, self->window, AS_CSTRING(title)); + } + } + + krk_attachNamedValue(&_self->fields, "title", title); + krk_attachNamedValue(&_self->fields, "icon", icon); + krk_attachNamedValue(&_self->fields, "closed", BOOLEAN_VAL(0)); + + return argv[0]; +} + +static KrkValue _window_flip(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + if (self->doubleBuffered) { + flip(self->ctx); + } + yutani_flip(((struct YutaniClass*)yctxInstance)->yctx, self->window); + return NONE_VAL(); +} + +static KrkValue _window_move(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + if (argc < 3 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2])) + return krk_runtimeError(vm.exceptions->typeError, "expected two integer arguments"); + yutani_window_move(((struct YutaniClass*)yctxInstance)->yctx, self->window, AS_INTEGER(argv[1]), AS_INTEGER(argv[2])); + return NONE_VAL(); +} + +static KrkValue _window_set_focused(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + if (argc < 2 || !IS_INTEGER(argv[1])) + return krk_runtimeError(vm.exceptions->typeError, "expected integer argument"); + self->window->focused = AS_INTEGER(argv[1]); + return NONE_VAL(); +} + +static KrkValue _window_close(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + yutani_close(((struct YutaniClass*)yctxInstance)->yctx, self->window); + self->window = NULL; + release_graphics_yutani(self->ctx); + self->ctx = NULL; + return NONE_VAL(); +} + +static KrkValue _window_set_stack(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + if (argc < 2 || !IS_INTEGER(argv[1])) return krk_runtimeError(vm.exceptions->typeError, "expected int for z-order"); + int z = AS_INTEGER(argv[1]); + yutani_set_stack(((struct YutaniClass*)yctxInstance)->yctx, self->window, z); + return NONE_VAL(); +} + +static KrkValue _window_update_shape(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + if (argc < 2 || !IS_INTEGER(argv[1])) return krk_runtimeError(vm.exceptions->typeError, "expected int for shape specifier"); + int set_shape = AS_INTEGER(argv[1]); + yutani_window_update_shape(((struct YutaniClass*)yctxInstance)->yctx, self->window, set_shape); + return NONE_VAL(); +} + +static KrkValue _window_warp_mouse(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + if (argc < 3 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2])) + return krk_runtimeError(vm.exceptions->typeError, "expected two int values for x, y"); + int32_t x = AS_INTEGER(argv[1]); + int32_t y = AS_INTEGER(argv[2]); + yutani_window_warp_mouse(((struct YutaniClass*)yctxInstance)->yctx, self->window, x, y); + return NONE_VAL(); +} + +static KrkValue _window_show_mouse(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + if (argc < 2 || !IS_INTEGER(argv[1])) return krk_runtimeError(vm.exceptions->typeError, "expected int for show_mouse"); + int show_mouse = AS_INTEGER(argv[1]); + yutani_window_show_mouse(((struct YutaniClass*)yctxInstance)->yctx, self->window, show_mouse); + return NONE_VAL(); +} + +static KrkValue _window_resize_start(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + if (argc < 2 || !IS_INTEGER(argv[1])) return krk_runtimeError(vm.exceptions->typeError, "expected int for direction"); + yutani_scale_direction_t direction = AS_INTEGER(argv[1]); + yutani_window_resize_start(((struct YutaniClass*)yctxInstance)->yctx, self->window, direction); + return NONE_VAL(); +} + +static KrkValue _window_resize(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + if (argc < 3 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2])) + return krk_runtimeError(vm.exceptions->typeError, "expected two int values for width, height"); + uint32_t width = AS_INTEGER(argv[1]); + uint32_t height = AS_INTEGER(argv[2]); + yutani_window_resize(((struct YutaniClass*)yctxInstance)->yctx, self->window, width, height); + return NONE_VAL(); +} + +static KrkValue _window_resize_offer(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + if (argc < 3 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2])) + return krk_runtimeError(vm.exceptions->typeError, "expected two int values for width, height"); + uint32_t width = AS_INTEGER(argv[1]); + uint32_t height = AS_INTEGER(argv[2]); + yutani_window_resize_offer(((struct YutaniClass*)yctxInstance)->yctx, self->window, width, height); + return NONE_VAL(); +} + +static KrkValue _window_resize_accept(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + if (argc < 3 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2])) + return krk_runtimeError(vm.exceptions->typeError, "expected two int values for width, height"); + uint32_t width = AS_INTEGER(argv[1]); + uint32_t height = AS_INTEGER(argv[2]); + yutani_window_resize_accept(((struct YutaniClass*)yctxInstance)->yctx, self->window, width, height); + return NONE_VAL(); +} + +static KrkValue _window_resize_done(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + yutani_window_resize_done(((struct YutaniClass*)yctxInstance)->yctx, self->window); + return NONE_VAL(); +} + +static KrkValue _window_advertise(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + if (argc < 2 || !IS_STRING(argv[1])) + return krk_runtimeError(vm.exceptions->typeError, "expected string for title"); + if (argc > 2 && !IS_STRING(argv[2])) + return krk_runtimeError(vm.exceptions->typeError, "expected string for icon"); + + if (argc > 2) { + yutani_window_advertise_icon(((struct YutaniClass*)yctxInstance)->yctx, self->window, AS_CSTRING(argv[1]), AS_CSTRING(argv[2])); + } else { + yutani_window_advertise(((struct YutaniClass*)yctxInstance)->yctx, self->window, AS_CSTRING(argv[1])); + } + return NONE_VAL(); +} + +static KrkValue _window_special_request(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + if (argc < 2 || !IS_INTEGER(argv[1])) return krk_runtimeError(vm.exceptions->typeError, "expected int for request"); + uint32_t request = AS_INTEGER(argv[1]); + yutani_special_request(((struct YutaniClass*)yctxInstance)->yctx, self->window, request); + return NONE_VAL(); +} + +static KrkValue _window_reinit(int argc, KrkValue argv[], int hasKw) { + CHECK_WINDOW(); + reinit_graphics_yutani(self->ctx, self->window); + return NONE_VAL(); +} + +#define WINDOW_PROPERTY(name) \ +static KrkValue _window_ ## name (int argc, KrkValue argv[], int hasKw) { \ + if (argc != 1 || !krk_isInstanceOf(argv[0], YutaniWindow)) \ + return krk_runtimeError(vm.exceptions->typeError, "Expected Window"); \ + struct WindowClass * self = (struct WindowClass*)AS_INSTANCE(argv[0]); \ + return INTEGER_VAL(self->window-> name); \ +} + +WINDOW_PROPERTY(wid); +WINDOW_PROPERTY(x); +WINDOW_PROPERTY(y); +WINDOW_PROPERTY(focused); + +static KrkValue _decor_get_bounds(int argc, KrkValue argv[], int hasKw) { + if (argc > 0 && !krk_isInstanceOf(argv[0], YutaniWindow)) + return krk_runtimeError(vm.exceptions->typeError, "expected window"); + struct decor_bounds bounds; + + decor_get_bounds((argc > 0) ? ((struct WindowClass*)AS_INSTANCE(argv[0]))->window : NULL, + &bounds); + + KrkValue result = krk_dict_of(0, NULL, 0); + krk_push(result); + +#define SET(val) krk_attachNamedValue(AS_DICT(result), #val, INTEGER_VAL(bounds. val)); + + SET(top_height); + SET(bottom_height); + SET(left_width); + SET(right_width); + SET(width); + SET(height); + + return krk_pop(); +} + +static KrkValue _decor_handle_event(int argc, KrkValue argv[], int hasKw) { + if (argc < 1 || !krk_isInstanceOf(argv[0], Message)) + return krk_runtimeError(vm.exceptions->typeError, "expected message"); + return INTEGER_VAL(decor_handle_event(((struct YutaniClass*)yctxInstance)->yctx, ((struct MessageClass*)AS_INSTANCE(argv[0]))->msg)); +} + +static KrkValue _decor_render(int argc, KrkValue argv[], int hasKw) { + if (argc < 1 || !krk_isInstanceOf(argv[0], YutaniWindow)) + return krk_runtimeError(vm.exceptions->typeError, "expected window"); + char * title = (argc > 1 && IS_STRING(argv[1])) ? AS_CSTRING(argv[1]) : NULL; + if (title == NULL) { + KrkValue winTitle; + if (!krk_tableGet(&AS_INSTANCE(argv[0])->fields, OBJECT_VAL(S("title")), &winTitle) || !IS_STRING(winTitle)) { + title = ""; + } else { + title = AS_CSTRING(winTitle); + } + } + render_decorations(((struct WindowClass*)AS_INSTANCE(argv[0]))->window, + ((struct WindowClass*)AS_INSTANCE(argv[0]))->ctx, title); + return NONE_VAL(); +} + +static KrkValue _decor_show_default_menu(int argc, KrkValue argv[], int hasKw) { + if (argc < 1 || !krk_isInstanceOf(argv[0], YutaniWindow)) + return krk_runtimeError(vm.exceptions->typeError, "show_default_menu() expects Window"); + if (argc < 3 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2])) + return krk_runtimeError(vm.exceptions->typeError, "show_default_menu() expects int coordinate pair"); + + struct WindowClass * window = (void*)AS_INSTANCE(argv[0]); + int32_t x = AS_INTEGER(argv[1]); + int32_t y = AS_INTEGER(argv[2]); + + decor_show_default_menu(window->window, x, y); + return NONE_VAL(); +} + +static KrkValue _yutani_color_init(int argc, KrkValue argv[], int hasKw) { + if (argc < 4 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2]) || !IS_INTEGER(argv[3]) || + (argc > 5) || (argc == 5 && !IS_INTEGER(argv[4]))) return krk_runtimeError(vm.exceptions->typeError, "color() expects three or four integer arguments"); + if (!krk_isInstanceOf(argv[0], YutaniColor)) return krk_runtimeError(vm.exceptions->typeError, "expected color [__init__], not '%s'", krk_typeName(argv[0])); + struct YutaniColor * self = (struct YutaniColor*)AS_INSTANCE(argv[0]); + if (argc == 5) { + self->color = rgba(AS_INTEGER(argv[1]),AS_INTEGER(argv[2]),AS_INTEGER(argv[3]),AS_INTEGER(argv[4])); + } else { + self->color = rgb(AS_INTEGER(argv[1]),AS_INTEGER(argv[2]),AS_INTEGER(argv[3])); + } + return argv[0]; +} + +static KrkValue _yutani_color_repr(int argc, KrkValue argv[], int hasKw) { + if (argc != 1 || !krk_isInstanceOf(argv[0], YutaniColor)) return krk_runtimeError(vm.exceptions->typeError, "expected color [__repr__], not '%s'", krk_typeName(argv[0])); + struct YutaniColor * self = (struct YutaniColor*)AS_INSTANCE(argv[0]); + char tmp[30]; + if (_ALP(self->color) != 255) { + sprintf(tmp, "color<#%02x%02x%02x%02x>", (int)_RED(self->color), (int)_GRE(self->color), (int)_BLU(self->color), (int)_ALP(self->color)); + } else { + sprintf(tmp, "color<#%02x%02x%02x>", (int)_RED(self->color), (int)_GRE(self->color), (int)_BLU(self->color)); + } + return OBJECT_VAL(krk_copyString(tmp,strlen(tmp))); +} + +static KrkValue _yutani_color_str(int argc, KrkValue argv[], int hasKw) { + if (argc != 1 || !krk_isInstanceOf(argv[0], YutaniColor)) return krk_runtimeError(vm.exceptions->typeError, "expected color [__str__], not '%s'", krk_typeName(argv[0])); + struct YutaniColor * self = (struct YutaniColor*)AS_INSTANCE(argv[0]); + char tmp[30]; + if (_ALP(self->color) != 255) { + sprintf(tmp, "#%02x%02x%02x%02x", (int)_RED(self->color), (int)_GRE(self->color), (int)_BLU(self->color), (int)_ALP(self->color)); + } else { + sprintf(tmp, "#%02x%02x%02x", (int)_RED(self->color), (int)_GRE(self->color), (int)_BLU(self->color)); + } + return OBJECT_VAL(krk_copyString(tmp,strlen(tmp))); +} + +#define CHECK_FONT() \ + if (argc < 1 || !krk_isInstanceOf(argv[0], YutaniFont)) \ + return krk_runtimeError(vm.exceptions->typeError, "expected Font"); \ + struct YutaniFont * self = (struct YutaniFont*)AS_INSTANCE(argv[0]) + +static KrkValue _font_init(int argc, KrkValue argv[], int hasKw) { + CHECK_FONT(); + + if (argc < 2 || !IS_INTEGER(argv[1])) + return krk_runtimeError(vm.exceptions->typeError, "expected int for font type"); + if (argc < 3 || !IS_INTEGER(argv[2])) + return krk_runtimeError(vm.exceptions->typeError, "expected int for font size"); + + KrkValue fontGamma = FLOATING_VAL(1.7); + KrkValue fontStroke = FLOATING_VAL(0.75); + KrkValue fontColor = NONE_VAL(); + if (hasKw) { + krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("gamma")), &fontGamma); + krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("stroke")), &fontStroke); + krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("color")), &fontColor); + if (!IS_FLOATING(fontGamma)) return krk_runtimeError(vm.exceptions->typeError, "expected float for gamma"); + if (!IS_FLOATING(fontStroke)) return krk_runtimeError(vm.exceptions->typeError, "expected float for stroke"); + if (!krk_isInstanceOf(fontColor, YutaniColor)) return krk_runtimeError(vm.exceptions->typeError, "expected color"); + } + + self->fontType = AS_INTEGER(argv[1]); + self->fontSize = AS_INTEGER(argv[2]); + self->fontGamma = AS_FLOATING(fontGamma); + self->fontStroke = AS_FLOATING(fontStroke); + self->fontColor = IS_NONE(fontColor) ? rgb(0,0,0) : ((struct YutaniColor*)AS_INSTANCE(fontColor))->color; + + return argv[0]; +} + +static KrkValue _font_size(int argc, KrkValue argv[], int hasKw) { + CHECK_FONT(); + return INTEGER_VAL(self->fontSize); +} + +static KrkValue _font_draw_string(int argc, KrkValue argv[], int hasKw) { + CHECK_FONT(); + if (argc < 2 || !krk_isInstanceOf(argv[1], GraphicsContext)) + return krk_runtimeError(vm.exceptions->typeError, "expected GraphicsContext"); + if (argc < 3 || !IS_STRING(argv[2])) + return krk_runtimeError(vm.exceptions->typeError, "expected str"); + if (argc < 5 || !IS_INTEGER(argv[3]) || !IS_INTEGER(argv[4])) + return krk_runtimeError(vm.exceptions->typeError, "expected int coordinate pair"); + + gfx_context_t * ctx = ((struct GraphicsContext*)AS_INSTANCE(argv[1]))->ctx; + const char * str = AS_CSTRING(argv[2]); + int32_t x = AS_INTEGER(argv[3]); + int32_t y = AS_INTEGER(argv[4]); + + return INTEGER_VAL(draw_sdf_string_stroke(ctx,x,y,str,self->fontSize,self->fontColor,self->fontType,self->fontGamma,self->fontStroke)); +} + +static KrkValue _font_width(int argc, KrkValue argv[], int hasKw) { + CHECK_FONT(); + if (argc < 2 || !IS_STRING(argv[1])) + return krk_runtimeError(vm.exceptions->typeError, "expected str"); + + const char * str = AS_CSTRING(argv[1]); + return INTEGER_VAL(draw_sdf_string_width(str, self->fontSize, self->fontType)); +} + +static void _MenuBar_gcsweep(KrkInstance * _self) { + struct MenuBarClass * self = (struct MenuBarClass*)_self; + if (self->menuBar.entries) { + for (size_t i = 0; self->menuBar.entries[i].title; ++i) { + free(self->menuBar.entries[i].title); + free(self->menuBar.entries[i].action); + } + free(self->menuBar.entries); + } +} + +static void _menubar_callback(struct menu_bar * _self) { + struct MenuBarClass * self = _self->_private; + KrkValue callback; + if (krk_tableGet(&self->inst.fields, OBJECT_VAL(S("callback")), &callback)) { + krk_push(OBJECT_VAL(self)); + krk_callSimple(callback, 1, 0); + } +} + +static KrkValue _MenuBar_init(int argc, KrkValue argv[], int hasKw) { + if (argc < 1 || !krk_isInstanceOf(argv[0], MenuBarClass)) + return krk_runtimeError(vm.exceptions->typeError, "expected MenuBar"); + if (argc < 2 || !IS_TUPLE(argv[1])) + return krk_runtimeError(vm.exceptions->typeError, "expected tuple of tuples"); + + struct MenuBarClass * self = (struct MenuBarClass*)AS_INSTANCE(argv[0]); + self->menuBar.entries = malloc(sizeof(struct menu_bar_entries) * (AS_TUPLE(argv[1])->values.count + 1)); + for (size_t i = 0; i < AS_TUPLE(argv[1])->values.count; ++i) { + if (!IS_TUPLE(AS_TUPLE(argv[1])->values.values[i]) || + AS_TUPLE(AS_TUPLE(argv[1])->values.values[i])->values.count != 2 || + !IS_STRING(AS_TUPLE(AS_TUPLE(argv[1])->values.values[i])->values.values[0]) || + !IS_STRING(AS_TUPLE(AS_TUPLE(argv[1])->values.values[i])->values.values[1])) { + return krk_runtimeError(vm.exceptions->typeError, "invalid menu bar entry: expected (str,str) but %d is '%s'", + (int)i, krk_typeName(AS_TUPLE(argv[1])->values.values[i])); + } + + KrkString * title = AS_STRING(AS_TUPLE(AS_TUPLE(argv[1])->values.values[i])->values.values[0]); + KrkString * action = AS_STRING(AS_TUPLE(AS_TUPLE(argv[1])->values.values[i])->values.values[1]); + + self->menuBar.entries[i].title = strdup(title->chars); + self->menuBar.entries[i].action = strdup(action->chars); + } + self->menuBar.entries[AS_TUPLE(argv[1])->values.count].title = NULL; + self->menuBar.entries[AS_TUPLE(argv[1])->values.count].action = NULL; + + self->menuBar.set = menu_set_create(); + self->menuBar._private = self; + self->menuBar.redraw_callback = _menubar_callback; + + krk_attachNamedValue(&self->inst.fields, "entries", argv[1]); + + /* Give ourselves a dict to track the same information */ + KrkValue dict = krk_dict_of(0,NULL, 0); + krk_attachNamedValue(&self->inst.fields, "set", dict); + + return argv[0]; +} + +static KrkValue _MenuBar_place(int argc, KrkValue argv[], int hasKw) { + if (argc < 1 || !krk_isInstanceOf(argv[0], MenuBarClass)) + return krk_runtimeError(vm.exceptions->typeError, "expected MenuBar"); + struct MenuBarClass * self = (struct MenuBarClass*)AS_INSTANCE(argv[0]); + if (argc < 4 || !IS_INTEGER(argv[1]) || !IS_INTEGER(argv[2]) || !IS_INTEGER(argv[3])) + return krk_runtimeError(vm.exceptions->typeError, "expected int for x, y, width"); + if (argc < 5 || !krk_isInstanceOf(argv[4],YutaniWindow)) + return krk_runtimeError(vm.exceptions->typeError, "expected Window"); + + self->menuBar.x = AS_INTEGER(argv[1]); + self->menuBar.y = AS_INTEGER(argv[2]); + self->menuBar.width = AS_INTEGER(argv[3]); + self->menuBar.window = ((struct WindowClass*)AS_INSTANCE(argv[4]))->window; + return NONE_VAL(); +} + +static KrkValue _MenuBar_render(int argc, KrkValue argv[], int hasKw) { + if (argc < 1 || !krk_isInstanceOf(argv[0], MenuBarClass)) + return krk_runtimeError(vm.exceptions->typeError, "expected MenuBar"); + struct MenuBarClass * self = (struct MenuBarClass*)AS_INSTANCE(argv[0]); + if (argc < 2 || !krk_isInstanceOf(argv[1], GraphicsContext)) + return krk_runtimeError(vm.exceptions->typeError, "expected GraphicsContext"); + menu_bar_render(&self->menuBar, ((struct GraphicsContext*)AS_INSTANCE(argv[1]))->ctx); + return NONE_VAL(); +} + +static KrkValue _MenuBar_mouse_event(int argc, KrkValue argv[], int hasKw) { + if (argc < 1 || !krk_isInstanceOf(argv[0], MenuBarClass)) + return krk_runtimeError(vm.exceptions->typeError, "expected MenuBar"); + struct MenuBarClass * self = (struct MenuBarClass*)AS_INSTANCE(argv[0]); + if (argc < 3 || !krk_isInstanceOf(argv[1], YutaniWindow) || + !krk_isInstanceOf(argv[2], Message)) + return krk_runtimeError(vm.exceptions->typeError, "expected Window and Message"); + + struct MessageClass * msg = ((struct MessageClass*)AS_INSTANCE(argv[2])); + struct yutani_msg_window_mouse_event * me = (struct yutani_msg_window_mouse_event*)msg->msg->data; + + return INTEGER_VAL(menu_bar_mouse_event(((struct YutaniClass*)yctxInstance)->yctx, + ((struct WindowClass*)AS_INSTANCE(argv[1]))->window, + &self->menuBar, me, me->new_x, me->new_y)); +} + +static KrkValue _MenuBar_insert(int argc, KrkValue argv[], int hasKw) { + if (argc < 1 || !krk_isInstanceOf(argv[0], MenuBarClass)) + return krk_runtimeError(vm.exceptions->typeError, "expected MenuBar"); + struct MenuBarClass * self = (struct MenuBarClass*)AS_INSTANCE(argv[0]); + if (argc < 3 || !IS_STRING(argv[1]) || !krk_isInstanceOf(argv[2], MenuListClass)) + return krk_runtimeError(vm.exceptions->typeError, "expected str and MenuList"); + + menu_set_insert(self->menuBar.set, AS_CSTRING(argv[1]), ((struct MenuListClass*)AS_INSTANCE(argv[2]))->menuList); + + /* Also assign it to our dict */ + KrkValue dict = NONE_VAL(); + krk_tableGet(&self->inst.fields, OBJECT_VAL(S("set")), &dict); + if (IS_NONE(dict) || !krk_isInstanceOf(dict,vm.baseClasses->dictClass)) + return krk_runtimeError(vm.exceptions->baseException, "Failed to get set entries?"); + krk_tableSet(AS_DICT(dict), argv[1], argv[2]); + + return NONE_VAL(); +} + +static KrkValue _MenuList_init(int argc, KrkValue argv[], int hasKw) { + if (argc < 1 || !krk_isInstanceOf(argv[0], MenuListClass)) + return krk_runtimeError(vm.exceptions->typeError, "expected MenuList"); + struct MenuListClass * self = (struct MenuListClass*)AS_INSTANCE(argv[0]); + self->menuList = menu_create(); + + /* Give us a list to put entries in for GC tracking and retrieval by kuroko code */ + KrkValue list = krk_list_of(0,NULL,0); + krk_attachNamedValue(&self->inst.fields, "entries", list); + + return argv[0]; +} + +static KrkValue _MenuList_insert(int argc, KrkValue argv[], int hasKw) { + if (argc < 1 || !krk_isInstanceOf(argv[0], MenuListClass)) + return krk_runtimeError(vm.exceptions->typeError, "expected MenuList"); + struct MenuListClass * self = (struct MenuListClass*)AS_INSTANCE(argv[0]); + if (argc < 2 || !krk_isInstanceOf(argv[1], MenuEntryClass)) + return krk_runtimeError(vm.exceptions->typeError, "Expected MenuEntry"); + + /* Append to menu */ + menu_insert(self->menuList, ((struct MenuEntryClass*)AS_INSTANCE(argv[1]))->menuEntry); + + /* Append to internal list */ + KrkValue list = NONE_VAL(); + krk_tableGet(&self->inst.fields, OBJECT_VAL(S("entries")), &list); + if (IS_NONE(list) || !krk_isInstanceOf(list,vm.baseClasses->listClass)) + return krk_runtimeError(vm.exceptions->baseException, "Failed to get entries?"); + krk_writeValueArray(AS_LIST(list), argv[1]); + + return NONE_VAL(); +} + +static void _MenuEntry_callback_internal(struct MenuEntry * _self) { + struct MenuEntryClass * self = (struct MenuEntryClass *)_self->_private; + KrkValue callback = NONE_VAL(); + krk_tableGet(&self->inst.fields, OBJECT_VAL(S("callback")), &callback); + krk_push(OBJECT_VAL(self)); + krk_callSimple(callback, 1, 0); +} + +static KrkValue _MenuEntry_init(int argc, KrkValue argv[], int hasKw) { + if (argc < 1 || !krk_isInstanceOf(argv[0], MenuEntryClass)) + return krk_runtimeError(vm.exceptions->typeError, "expected MenuEntry"); + struct MenuEntryClass * self = (struct MenuEntryClass*)AS_INSTANCE(argv[0]); + + if (argc < 3 || !IS_STRING(argv[1])) + return krk_runtimeError(vm.exceptions->typeError, "expected title and callback"); + + KrkValue icon = NONE_VAL(), action = NONE_VAL(); + if (hasKw) { + krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("icon")), &icon); + krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("action")), &action); + if (!IS_NONE(icon) && !IS_STRING(icon)) + return krk_runtimeError(vm.exceptions->typeError, "icon must be str, not '%s'", krk_typeName(icon)); + if (!IS_NONE(action) && !IS_STRING(action)) + return krk_runtimeError(vm.exceptions->typeError, "action must be str, not '%s'", krk_typeName(action)); + } + + self->menuEntry = menu_create_normal( + IS_STRING(icon) ? AS_CSTRING(icon) : NULL, + IS_STRING(action) ? AS_CSTRING(action) : NULL, + AS_CSTRING(argv[1]), + _MenuEntry_callback_internal); + + self->menuEntry->_private = self; + + krk_attachNamedValue(&self->inst.fields, "callback", argv[2]); + + return argv[0]; +} + +/* TODO properties: icon, action, title */ + +static KrkValue _MenuEntrySubmenu_init(int argc, KrkValue argv[], int hasKw) { + if (argc < 1 || !krk_isInstanceOf(argv[0], MenuEntrySubmenuClass)) + return krk_runtimeError(vm.exceptions->typeError, "expected MenuEntrySubmenu"); + + struct MenuEntryClass * self = (struct MenuEntryClass*)AS_INSTANCE(argv[0]); + + if (argc < 2 || !IS_STRING(argv[1])) + return krk_runtimeError(vm.exceptions->typeError, "expected title to be a str"); + + KrkValue icon = NONE_VAL(), action = NONE_VAL(); + if (hasKw) { + krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("icon")), &icon); + krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S("action")), &action); + if (!IS_NONE(icon) && !IS_STRING(icon)) + return krk_runtimeError(vm.exceptions->typeError, "icon must be str, not '%s'", krk_typeName(icon)); + if (!IS_NONE(action) && !IS_STRING(action)) + return krk_runtimeError(vm.exceptions->typeError, "action must be str, not '%s'", krk_typeName(action)); + } + + self->menuEntry = menu_create_submenu( + IS_STRING(icon) ? AS_CSTRING(icon) : NULL, + IS_STRING(action) ? AS_CSTRING(action) : NULL, + AS_CSTRING(argv[1])); + + self->menuEntry->_private = self; + + return argv[0]; +} + +static KrkValue _MenuEntrySeparator_init(int argc, KrkValue argv[], int hasKw) { + if (argc < 1 || !krk_isInstanceOf(argv[0], MenuEntrySeparatorClass)) + return krk_runtimeError(vm.exceptions->typeError, "expected MenuEntrySeparator"); + struct MenuEntryClass * self = (struct MenuEntryClass*)AS_INSTANCE(argv[0]); + self->menuEntry = menu_create_separator(); + self->menuEntry->_private = self; + return argv[0]; +} + +KrkValue krk_module_onload__yutani(void) { + module = krk_newInstance(vm.baseClasses->moduleClass); + /* Store it on the stack for now so we can do stuff that may trip GC + * and not lose it to garbage colletion... */ + krk_push(OBJECT_VAL(module)); + + /** + * class Message(object): + * MSG_... = ... # Directly from the library headers. + */ + Message = krk_createClass(module, "Message", NULL); + Message->allocSize = sizeof(struct MessageClass); + Message->_ongcsweep = _message_sweep; + /* All the MSG_ constants */ +#define TYPE(type) krk_attachNamedValue(&Message->methods, "MSG_" #type, INTEGER_VAL(YUTANI_MSG_ ## type)) + TYPE(HELLO); TYPE(WINDOW_NEW); TYPE(FLIP); TYPE(KEY_EVENT); TYPE(MOUSE_EVENT); + TYPE(WINDOW_MOVE); TYPE(WINDOW_CLOSE); TYPE(WINDOW_SHOW); TYPE(WINDOW_HIDE); + TYPE(WINDOW_STACK); TYPE(WINDOW_FOCUS_CHANGE); TYPE(WINDOW_MOUSE_EVENT); + TYPE(FLIP_REGION); TYPE(WINDOW_NEW_FLAGS); TYPE(RESIZE_REQUEST); + TYPE(RESIZE_OFFER); TYPE(RESIZE_ACCEPT); TYPE(RESIZE_BUFID); TYPE(RESIZE_DONE); + TYPE(WINDOW_ADVERTISE); TYPE(SUBSCRIBE); TYPE(UNSUBSCRIBE); TYPE(NOTIFY); + TYPE(QUERY_WINDOWS); TYPE(WINDOW_FOCUS); TYPE(WINDOW_DRAG_START); TYPE(WINDOW_WARP_MOUSE); + TYPE(WINDOW_SHOW_MOUSE); TYPE(WINDOW_RESIZE_START); TYPE(SESSION_END); + TYPE(KEY_BIND); TYPE(WINDOW_UPDATE_SHAPE); TYPE(CLIPBOARD); TYPE(GOODBYE); + TYPE(SPECIAL_REQUEST); TYPE(WELCOME); TYPE(WINDOW_INIT); +#undef TYPE + /* Structure bindings */ + krk_defineNative(&Message->methods, "__getattr__", _message_getattr); + krk_finalizeClass(Message); + + /** + * class color(): + * rgb(a) value for use with graphics functions. + */ + YutaniColor = krk_createClass(module, "color", NULL); + YutaniColor->allocSize = sizeof(struct YutaniColor); + YutaniColor->docstring = S("color(r,g,b,a=255)\n Representation of an RGB(A) color."); + krk_defineNative(&YutaniColor->methods, "__init__", _yutani_color_init); + krk_defineNative(&YutaniColor->methods, "__repr__", _yutani_color_repr); + krk_defineNative(&YutaniColor->methods, "__str__", _yutani_color_str); + krk_finalizeClass(YutaniColor); + + /** + * class Yutani(object): + * yctx = yutani_t * + * display_width = yctx->display_width + * display_height = yctx->display_height + * + * def __init__(self): # Call yutani_init() + */ + Yutani = krk_createClass(module, "Yutani", NULL); + Yutani->allocSize = sizeof(struct YutaniClass); + Yutani->docstring = S("Yutani()\n Establish a connection to the compositor display server."); + krk_defineNative(&Yutani->methods, "display_width", _yutani_display_width)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY; + krk_defineNative(&Yutani->methods, "display_height", _yutani_display_height)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY; + krk_defineNative(&Yutani->methods, "__repr__", _yutani_repr); + krk_defineNative(&Yutani->methods, "__init__", _yutani_init); + krk_defineNative(&Yutani->methods, "poll", _yutani_poll); + krk_defineNative(&Yutani->methods, "wait_for", _yutani_wait_for); + krk_defineNative(&Yutani->methods, "subscribe", _yutani_subscribe); + krk_defineNative(&Yutani->methods, "unsubscribe", _yutani_unsubscribe); + krk_defineNative(&Yutani->methods, "query_windows", _yutani_query_windows); + krk_defineNative(&Yutani->methods, "fileno", _yutani_fileno); + krk_defineNative(&Yutani->methods, "query", _yutani_query); + krk_defineNative(&Yutani->methods, "menu_process_event", _yutani_menu_process_event); + #if 0 + krk_defineNative(&Yutani->methods, "focus_window", _yutani_focus_window); + krk_defineNative(&Yutani->methods, "session_end", _yutani_session_end); + krk_defineNative(&Yutani->methods, "key_bind", _yutani_key_bind); + #endif + krk_finalizeClass(Yutani); + + /** + * class GraphicsContext(): + * ctx = gfx_context_t * + */ + GraphicsContext = krk_createClass(module, "GraphicsContext", NULL); + GraphicsContext->allocSize = sizeof(struct GraphicsContext); + krk_defineNative(&GraphicsContext->methods, "width", _gfx_width)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY; + krk_defineNative(&GraphicsContext->methods, "height", _gfx_height)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY; + krk_defineNative(&GraphicsContext->methods, "fill", _gfx_fill)->doc = + "GraphicsContext.fill(color)\n" + " Fill the entire context with the given color."; + krk_defineNative(&GraphicsContext->methods, "flip", _gfx_flip)->doc = + "GraphicsContext.flip()\n" + " If the context is double-buffered, flip its backbuffer."; + krk_defineNative(&GraphicsContext->methods, "blur", _gfx_blur)->doc = + "GraphicsContext.blur(radius=2)\n" + " Perform an in-place box blur on this graphics context."; + krk_defineNative(&GraphicsContext->methods, "line", _gfx_line)->doc = + "GraphicsContext.line(x0,x1,y0,y1,color,thickness=None)\n" + " Draw a line between the given points. If thickness is not provided, uses a\n" + " a simple Bresenham algorithm. If thickness is an int, draws with a box-shaped pen.\n" + " If thickness is a float, draws using a point-distance antialiasing algorithm."; + krk_defineNative(&GraphicsContext->methods, "rect", _gfx_rect)->doc = + "GraphicsContext.rect(x,y,width,height,color,solid=False,radius=None)\n" + " Draw a filled rectangle. If solid is True, paints the given color directly to\n" + " the underlying backbuffer with no alpha calculations. If radius is provided,\n" + " draws a rounded rectangle."; + krk_defineNative(&GraphicsContext->methods, "draw_sprite", _gfx_draw_sprite)->doc = + "GraphicsContext.draw_sprite(sprite,x,y,alpha=None,rotation=None,scale=None,color=None)\n" + " Blit a sprite to this graphics context at the given coordinates.\n" + " alpha: float of opacity; 1.0 = fully opaque (default)\n" + " rotation: float of radians; when a rotation is given, the coordinates provided are\n" + " the center of the rendered sprite, rather than the upper left corner.\n" + " scale: (int,int) of final resolution of sprite; can not be used with rotation.\n" + " color: color to paint the sprite as, can not be used with rotation or scale;\n" + " used to paint a given color with this sprite as a 'brush'. Useful for\n" + " colored icons, such as those found in the panel."; + krk_finalizeClass(GraphicsContext); + + /** + * class Window(GraphicsContext): + * ctx = gfx_context_t * + * window = yutani_window_t * + */ + YutaniWindow = krk_createClass(module, "Window", GraphicsContext); + YutaniWindow->allocSize = sizeof(struct WindowClass); + YutaniWindow->docstring = S("Window(width,height,flags=0,title=None,icon=None,doublebuffer=False)\n" + " Create a new window and initializes a graphics rendering context for it."); + krk_defineNative(&YutaniWindow->methods, "__repr__", _window_repr); + krk_defineNative(&YutaniWindow->methods, "__init__", _window_init); + krk_defineNative(&YutaniWindow->methods, "flip", _window_flip); + krk_defineNative(&YutaniWindow->methods, "move", _window_move); + krk_defineNative(&YutaniWindow->methods, "set_focused", _window_set_focused); + krk_defineNative(&YutaniWindow->methods, "close", _window_close); + krk_defineNative(&YutaniWindow->methods, "set_stack", _window_set_stack); + krk_defineNative(&YutaniWindow->methods, "special_request", _window_special_request); + krk_defineNative(&YutaniWindow->methods, "resize", _window_resize); + krk_defineNative(&YutaniWindow->methods, "resize_start", _window_resize_start); + krk_defineNative(&YutaniWindow->methods, "resize_done", _window_resize_done); + krk_defineNative(&YutaniWindow->methods, "resize_offer", _window_resize_offer); + krk_defineNative(&YutaniWindow->methods, "resize_accept", _window_resize_accept); + krk_defineNative(&YutaniWindow->methods, "update_shape", _window_update_shape); + krk_defineNative(&YutaniWindow->methods, "show_mouse", _window_show_mouse); + krk_defineNative(&YutaniWindow->methods, "warp_mouse", _window_warp_mouse); + krk_defineNative(&YutaniWindow->methods, "set_stack", _window_set_stack); + krk_defineNative(&YutaniWindow->methods, "advertise", _window_advertise); + krk_defineNative(&YutaniWindow->methods, "reinit", _window_reinit); + + krk_defineNative(&YutaniWindow->methods, "wid", _window_wid)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY; + krk_defineNative(&YutaniWindow->methods, "x", _window_x)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY; + krk_defineNative(&YutaniWindow->methods, "y", _window_y)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY; + krk_defineNative(&YutaniWindow->methods, "focused", _window_focused)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY; + krk_finalizeClass(YutaniWindow); + + /** + * class Sprite(GraphicsContext): + * ctx = gfx_context_t * + * sprite = sprite_t + */ + YutaniSprite = krk_createClass(module, "Sprite", GraphicsContext); + YutaniSprite->allocSize = sizeof(struct YutaniSprite); + YutaniSprite->_ongcsweep = _sprite_sweep; + YutaniSprite->docstring = S("Sprite(filename)\n Create a sprite from the requested texture file."); + krk_defineNative(&YutaniSprite->methods, "__repr__", _sprite_repr); + krk_defineNative(&YutaniSprite->methods, "__init__", _sprite_init); + krk_finalizeClass(YutaniSprite); + + /** + * class Font(): + * fontType, fontSize, fontGamma, fontStroke + */ + YutaniFont = krk_createClass(module, "Font", NULL); + YutaniFont->allocSize = sizeof(struct YutaniFont); + YutaniFont->docstring = S("Font(type,size,gamma=1.7,stroke=0.75,color=color(0,0,0))\n" + " Create a Font specification for rendering text."); + krk_defineNative(&YutaniFont->methods, "__init__", _font_init); + krk_defineNative(&YutaniFont->methods, "draw_string", _font_draw_string)->doc = + "Font.draw_string(gfxContext, string, x, y)\n" + " Draw text to a graphics context with this font."; + krk_defineNative(&YutaniFont->methods, "width", _font_width)->doc = + "Font.width(string)\n" + " Calculate the rendered width of the given string when drawn with this font."; + krk_defineNative(&YutaniFont->methods, "size", _font_size)->flags |= KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY; + /* Some static values */ +#define ATTACH_FONT(name) krk_attachNamedValue(&YutaniFont->methods, #name, INTEGER_VAL(SDF_ ## name)) + ATTACH_FONT(FONT_THIN); + ATTACH_FONT(FONT_BOLD); + ATTACH_FONT(FONT_MONO); + ATTACH_FONT(FONT_MONO_BOLD); + ATTACH_FONT(FONT_MONO_OBLIQUE); + ATTACH_FONT(FONT_MONO_BOLD_OBLIQUE); + ATTACH_FONT(FONT_OBLIQUE); + ATTACH_FONT(FONT_BOLD_OBLIQUE); + krk_finalizeClass(YutaniFont); + + MenuBarClass = krk_createClass(module, "MenuBar", NULL); + MenuBarClass->allocSize = sizeof(struct MenuBarClass); + MenuBarClass->_ongcsweep = _MenuBar_gcsweep; + krk_defineNative(&MenuBarClass->methods, "__init__", _MenuBar_init); + krk_defineNative(&MenuBarClass->methods, "place", _MenuBar_place); + krk_defineNative(&MenuBarClass->methods, "render", _MenuBar_render); + krk_defineNative(&MenuBarClass->methods, "mouse_event", _MenuBar_mouse_event); + krk_defineNative(&MenuBarClass->methods, "insert", _MenuBar_insert); + krk_finalizeClass(MenuBarClass); + + MenuListClass = krk_createClass(module, "MenuList", NULL); + MenuListClass->allocSize = sizeof(struct MenuListClass); + krk_defineNative(&MenuListClass->methods, "__init__", _MenuList_init); + krk_defineNative(&MenuListClass->methods, "insert", _MenuList_insert); + krk_finalizeClass(MenuListClass); + + MenuEntryClass = krk_createClass(module, "MenuEntry", NULL); + MenuEntryClass->allocSize = sizeof(struct MenuEntryClass); + krk_defineNative(&MenuEntryClass->methods, "__init__", _MenuEntry_init); + krk_finalizeClass(MenuEntryClass); + + MenuEntrySubmenuClass = krk_createClass(module, "MenuEntrySubmenu", MenuEntryClass); + krk_defineNative(&MenuEntrySubmenuClass->methods, "__init__", _MenuEntrySubmenu_init); + krk_finalizeClass(MenuEntrySubmenuClass); + MenuEntrySeparatorClass = krk_createClass(module, "MenuEntrySeparator", MenuEntryClass); + krk_defineNative(&MenuEntrySeparatorClass->methods, "__init__", _MenuEntrySeparator_init); + krk_finalizeClass(MenuEntrySeparatorClass); + + KrkInstance * Decorator = krk_newInstance(vm.baseClasses->objectClass); + krk_attachNamedObject(&module->fields, "Decorator", (KrkObj*)Decorator); + krk_defineNative(&Decorator->fields, "get_bounds", _decor_get_bounds); + krk_defineNative(&Decorator->fields, "render", _decor_render); + krk_defineNative(&Decorator->fields, "handle_event", _decor_handle_event); + krk_defineNative(&Decorator->fields, "show_default_menu", _decor_show_default_menu); +#define ATTACH_CONSTANT(name) krk_attachNamedValue(&Decorator->fields, #name, INTEGER_VAL(name)) + ATTACH_CONSTANT(DECOR_OTHER); + ATTACH_CONSTANT(DECOR_CLOSE); + ATTACH_CONSTANT(DECOR_RESIZE); + ATTACH_CONSTANT(DECOR_MAXIMIZE); + ATTACH_CONSTANT(DECOR_RIGHT); + + ATTACH_CONSTANT(DECOR_ACTIVE); + ATTACH_CONSTANT(DECOR_INACTIVE); + + ATTACH_CONSTANT(DECOR_FLAG_DECORATED); + ATTACH_CONSTANT(DECOR_FLAG_NO_MAXIMIZE); + ATTACH_CONSTANT(DECOR_FLAG_TILED); + ATTACH_CONSTANT(DECOR_FLAG_TILE_LEFT); + ATTACH_CONSTANT(DECOR_FLAG_TILE_RIGHT); + ATTACH_CONSTANT(DECOR_FLAG_TILE_UP); + ATTACH_CONSTANT(DECOR_FLAG_TILE_DOWN); +#undef ATTACH_CONSTANT + + /* Pop the module object before returning; it'll get pushed again + * by the VM before the GC has a chance to run, so it's safe. */ + assert(AS_INSTANCE(krk_pop()) == module); + return OBJECT_VAL(module); +} + diff --git a/lib/menu.c b/lib/menu.c index 4ae643ca..5a45ead2 100644 --- a/lib/menu.c +++ b/lib/menu.c @@ -713,7 +713,7 @@ void menu_key_action(struct MenuList * menu, struct yutani_msg_key_event * me) { int active = (bar->active_entry_idx + 1 + bar->num_entries) % (bar->num_entries); bar->active_entry = &bar->entries[active]; if (bar->redraw_callback) { - bar->redraw_callback(); + bar->redraw_callback(bar); } menu_bar_show_menu(yctx, bar->window, bar, -1, bar->active_entry); } else { @@ -738,7 +738,7 @@ void menu_key_action(struct MenuList * menu, struct yutani_msg_key_event * me) { int active = (menu->_bar->active_entry_idx - 1 + menu->_bar->num_entries) % (menu->_bar->num_entries); menu->_bar->active_entry = &menu->_bar->entries[active]; if (menu->_bar->redraw_callback) { - menu->_bar->redraw_callback(); + menu->_bar->redraw_callback(menu->_bar); } menu_bar_show_menu(yctx, menu->_bar->window, menu->_bar, -1, menu->_bar->active_entry); } else if (menu->parent && menu->parent->window) { @@ -941,7 +941,7 @@ void menu_bar_show_menu(yutani_t * yctx, yutani_window_t * window, struct menu_b self->active_entry = _entries; self->active_entry_idx = i; if (self->redraw_callback) { - self->redraw_callback(); + self->redraw_callback(self); } } diff --git a/lib/png.c b/lib/png.c new file mode 100644 index 00000000..37662b76 --- /dev/null +++ b/lib/png.c @@ -0,0 +1,508 @@ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * This file is part of ToaruOS and is released under the terms + * of the NCSA / University of Illinois License - see LICENSE.md + * Copyright (C) 2020 K. Lange + * + * libtoaru_png: PNG decoder + */ +#include +#include + +#include +#include + +/** + * Read 32-bit big-endian value from file. + */ +unsigned int read_32(FILE * f) { + unsigned char a = fgetc(f); + unsigned char b = fgetc(f); + unsigned char c = fgetc(f); + unsigned char d = fgetc(f); + return (a << 24) | (b << 16) | (c << 8) | d; +} + +/** + * Read 16-bit big-endian value from file. + */ +unsigned int read_16(FILE * f) { + unsigned char a = fgetc(f); + unsigned char b = fgetc(f); + return (a << 8) | b; +} + +/** + * (Debug) Return a chunk type as a string. + */ +__attribute__((unused)) +static char* reorder_type(unsigned int type) { + static char out[4]; + out[0] = (type >> 24) & 0xFF; + out[1] = (type >> 16) & 0xFF; + out[2] = (type >> 8) & 0xFF; + out[3] = (type >> 0) & 0xFF; + return out; +} + +/** + * Internal PNG decoder state for use with inflate. + */ +struct png_ctx { + FILE * f; /* File being decoded. */ + sprite_t * sprite; /* Sprite being generated. */ + int y; /* Cursor pointers for writing out bitmap data */ + int x; + char buffer[4]; /* A buffer to hold a pixel's worth of data until it can + be written out to the image with the right filter. */ + int buf_off; /* How much data is in the above buffer */ + int seen_ihdr; /* Whether the IHDR was seen; for error handling */ + + unsigned int width; /* Image width (dup from sprite) */ + unsigned int height; /* Image height (dup from sprite) */ + int bit_depth; /* Bit depth of the image */ + int color_type; /* PNG color type */ + int compression; /* Compression method (must be 0) */ + int filter; /* Filter method (must be 0) */ + int interlace; /* Interlace method (we only support 0) */ + + unsigned int size; /* Remaining IDAT chunk size */ + int sf; /* Current scanline filter type */ +}; + +/* PNG chunk types */ +#define PNG_IHDR 0x49484452 +#define PNG_IDAT 0x49444154 +#define PNG_IEND 0x49454e44 + +/* PNG filter types */ +#define PNG_FILTER_NONE 0 +#define PNG_FILTER_SUB 1 +#define PNG_FILTER_UP 2 +#define PNG_FILTER_AVG 3 +#define PNG_FILTER_PAETH 4 + +/** + * Read a byte from the IDAT chunk. + * Tracks when an IDAT has been read to completion and + * can load the next IDAT (or bail of this was the last one) + */ +static uint8_t _get(struct inflate_context * ctx) { + struct png_ctx * c = (ctx->input_priv); + if (c->size == 0) { + + /* Read the CRC32 from the end of this IDAT */ + unsigned int check = read_32(c->f); + (void)check; /* ... and in theory check it... */ + + /* Read the next IDAT chunk header */ + unsigned int size = read_32(c->f); + unsigned int type = read_32(c->f); + + c->size = size; + + if (type != PNG_IDAT) { + /* This isn't an IDAT? That's wrong! */ + fprintf(stderr, "And this is the wrong type (0x%x), I'm just bailing.\n", type); + fprintf(stderr, "size read was 0x%x\n", size); + exit(0); + } + } + + /* Read one byte from the input */ + c->size--; + int i = fgetc(c->f); + + /* If this was EOF, we should handle that error case... probably... */ + if (i < 0) fprintf(stderr, "This is probably not good.\n"); + + return i; +} + +/** + * Paeth predictor + * Described in section 6.6 of the RFC + */ +#define ABS(a) ((a >= 0) ? (a) : -(a)) +static int paeth(int a, int b, int c) { + int p = a + b - c; + int pa = ABS(p - a); + int pb = ABS(p - b); + int pc = ABS(p - c); + if (pa <= pb && pa <= pc) return a; + else if (pb <= pc) return b; + return c; +} + +static void write_pixel(struct png_ctx * c, uint32_t color) { + SPRITE((c->sprite), (c->x), (c->y)) = color; + + /* Reset the short buffer */ + c->buf_off = 0; + + /* Advance to next pixel */ + c->x++; + if (c->x == (int)c->width) { + /* Advance to next line; next read is scanline filter type */ + c->x = -1; + c->y++; + } +} + +static void process_pixel_type_6(struct png_ctx * c) { + /* + * Obtain pixel data from short buffer; + * For color type 6, this is always in R G B A order in the + * bytestream, so we don't have to worry about subpixel ordering + * or weird color masks. + */ + unsigned int r = c->buffer[0]; + unsigned int g = c->buffer[1]; + unsigned int b = c->buffer[2]; + unsigned int a = c->buffer[3]; + + /* Apply filters */ + if (c->sf == PNG_FILTER_SUB) { + /* Add raw value to the pixel on the left */ + if (c->x > 0) { + uint32_t left = SPRITE((c->sprite), (c->x - 1), (c->y)); + r += _RED(left); + g += _GRE(left); + b += _BLU(left); + a += _ALP(left); + } + } else if (c->sf == PNG_FILTER_UP) { + /* Add raw value to the pixel above */ + if (c->y > 0) { + uint32_t up = SPRITE((c->sprite), (c->x), (c->y - 1)); + r += _RED(up); + g += _GRE(up); + b += _BLU(up); + a += _ALP(up); + } + } else if (c->sf == PNG_FILTER_AVG) { + /* Add raw value to the average of the pixel above and left */ + uint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0; + uint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0; + + r += ((int)_RED(left) + (int)_RED(up)) / 2; + g += ((int)_GRE(left) + (int)_GRE(up)) / 2; + b += ((int)_BLU(left) + (int)_BLU(up)) / 2; + a += ((int)_ALP(left) + (int)_ALP(up)) / 2; + } else if (c->sf == PNG_FILTER_PAETH) { + /* Use the Paeth predictor */ + uint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0; + uint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0; + uint32_t upleft = (c->x > 0 && c->y > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y - 1)) : 0; + + r = ((int)r + paeth((int)_RED(left),(int)_RED(up),(int)_RED(upleft))) % 256; + g = ((int)g + paeth((int)_GRE(left),(int)_GRE(up),(int)_GRE(upleft))) % 256; + b = ((int)b + paeth((int)_BLU(left),(int)_BLU(up),(int)_BLU(upleft))) % 256; + a = ((int)a + paeth((int)_ALP(left),(int)_ALP(up),(int)_ALP(upleft))) % 256; + } + + /* Write new pixel to the image */ + write_pixel(c, rgba(r,g,b,a)); +} + +static void process_pixel_type_2(struct png_ctx * c) { + /* + * Obtain pixel data from short buffer; + * For color type 6, this is always in R G B A order in the + * bytestream, so we don't have to worry about subpixel ordering + * or weird color masks. + */ + unsigned int r = c->buffer[0]; + unsigned int g = c->buffer[1]; + unsigned int b = c->buffer[2]; + + /* Apply filters */ + if (c->sf == PNG_FILTER_SUB) { + /* Add raw value to the pixel on the left */ + if (c->x > 0) { + uint32_t left = SPRITE((c->sprite), (c->x - 1), (c->y)); + r += _RED(left); + g += _GRE(left); + b += _BLU(left); + } + } else if (c->sf == PNG_FILTER_UP) { + /* Add raw value to the pixel above */ + if (c->y > 0) { + uint32_t up = SPRITE((c->sprite), (c->x), (c->y - 1)); + r += _RED(up); + g += _GRE(up); + b += _BLU(up); + } + } else if (c->sf == PNG_FILTER_AVG) { + /* Add raw value to the average of the pixel above and left */ + uint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0; + uint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0; + + r += ((int)_RED(left) + (int)_RED(up)) / 2; + g += ((int)_GRE(left) + (int)_GRE(up)) / 2; + b += ((int)_BLU(left) + (int)_BLU(up)) / 2; + } else if (c->sf == PNG_FILTER_PAETH) { + /* Use the Paeth predictor */ + uint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0; + uint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0; + uint32_t upleft = (c->x > 0 && c->y > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y - 1)) : 0; + + r = ((int)r + paeth((int)_RED(left),(int)_RED(up),(int)_RED(upleft))) % 256; + g = ((int)g + paeth((int)_GRE(left),(int)_GRE(up),(int)_GRE(upleft))) % 256; + b = ((int)b + paeth((int)_BLU(left),(int)_BLU(up),(int)_BLU(upleft))) % 256; + } + + /* Write new pixel to the image */ + write_pixel(c, rgb(r,g,b)); +} + +static void process_pixel_type_4(struct png_ctx * c) { + /* + * Obtain pixel data from short buffer; + * For color type 6, this is always in R G B A order in the + * bytestream, so we don't have to worry about subpixel ordering + * or weird color masks. + */ + unsigned int b = c->buffer[0]; + unsigned int a = c->buffer[1]; + + /* Apply filters */ + if (c->sf == PNG_FILTER_SUB) { + /* Add raw value to the pixel on the left */ + if (c->x > 0) { + uint32_t left = SPRITE((c->sprite), (c->x - 1), (c->y)); + b += _BLU(left); + a += _ALP(left); + } + } else if (c->sf == PNG_FILTER_UP) { + /* Add raw value to the pixel above */ + if (c->y > 0) { + uint32_t up = SPRITE((c->sprite), (c->x), (c->y - 1)); + b += _BLU(up); + a += _ALP(up); + } + } else if (c->sf == PNG_FILTER_AVG) { + /* Add raw value to the average of the pixel above and left */ + uint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0; + uint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0; + + b += ((int)_BLU(left) + (int)_BLU(up)) / 2; + a += ((int)_ALP(left) + (int)_ALP(up)) / 2; + } else if (c->sf == PNG_FILTER_PAETH) { + /* Use the Paeth predictor */ + uint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0; + uint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0; + uint32_t upleft = (c->x > 0 && c->y > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y - 1)) : 0; + + b = ((int)b + paeth((int)_BLU(left),(int)_BLU(up),(int)_BLU(upleft))) % 256; + a = ((int)a + paeth((int)_ALP(left),(int)_ALP(up),(int)_ALP(upleft))) % 256; + } + + /* Write new pixel to the image */ + write_pixel(c, rgba(b,b,b,a)); +} + +static void process_pixel_type_0(struct png_ctx * c) { + unsigned int b = c->buffer[0]; + if (c->sf == PNG_FILTER_SUB) { + if (c->x > 0) { + uint32_t left = SPRITE((c->sprite), (c->x - 1), (c->y)); + b += _BLU(left); + } + } else if (c->sf == PNG_FILTER_UP) { + if (c->y > 0) { + uint32_t up = SPRITE((c->sprite), (c->x), (c->y - 1)); + b += _BLU(up); + } + } else if (c->sf == PNG_FILTER_AVG) { + uint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0; + uint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0; + b += ((int)_BLU(left) + (int)_BLU(up)) / 2; + } else if (c->sf == PNG_FILTER_PAETH) { + uint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0; + uint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0; + uint32_t upleft = (c->x > 0 && c->y > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y - 1)) : 0; + b = ((int)b + paeth((int)_BLU(left),(int)_BLU(up),(int)_BLU(upleft))) % 256; + } + + /* Write new pixel to the image */ + write_pixel(c, rgb(b,b,b)); +} + + +/** + * Handle decompressed output from the inflater + * + * Writes pixel data to the image, and applies relevant filters. + */ +static void _write(struct inflate_context * ctx, unsigned int sym) { + struct png_ctx * c = (ctx->input_priv); + + /* Put this byte into the short buffer */ + c->buffer[c->buf_off] = sym; + c->buf_off++; + + /* If this is the beginning of a scanline... */ + if (c->x == -1 && c->buf_off == 1) { + /* Then this is the scanline filter type */ + c->sf = sym; + + /* Reset the buffer, advance to the beginning of the actual scanline */ + c->x = 0; + c->buf_off = 0; + } else if (c->buf_off == 1 && c->color_type == 0) { + process_pixel_type_0(c); + } else if (c->buf_off == 2 && c->color_type == 4) { + process_pixel_type_4(c); + } else if (c->buf_off == 3 && c->color_type == 2) { + process_pixel_type_2(c); + } else if (c->buf_off == 4 && c->color_type == 6) { + process_pixel_type_6(c); + } +} + +static int color_type_has_alpha(int c) { + switch (c) { + case 4: + case 6: + return ALPHA_EMBEDDED; + default: + return 0; + } +} + +int load_sprite_png(sprite_t * sprite, char * filename) { + FILE * f = fopen(filename,"r"); + if (!f) { + fprintf(stderr, "Failed to open file %s\n", filename); + return 1; + } + + /* Read the PNG signature */ + unsigned char sig[] = {137, 80, 78, 71, 13, 10, 26, 10}; + for (int i = 0; i < 8; ++i) { + unsigned char c = fgetc(f); + if (c != sig[i]) { + fprintf(stderr, "byte %d (%d) does not match expected (%d)\n", i, c, sig[i]); + goto _error; + } + } + + /* Set up context for future calls to inflate */ + struct png_ctx c; + c.sprite = sprite; + c.x = -1; + c.y = 0; + c.f = f; + c.buf_off = 0; + c.seen_ihdr = 0; + + while (1) { + /* read chunks */ + unsigned int size = read_32(f); + unsigned int type = read_32(f); + + if (feof(f)) break; + + switch (type) { + case PNG_IHDR: + { + /* Image should only have one IHDR */ + if (c.seen_ihdr) return 1; + + c.seen_ihdr = 1; + c.width = read_32(f); /* 4 */ + c.height = read_32(f); /* 8 */ + c.bit_depth = fgetc(f); /* 9 */ + c.color_type = fgetc(f); /* 10 */ + c.compression = fgetc(f); /* 11 */ + c.filter = fgetc(f); /* 12 */ + c.interlace = fgetc(f); /* 13 */ + + /* Invalid / non-standard compression and filter types */ + if (c.compression != 0) return 1; + if (c.filter != 0) return 1; + + /* 0 for none, 1 for Adam7 */ + if (c.interlace != 0 && c.interlace != 1) return 1; + + if (c.bit_depth != 8) return 1; /* Sorry */ + if (c.color_type < 0 || c.color_type > 6 || (c.color_type & 1)) return 1; /* Sorry, no indexed support */ + + /* Allocate space */ + sprite->width = c.width; + sprite->height = c.height; + sprite->bitmap = malloc(sizeof(uint32_t) * sprite->width * sprite->height); + sprite->masks = NULL; + sprite->alpha = color_type_has_alpha(c.color_type); + sprite->blank = 0; + + + /* Skip */ + for (unsigned int i = 13; i < size; ++i) fgetc(f); + } + break; + + case PNG_IDAT: + { + /* First two bytes of IDAT data are ZLIB header */ + unsigned int cflags = fgetc(f); + if ((cflags & 0xF) != 8) { + /* Compression type must be 8 */ + fprintf(stderr, "Expected flags to be 8 but it's 0x%x\n", cflags); + return 1; + } + unsigned int aflags = fgetc(f); + if (aflags & (1 << 5)) { + fprintf(stderr, "There are preset bytes and I don't know what to do.\n"); + return 1; + } + + struct inflate_context ctx; + ctx.input_priv = &c; + ctx.output_priv = &c; + ctx.get_input = _get; + ctx.write_output = _write; + ctx.ring = NULL; /* use builtin */ + + c.size = size - 2; /* 2 for the bytes we already read */ + + deflate_decompress(&ctx); + + /* The IDATs contain a ZLIB stream, so they end with an + * adler32 checksum. Skip that. */ + unsigned int adler = read_32(f); + (void)adler; + } + break; + case PNG_IEND: + /* We don't actually have anything to do here. */ + break; + default: + /* IHDR must be first */ + if (!c.seen_ihdr) return 1; + //fprintf(stderr, "I don't know what this is! %4s 0x%x\n", reorder_type(type), type); + /* Skip */ + for (unsigned int i = 0; i < size; ++i) fgetc(f); + break; + } + + unsigned int crc32 = read_32(f); + (void)crc32; + } + + /* + * Data in PNGs is unpremultiplied, but our sprites expect + * premultiplied alpha, so convert the image data + */ + for (int y = 0; y < sprite->height; ++y) { + for (int x = 0; x < sprite->width; ++x) { + SPRITE(sprite,x,y) = premultiply(SPRITE(sprite,x,y)); + } + } + + return 0; + +_error: + fclose(f); + return 1; +} diff --git a/lib/rline.c b/lib/rline.c index 4022d1d8..6c1fb9c1 100644 --- a/lib/rline.c +++ b/lib/rline.c @@ -1,65 +1,51 @@ /* vim: tabstop=4 shiftwidth=4 noexpandtab * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md - * Copyright (C) 2015-2018 K. Lange + * Copyright (C) 2018 K. Lange * - * rline - a line reading library. + * Experimental rline replacement with syntax highlighting, based + * on bim's highlighting and line editing. * - * Implements an interface similar to readline, providing more - * complex line editing than what the raw tty interface supplies. */ - -#define _POSIX_C_SOURCE 1 -#define _XOPEN_SOURCE 500 -#include - +#define _XOPEN_SOURCE +#define _DEFAULT_SOURCE #include #include +#include +#include #include +#include +#include +#include +#include +#ifndef _WIN32 #include - -#include +#include +#else +#include +#include +#include "wcwidth._h" +#endif +#ifdef __toaru__ #include +#else +#include "rline.h" +#endif -static struct termios old; - -static void set_unbuffered() { - tcgetattr(fileno(stdin), &old); - struct termios new = old; - new.c_lflag &= (~ICANON & ~ECHO); - tcsetattr(fileno(stdin), TCSAFLUSH, &new); -} - -static void set_buffered() { - tcsetattr(fileno(stdin), TCSAFLUSH, &old); -} - - -void rline_redraw(rline_context_t * context) { - if (context->quiet) return; - printf("\033[u%s\033[K", context->buffer); - for (int i = context->offset; i < context->collected; ++i) { - printf("\033[D"); - } - fflush(stdout); -} +static __attribute__((used)) int _isdigit(int c) { if (c > 128) return 0; return isdigit(c); } +static __attribute__((used)) int _isxdigit(int c) { if (c > 128) return 0; return isxdigit(c); } -void rline_redraw_clean(rline_context_t * context) { - if (context->quiet) return; - printf("\033[u%s", context->buffer); - for (int i = context->offset; i < context->collected; ++i) { - printf("\033[D"); - } - fflush(stdout); -} +#undef isdigit +#undef isxdigit +#define isdigit(c) _isdigit(c) +#define isxdigit(c) _isxdigit(c) char * rline_history[RLINE_HISTORY_ENTRIES]; int rline_history_count = 0; int rline_history_offset = 0; int rline_scroll = 0; char * rline_exit_string = "exit\n"; - -static char rline_temp[1024]; +int rline_terminal_width = 0; void rline_history_insert(char * str) { if (str[strlen(str)-1] == '\n') { @@ -84,8 +70,9 @@ void rline_history_insert(char * str) { void rline_history_append_line(char * str) { if (rline_history_count) { char ** s = &rline_history[(rline_history_count - 1 + rline_history_offset) % RLINE_HISTORY_ENTRIES]; - char * c = malloc(strlen(*s) + strlen(str) + 2); - sprintf(c, "%s\n%s", *s, str); + size_t len = strlen(*s) + strlen(str) + 2; + char * c = malloc(len); + snprintf(c, len, "%s\n%s", *s, str); if (c[strlen(c)-1] == '\n') { c[strlen(c)-1] = '\0'; } @@ -104,475 +91,2183 @@ char * rline_history_prev(int item) { return rline_history_get(rline_history_count - item); } -void rline_reverse_search(rline_context_t * context) { - char input[512] = {0}; - int collected = 0; - int start_at = 0; - int changed = 0; - fprintf(stderr, "\033[G\033[0m\033[s"); - fflush(stderr); - key_event_state_t kbd_state = {0}; - char * match = ""; - int match_index = 0; - while (1) { - /* Find matches */ -try_rev_search_again: - if (collected && changed) { - match = ""; - match_index = 0; - for (int i = start_at; i < rline_history_count; i++) { - char * c = rline_history_prev(i+1); - if (strstr(c, input)) { - match = c; - match_index = i; - break; - } - } - if (!strcmp(match,"")) { - if (start_at) { - start_at = 0; - goto try_rev_search_again; - } - collected--; - input[collected] = '\0'; - if (collected) { - goto try_rev_search_again; - } - } - } - fprintf(stderr, "\033[u(reverse-i-search)`%s': %s\033[K", input, match); - fflush(stderr); - changed = 0; +#define UTF8_ACCEPT 0 +#define UTF8_REJECT 1 - uint32_t key_sym = kbd_key(&kbd_state, fgetc(stdin)); - switch (key_sym) { - case KEY_NONE: - break; - case KEY_BACKSPACE: - case 0x7F: /* delete */ - if (collected > 0) { - collected--; - input[collected] = '\0'; - start_at = 0; - changed = 1; - } - break; - case KEY_CTRL_C: - printf("^C\n"); - return; - case KEY_CTRL_R: - start_at = match_index + 1; - changed = 1; - break; - case KEY_ESCAPE: - case KEY_ARROW_LEFT: - case KEY_ARROW_RIGHT: - context->cancel = 1; - case '\n': - memcpy(context->buffer, match, strlen(match) + 1); - context->collected = strlen(match); - context->offset = context->collected; - if (!context->quiet && context->callbacks->redraw_prompt) { - fprintf(stderr, "\033[G\033[K"); - context->callbacks->redraw_prompt(context); - } - fprintf(stderr, "\033[s"); - rline_redraw_clean(context); - if (key_sym == '\n' && !context->quiet) { - fprintf(stderr, "\n"); - } - return; - default: - if (key_sym < KEY_NORMAL_MAX) { - input[collected] = (char)key_sym; - collected++; - input[collected] = '\0'; - start_at = 0; - changed = 1; - } - break; - } +/** + * Conceptually similar to its predecessor, this implementation is much + * less cool, as it uses three separate state tables and more shifts. + */ +static inline uint32_t decode(uint32_t* state, uint32_t* codep, uint32_t byte) { + static int state_table[32] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xxxxxxx */ + 1,1,1,1,1,1,1,1, /* 10xxxxxx */ + 2,2,2,2, /* 110xxxxx */ + 3,3, /* 1110xxxx */ + 4, /* 11110xxx */ + 1 /* 11111xxx */ + }; + + static int mask_bytes[32] = { + 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F, + 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x1F,0x1F,0x1F,0x1F, + 0x0F,0x0F, + 0x07, + 0x00 + }; + + static int next[5] = { + 0, + 1, + 0, + 2, + 3 + }; + + if (*state == UTF8_ACCEPT) { + *codep = byte & mask_bytes[byte >> 3]; + *state = state_table[byte >> 3]; + } else if (*state > 0) { + *codep = (byte & 0x3F) | (*codep << 6); + *state = next[*state]; } + return *state; } -static void history_previous(rline_context_t * context) { - if (rline_scroll == 0) { - memcpy(rline_temp, context->buffer, strlen(context->buffer) + 1); +#define ENTER_KEY '\n' +#define BACKSPACE_KEY 0x08 +#define DELETE_KEY 0x7F +#define MINIMUM_SIZE 10 + +/** + * Same structures as in bim. + * A single character has: + * - A codepoint (Unicode) of up to 21 bits. + * - Flags for syntax highlighting. + * - A display width for rendering. + */ +typedef struct { + uint32_t display_width:4; + uint32_t flags:7; + uint32_t codepoint:21; +} __attribute__((packed)) char_t; + +/** + * We generally only have the one line, + * but this matches bim for compatibility reasons. + */ +typedef struct { + int available; + int actual; + int istate; + char_t text[]; +} line_t; + +/** + * We operate on a single line of text. + * Maybe we can expand this in the future + * for continuations of edits such as when + * a quote is unclosed? + */ +static line_t * the_line = NULL; + +/** + * Line editor state + */ +static int loading = 0; +static int column = 0; +static int offset = 0; +static int width = 0; +static int show_right_side = 0; +static int show_left_side = 0; +static int prompt_width_calc = 0; +static int buf_size_max = 0; + +/** + * Prompt strings. + * Defaults to just a "> " prompt with no right side. + * Support for right side prompts is important + * for the ToaruOS shell. + */ +static int prompt_width = 2; +static char * prompt = "> "; +static int prompt_right_width = 0; +static char * prompt_right = ""; + +int rline_exp_set_prompts(char * left, char * right, int left_width, int right_width) { + prompt = left; + prompt_right = right; + prompt_width = left_width; + prompt_right_width = right_width; + return 0; +} + +/** + * Extra shell commands to highlight as keywords. + * These are basically just copied from the + * shell's tab completion database on startup. + */ +static char ** shell_commands = {0}; +static int shell_commands_len = 0; + +int rline_exp_set_shell_commands(char ** cmds, int len) { + shell_commands = cmds; + shell_commands_len = len; + return 0; +} + +/** + * Tab completion callback. + * Compatible with the original rline version. + */ +static rline_callback_t tab_complete_func = NULL; + +int rline_exp_set_tab_complete_func(rline_callback_t func) { + tab_complete_func = func; + return 0; +} + +static int getch(int timeout) { +#ifndef _WIN32 + return fgetc(stdin); +#else + static int bytesRead = 0; + static char buf8[8]; + static uint8_t * b; + + if (bytesRead) { + bytesRead--; + return *(b++); } - if (rline_scroll < rline_history_count) { - rline_scroll++; - for (int i = 0; i < (int)strlen(context->buffer); ++i) { - printf("\010 \010"); - } - char * h = rline_history_prev(rline_scroll); - memcpy(context->buffer, h, strlen(h) + 1); - printf("\033[u%s\033[K", h); - fflush(stdout); + + DWORD dwRead; + uint16_t buf16[8] = {0}; + if (ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE),buf16,2,&dwRead,NULL)) { + int r = WideCharToMultiByte(CP_UTF8, 0, buf16, -1, buf8, 8, 0, 0); + if (r > 1 && buf8[r-1] == '\0') r--; + b = (uint8_t*)buf8; + bytesRead = r - 1; + return *(b++); + } else { + fprintf(stderr, "error on console read\n"); + return -1; } - context->collected = strlen(context->buffer); - context->offset = context->collected; +#endif } -static void history_next(rline_context_t * context) { - if (rline_scroll > 1) { - rline_scroll--; - for (int i = 0; i < (int)strlen(context->buffer); ++i) { - printf("\010 \010"); - } - char * h = rline_history_prev(rline_scroll); - memcpy(context->buffer, h, strlen(h) + 1); - printf("%s", h); - fflush(stdout); - } else if (rline_scroll == 1) { - for (int i = 0; i < (int)strlen(context->buffer); ++i) { - printf("\010 \010"); +/** + * Convert from Unicode string to utf-8. + */ +static int to_eight(uint32_t codepoint, char * out) { + memset(out, 0x00, 7); + + if (codepoint < 0x0080) { + out[0] = (char)codepoint; + } else if (codepoint < 0x0800) { + out[0] = 0xC0 | (codepoint >> 6); + out[1] = 0x80 | (codepoint & 0x3F); + } else if (codepoint < 0x10000) { + out[0] = 0xE0 | (codepoint >> 12); + out[1] = 0x80 | ((codepoint >> 6) & 0x3F); + out[2] = 0x80 | (codepoint & 0x3F); + } else if (codepoint < 0x200000) { + out[0] = 0xF0 | (codepoint >> 18); + out[1] = 0x80 | ((codepoint >> 12) & 0x3F); + out[2] = 0x80 | ((codepoint >> 6) & 0x3F); + out[3] = 0x80 | ((codepoint) & 0x3F); + } else if (codepoint < 0x4000000) { + out[0] = 0xF8 | (codepoint >> 24); + out[1] = 0x80 | (codepoint >> 18); + out[2] = 0x80 | ((codepoint >> 12) & 0x3F); + out[3] = 0x80 | ((codepoint >> 6) & 0x3F); + out[4] = 0x80 | ((codepoint) & 0x3F); + } else { + out[0] = 0xF8 | (codepoint >> 30); + out[1] = 0x80 | ((codepoint >> 24) & 0x3F); + out[2] = 0x80 | ((codepoint >> 18) & 0x3F); + out[3] = 0x80 | ((codepoint >> 12) & 0x3F); + out[4] = 0x80 | ((codepoint >> 6) & 0x3F); + out[5] = 0x80 | ((codepoint) & 0x3F); + } + + return strlen(out); +} + +/** + * Obtain codepoint display width. + * + * This is copied from bim. Supports a few useful + * things like rendering escapes as codepoints. + */ +static int codepoint_width(int codepoint) { + if (codepoint == '\t') { + return 1; /* Recalculate later */ + } + if (codepoint < 32) { + /* We render these as ^@ */ + return 2; + } + if (codepoint == 0x7F) { + /* Renders as ^? */ + return 2; + } + if (codepoint > 0x7f && codepoint < 0xa0) { + /* Upper control bytes */ + return 4; + } + if (codepoint == 0xa0) { + /* Non-breaking space _ */ + return 1; + } + /* Skip wcwidth for anything under 256 */ + if (codepoint > 256) { + /* Higher codepoints may be wider (eg. Japanese) */ + int out = wcwidth(codepoint); + if (out >= 1) return out; + /* Invalid character, render as [U+ABCD] or [U+ABCDEF] */ + return (codepoint < 0x10000) ? 8 : 10; + } + return 1; +} + +void recalculate_tabs(line_t * line) { + int j = 0; + for (int i = 0; i < line->actual; ++i) { + if (line->text[i].codepoint == '\t') { + line->text[i].display_width = 4 - (j % 4); } - rline_scroll = 0; - memcpy(context->buffer, rline_temp, strlen(rline_temp) + 1); - printf("\033[u%s\033[K", context->buffer); - fflush(stdout); + j += line->text[i].display_width; } - context->collected = strlen(context->buffer); - context->offset = context->collected; } /** - * Insert characters at the current cursor offset. + * Color themes have also been copied from bim. + * + * Slimmed down to only the ones we use for syntax + * highlighting; the UI colors have been removed. */ -void rline_insert(rline_context_t * context, const char * what) { - size_t insertion_length = strlen(what); +static const char * COLOR_FG = "@9"; +static const char * COLOR_BG = "@9"; +static const char * COLOR_ALT_FG = "@5"; +static const char * COLOR_ALT_BG = "@9"; +static const char * COLOR_KEYWORD = "@4"; +static const char * COLOR_STRING = "@2"; +static const char * COLOR_COMMENT = "@5"; +static const char * COLOR_TYPE = "@3"; +static const char * COLOR_PRAGMA = "@1"; +static const char * COLOR_NUMERAL = "@1"; +static const char * COLOR_RED = "@1"; +static const char * COLOR_GREEN = "@2"; +static const char * COLOR_ESCAPE = "@2"; +static const char * COLOR_SEARCH_FG = "@0"; +static const char * COLOR_SEARCH_BG = "@3"; - if (context->collected + (int)insertion_length > context->requested) { - insertion_length = context->requested - context->collected; +/** + * Themes are selected from the $RLINE_THEME + * environment variable. + */ +static void rline_exp_load_colorscheme_default(void) { + COLOR_FG = "@9"; + COLOR_BG = "@9"; + COLOR_ALT_FG = "@10"; + COLOR_ALT_BG = "@9"; + COLOR_KEYWORD = "@14"; + COLOR_STRING = "@2"; + COLOR_COMMENT = "@10"; + COLOR_TYPE = "@3"; + COLOR_PRAGMA = "@1"; + COLOR_NUMERAL = "@1"; + COLOR_RED = "@1"; + COLOR_GREEN = "@2"; + COLOR_ESCAPE = "@12"; + COLOR_SEARCH_FG = "@0"; + COLOR_SEARCH_BG = "@13"; +} + +static void rline_exp_load_colorscheme_sunsmoke(void) { + COLOR_FG = "2;230;230;230"; + COLOR_BG = "@9"; + COLOR_ALT_FG = "2;122;122;122"; + COLOR_ALT_BG = "2;46;43;46"; + COLOR_KEYWORD = "2;51;162;230"; + COLOR_STRING = "2;72;176;72"; + COLOR_COMMENT = "2;158;153;129;3"; + COLOR_TYPE = "2;230;206;110"; + COLOR_PRAGMA = "2;194;70;54"; + COLOR_NUMERAL = "2;230;43;127"; + COLOR_RED = "2;222;53;53"; + COLOR_GREEN = "2;55;167;0"; + COLOR_ESCAPE = "2;113;203;173"; + COLOR_SEARCH_FG = "5;234"; + COLOR_SEARCH_BG = "5;226"; +} + +/** + * Syntax highlighting flags. + */ +#define FLAG_NONE 0 +#define FLAG_KEYWORD 1 +#define FLAG_STRING 2 +#define FLAG_COMMENT 3 +#define FLAG_TYPE 4 +#define FLAG_PRAGMA 5 +#define FLAG_NUMERAL 6 +#define FLAG_ERROR 7 +#define FLAG_DIFFPLUS 8 +#define FLAG_DIFFMINUS 9 +#define FLAG_NOTICE 10 +#define FLAG_BOLD 11 +#define FLAG_LINK 12 +#define FLAG_ESCAPE 13 + +#define FLAG_SELECT (1 << 5) + +struct syntax_state { + line_t * line; + int line_no; + int state; + int i; +}; + +#define paint(length, flag) do { for (int i = 0; i < (length) && state->i < state->line->actual; i++, state->i++) { state->line->text[state->i].flags = (flag); } } while (0) +#define charat() (state->i < state->line->actual ? state->line->text[(state->i)].codepoint : -1) +#define nextchar() (state->i + 1 < state->line->actual ? state->line->text[(state->i+1)].codepoint : -1) +#define lastchar() (state->i - 1 >= 0 ? state->line->text[(state->i-1)].codepoint : -1) +#define skip() (state->i++) +#define charrel(x) (state->i + (x) < state->line->actual ? state->line->text[(state->i+(x))].codepoint : -1) + +/** + * Match and paint a single keyword. Returns 1 if the keyword was matched and 0 otherwise, + * so it can be used for prefix checking for things that need further special handling. + */ +int match_and_paint(struct syntax_state * state, const char * keyword, int flag, int (*keyword_qualifier)(int c)) { + if (keyword_qualifier(lastchar())) return 0; + if (!keyword_qualifier(charat())) return 0; + int i = state->i; + int slen = 0; + while (i < state->line->actual || *keyword == '\0') { + if (*keyword == '\0' && (i >= state->line->actual || !keyword_qualifier(state->line->text[i].codepoint))) { + for (int j = 0; j < slen; ++j) { + paint(1, flag); + } + return 1; + } + if (*keyword != state->line->text[i].codepoint) return 0; + + i++; + keyword++; + slen++; } + return 0; +} - /* Move */ - memmove(&context->buffer[context->offset + insertion_length], &context->buffer[context->offset], context->collected - context->offset); - memcpy(&context->buffer[context->offset], what, insertion_length); - context->collected += insertion_length; - context->offset += insertion_length; +/** + * Find keywords from a list and paint them, assuming they aren't in the middle of other words. + * Returns 1 if a keyword from the last was found, otherwise 0. + */ +int find_keywords(struct syntax_state * state, char ** keywords, int flag, int (*keyword_qualifier)(int c)) { + if (keyword_qualifier(lastchar())) return 0; + if (!keyword_qualifier(charat())) return 0; + for (char ** keyword = keywords; *keyword; ++keyword) { + int d = 0; + while (state->i + d < state->line->actual && state->line->text[state->i+d].codepoint == (*keyword)[d]) d++; + if ((*keyword)[d] == '\0' && (state->i + d >= state->line->actual || !keyword_qualifier(state->line->text[state->i+d].codepoint))) { + paint((int)strlen(*keyword), flag); + return 1; + } + } + + return 0; +} + +/** + * This is a basic character matcher for "keyword" characters. + */ +int simple_keyword_qualifier(int c) { + return isalnum(c) || (c == '_'); } -static rline_callbacks_t _rline_null_callbacks = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}; -int rline(char * buffer, int buf_size, rline_callbacks_t * callbacks) { - /* Initialize context */ - rline_context_t context = { - buffer, - callbacks, - 0, - buf_size, - 0, - 0, - 0, - 0, - 0, - }; +int common_comment_buzzwords(struct syntax_state * state) { + if (match_and_paint(state, "TODO", FLAG_NOTICE, simple_keyword_qualifier)) { return 1; } + else if (match_and_paint(state, "XXX", FLAG_NOTICE, simple_keyword_qualifier)) { return 1; } + else if (match_and_paint(state, "FIXME", FLAG_ERROR, simple_keyword_qualifier)) { return 1; } + return 0; +} - if (!callbacks) { - callbacks = &_rline_null_callbacks; +/** + * Paint a comment until end of line, assumes this comment can not continue. + * (Some languages have comments that can continue with a \ - don't use this!) + * Assumes you've already painted your comment start characters. + */ +int paint_comment(struct syntax_state * state) { + while (charat() != -1) { + if (common_comment_buzzwords(state)) continue; + else { paint(1, FLAG_COMMENT); } } + return -1; +} - set_unbuffered(); +int c_keyword_qualifier(int c) { + return isalnum(c) || (c == '_'); +} - printf("\033[s"); - fflush(stdout); +void paintNHex(struct syntax_state * state, int n) { + paint(2, FLAG_ESCAPE); + /* Why is my FLAG_ERROR not valid in rline? */ + for (int i = 0; i < n; ++i) { + paint(1, isxdigit(charat()) ? FLAG_ESCAPE : FLAG_DIFFMINUS); + } +} - key_event_state_t kbd_state = {0}; - - /* Read keys */ - while ((context.collected < context.requested) && (!context.newline)) { - uint32_t key_sym = kbd_key(&kbd_state, fgetc(stdin)); - if (key_sym == KEY_NONE) continue; - if (key_sym != '\t') context.tabbed = 0; - switch (key_sym) { - case KEY_CTRL_C: - printf("^C\n"); - context.buffer[0] = '\0'; - set_buffered(); - return 0; - case KEY_CTRL_R: - if (callbacks->rev_search) { - callbacks->rev_search(&context); - } else { - rline_reverse_search(&context); - } - if (context.cancel) { - context.cancel = 0; - continue; - } else { - set_buffered(); - return context.collected; - } - case KEY_ARROW_UP: - case KEY_CTRL_P: - if (callbacks->key_up) { - callbacks->key_up(&context); - } else { - history_previous(&context); - } - continue; - case KEY_ARROW_DOWN: - case KEY_CTRL_N: - if (callbacks->key_down) { - callbacks->key_down(&context); - } else { - history_next(&context); - } - continue; - case KEY_CTRL_ARROW_RIGHT: - while (context.offset < context.collected && context.buffer[context.offset] == ' ') { - context.offset++; - printf("\033[C"); - } - while (context.offset < context.collected) { - context.offset++; - printf("\033[C"); - if (context.buffer[context.offset] == ' ') break; - } - fflush(stdout); - continue; - case KEY_CTRL_ARROW_LEFT: - if (context.offset == 0) continue; - context.offset--; - printf("\033[D"); - while (context.offset && context.buffer[context.offset] == ' ') { - context.offset--; - printf("\033[D"); - } - while (context.offset > 0) { - if (context.buffer[context.offset-1] == ' ') break; - context.offset--; - printf("\033[D"); - } - fflush(stdout); - continue; - case KEY_ARROW_RIGHT: - if (callbacks->key_right) { - callbacks->key_right(&context); - } else { - if (context.offset < context.collected) { - printf("\033[C"); - fflush(stdout); - context.offset++; - } - } - continue; - case KEY_ARROW_LEFT: - if (callbacks->key_left) { - callbacks->key_left(&context); - } else { - if (context.offset > 0) { - printf("\033[D"); - fflush(stdout); - context.offset--; - } - } - continue; - case KEY_CTRL_A: - case KEY_HOME: - while (context.offset > 0) { - printf("\033[D"); - context.offset--; - } - fflush(stdout); - continue; - case KEY_CTRL_E: - case KEY_END: - while (context.offset < context.collected) { - printf("\033[C"); - context.offset++; - } - fflush(stdout); - continue; - case KEY_CTRL_K: - context.collected = context.offset; - printf("\033[K"); - fflush(stdout); - continue; - case KEY_CTRL_D: - if (context.collected == 0) { - printf(rline_exit_string); - sprintf(context.buffer, rline_exit_string); - set_buffered(); - return strlen(context.buffer); - } - /* Intentional fallthrough */ - case KEY_DEL: - if (context.collected) { - if (context.offset == context.collected) { - continue; - } - int remaining = context.collected - context.offset; - for (int i = 1; i < remaining; ++i) { - printf("%c", context.buffer[context.offset + i]); - context.buffer[context.offset + i - 1] = context.buffer[context.offset + i]; - } - printf(" "); - for (int i = 0; i < remaining; ++i) { - printf("\033[D"); - } - context.collected--; - fflush(stdout); - } - continue; - case KEY_BACKSPACE: - case 0x7F: /* delete */ - if (context.collected) { - int should_redraw = 0; - if (!context.offset) { - continue; - } - printf("\010 \010"); - if (context.buffer[context.offset-1] == '\t') { - should_redraw = 1; - } - if (context.offset != context.collected) { - int remaining = context.collected - context.offset; - for (int i = 0; i < remaining; ++i) { - printf("%c", context.buffer[context.offset + i]); - context.buffer[context.offset + i - 1] = context.buffer[context.offset + i]; - } - printf(" "); - for (int i = 0; i < remaining + 1; ++i) { - printf("\033[D"); - } - context.offset--; - context.collected--; - } else { - context.buffer[--context.collected] = '\0'; - context.offset--; - } - if (should_redraw) { - rline_redraw_clean(&context); - } - fflush(stdout); - } - continue; - case KEY_CTRL_L: /* ^L: Clear Screen, redraw prompt and buffer */ - printf("\033[H\033[2J"); - fflush(stdout); - /* Flush before yielding control to potentially foreign environment. */ - if (callbacks->redraw_prompt) { - callbacks->redraw_prompt(&context); - } - printf("\033[s"); - rline_redraw_clean(&context); - continue; - case KEY_CTRL_W: - /* - * Erase word before cursor. - * If the character before the cursor is a space, delete it. - * Continue deleting until the previous character is a space. - */ - if (context.collected) { - if (!context.offset) { - continue; +void paint_krk_string(struct syntax_state * state, int type) { + /* Assumes you came in from a check of charat() == '"' */ + paint(1, FLAG_STRING); + while (charat() != -1) { + if (charat() == '\\' && nextchar() == type) { + paint(2, FLAG_ESCAPE); + } else if (charat() == type) { + paint(1, FLAG_STRING); + return; + } else if (charat() == '\\') { + if (nextchar() == 'x') { + paintNHex(state, 2); + } else if (nextchar() == 'u') { + paintNHex(state, 4); + } else if (nextchar() == 'U') { + paintNHex(state, 8); + } else if (nextchar() >= '0' && nextchar() <= '7') { + paint(2, FLAG_ESCAPE); + if (charat() >= '0' && charat() <= '7') { + paint(1, FLAG_ESCAPE); + if (charat() >= '0' && charat() <= '7') { + paint(1, FLAG_ESCAPE); } - do { - printf("\010 \010"); - if (context.offset != context.collected) { - int remaining = context.collected - context.offset; - for (int i = 0; i < remaining; ++i) { - printf("%c", context.buffer[context.offset + i]); - context.buffer[context.offset + i - 1] = context.buffer[context.offset + i]; - } - printf(" "); - for (int i = 0; i < remaining + 1; ++i) { - printf("\033[D"); - } - context.offset--; - context.collected--; - } else { - context.buffer[--context.collected] = '\0'; - context.offset--; - } - } while ((context.offset) && (context.buffer[context.offset-1] != ' ')); - fflush(stdout); - } - continue; - case '\t': - if (callbacks->tab_complete) { - callbacks->tab_complete(&context); - } - continue; - case '\n': - while (context.offset < context.collected) { - printf("\033[C"); - context.offset++; - } - if (context.collected < context.requested) { - context.buffer[context.collected] = '\n'; - context.buffer[++context.collected] = '\0'; - context.offset++; } - printf("\n"); - fflush(stdout); - context.newline = 1; - continue; - } - if (context.offset != context.collected) { - for (int i = context.collected; i > context.offset; --i) { - context.buffer[i] = context.buffer[i-1]; + } else { + paint(2, FLAG_ESCAPE); } - if (context.collected < context.requested) { - context.buffer[context.offset] = (char)key_sym; - context.buffer[++context.collected] = '\0'; - context.offset++; - } - for (int i = context.offset - 1; i < context.collected; ++i) { - printf("%c", context.buffer[i]); - } - for (int i = context.offset; i < context.collected; ++i) { - printf("\033[D"); - } - fflush(stdout); } else { - printf("%c", (char)key_sym); - if (context.collected < context.requested) { - context.buffer[context.collected] = (char)key_sym; - context.buffer[++context.collected] = '\0'; - context.offset++; - } - fflush(stdout); + paint(1, FLAG_STRING); } } - - /* Cap that with a null */ - context.buffer[context.collected] = '\0'; - set_buffered(); - return context.collected; } +char * syn_krk_keywords[] = { + "and","class","def","else","for","if","in","import","del", + "let","not","or","return","while","try","except","raise", + "continue","break","as","from","elif","lambda","with","is", + "pass","assert","yield","finally", + NULL +}; -static char * last_prompt = NULL; -static void redraw_prompt(rline_context_t * c) { - (void)c; - printf("%s", last_prompt); - fflush(stdout); - return; +char * syn_krk_types[] = { + /* built-in functions */ + "self", "super", /* implicit in a class method */ + "len", "str", "int", "float", "dir", "repr", /* global functions from __builtins__ */ + "list","dict","range", /* builtin classes */ + "object","exception","isinstance","type","tuple","reversed", + "print","set","any","all","bool","ord","chr","hex","oct","filter", + "sorted","bytes","getattr","sum","min","max","id","hash","map","bin", + "enumerate","zip","setattr","property","staticmethod","classmethod", + NULL +}; + +char * syn_krk_special[] = { + "True","False","None", + /* Exception names */ + NULL +}; + +char * syn_krk_exception[] = { + "TypeError","ArgumentError","IndexError","KeyError","AttributeError", + "NameError","ImportError","IOError","ValueError","KeyboardInterrupt", + "ZeroDivisionError","SyntaxError","Exception", + NULL +}; + +int paint_krk_numeral(struct syntax_state * state) { + if (charat() == '0' && (nextchar() == 'x' || nextchar() == 'X')) { + paint(2, FLAG_NUMERAL); + while (isxdigit(charat())) paint(1, FLAG_NUMERAL); + } else if (charat() == '0' && (nextchar() == 'o' || nextchar() == 'O')) { + paint(2, FLAG_NUMERAL); + while (charat() >= '0' && charat() <= '7') paint(1, FLAG_NUMERAL); + } else if (charat() == '0' && (nextchar() == 'b' || nextchar() == 'B')) { + paint(2, FLAG_NUMERAL); + while (charat() == '0' || charat() == '1') paint(1, FLAG_NUMERAL); + } else { + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + if (charat() == '.' && isdigit(nextchar())) { + paint(1, FLAG_NUMERAL); + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + } + } + return 0; +} + +int paint_krk_triple_string(struct syntax_state * state, int type) { + while (charat() != -1) { + if (charat() == type) { + paint(1, FLAG_STRING); + if (charat() == type && nextchar() == type) { + paint(2, FLAG_STRING); + return 0; + } + } else { + paint(1, FLAG_STRING); + } + } + return (type == '"') ? 1 : 2; /* continues */ } -static void insert_tab(rline_context_t * c) { - rline_insert(c, "\t"); - rline_redraw_clean(c); + +int syn_krk_calculate(struct syntax_state * state) { + switch (state->state) { + case -1: + case 0: + if (charat() == '#') { + paint_comment(state); + } else if (charat() == '@') { + paint(1, FLAG_TYPE); + while (c_keyword_qualifier(charat())) paint(1, FLAG_TYPE); + return 0; + } else if (charat() == '"' || charat() == '\'') { + if (nextchar() == charat() && charrel(2) == charat()) { + int type = charat(); + paint(3, FLAG_STRING); + return paint_krk_triple_string(state, type); + } else { + paint_krk_string(state, charat()); + } + return 0; + } else if (find_keywords(state, syn_krk_keywords, FLAG_KEYWORD, c_keyword_qualifier)) { + return 0; + } else if (lastchar() != '.' && find_keywords(state, syn_krk_types, FLAG_TYPE, c_keyword_qualifier)) { + return 0; + } else if (find_keywords(state, syn_krk_special, FLAG_NUMERAL, c_keyword_qualifier)) { + return 0; + } else if (find_keywords(state, syn_krk_exception, FLAG_PRAGMA, c_keyword_qualifier)) { + return 0; + } else if (!c_keyword_qualifier(lastchar()) && isdigit(charat())) { + paint_krk_numeral(state); + return 0; + } else if (charat() != -1) { + skip(); + return 0; + } + break; + case 1: + return paint_krk_triple_string(state, '"'); + case 2: + return paint_krk_triple_string(state, '\''); + } + return -1; } -void * rline_for_python(void * _stdin, void * _stdout, char * prompt) { - last_prompt = prompt; +char * syn_krk_dbg_commands[] = { + "s", "skip", + "c", "continue", + "q", "quit", + "e", "enable", + "d", "disable", + "r", "remove", + "bt", "backtrace", + "break", + "abort", + "help", + NULL, +}; - rline_callbacks_t callbacks = { - insert_tab, redraw_prompt, NULL, - NULL, NULL, NULL, NULL, NULL - }; +char * syn_krk_dbg_info_types[] = { + "breakpoints", + NULL, +}; +int syn_krk_dbg_calculate(struct syntax_state * state) { + if (state->state < 1) { + if (state->i == 0) { + if (match_and_paint(state, "p", FLAG_KEYWORD, c_keyword_qualifier) || + match_and_paint(state, "print", FLAG_KEYWORD, c_keyword_qualifier)) { + while (1) { + int result = syn_krk_calculate(state); + if (result == 0) continue; + if (result == -1) return -1; + return result + 1; + } + } else if (match_and_paint(state,"info", FLAG_KEYWORD, c_keyword_qualifier) || + match_and_paint(state,"i", FLAG_KEYWORD, c_keyword_qualifier)) { + skip(); + find_keywords(state,syn_krk_dbg_info_types, FLAG_TYPE, c_keyword_qualifier); + return -1; + } else if (find_keywords(state, syn_krk_dbg_commands, FLAG_KEYWORD, c_keyword_qualifier)) { + return 0; + } + } + return -1; + } else { + state->state -= 1; + return syn_krk_calculate(state) + 1; + } +} - redraw_prompt(NULL); - char * buf = malloc(1024); - memset(buf, 0, 1024); - rline(buf, 1024, &callbacks); - rline_history_insert(strdup(buf)); - rline_scroll = 0; +#ifdef __toaru__ +int esh_variable_qualifier(int c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '_'); +} - return buf; +int paint_esh_variable(struct syntax_state * state) { + if (charat() == '{') { + paint(1, FLAG_TYPE); + while (charat() != '}' && charat() != -1) paint(1, FLAG_TYPE); + if (charat() == '}') paint(1, FLAG_TYPE); + } else { + if (charat() == '?' || charat() == '$' || charat() == '#') { + paint(1, FLAG_TYPE); + } else { + while (esh_variable_qualifier(charat())) paint(1, FLAG_TYPE); + } + } + return 0; +} + +int paint_esh_string(struct syntax_state * state) { + int last = -1; + while (charat() != -1) { + if (last != '\\' && charat() == '"') { + paint(1, FLAG_STRING); + return 0; + } else if (charat() == '$') { + paint(1, FLAG_TYPE); + paint_esh_variable(state); + last = -1; + } else if (charat() != -1) { + last = charat(); + paint(1, FLAG_STRING); + } + } + return 2; +} + +int paint_esh_single_string(struct syntax_state * state) { + int last = -1; + while (charat() != -1) { + if (last != '\\' && charat() == '\'') { + paint(1, FLAG_STRING); + return 0; + } else if (charat() != -1) { + last = charat(); + paint(1, FLAG_STRING); + } + } + return 1; +} + +int esh_keyword_qualifier(int c) { + return (isalnum(c) || c == '?' || c == '_' || c == '-'); /* technically anything that isn't a space should qualify... */ +} + +char * esh_keywords[] = { + "cd","exit","export","help","history","if","empty?", + "equals?","return","export-cmd","source","exec","not","while", + "then","else","echo", + NULL +}; + +int syn_esh_calculate(struct syntax_state * state) { + if (state->state == 1) { + return paint_esh_single_string(state); + } else if (state->state == 2) { + return paint_esh_string(state); + } + if (charat() == '#') { + while (charat() != -1) { + if (common_comment_buzzwords(state)) continue; + else paint(1, FLAG_COMMENT); + } + return -1; + } else if (charat() == '$') { + paint(1, FLAG_TYPE); + paint_esh_variable(state); + return 0; + } else if (charat() == '\'') { + paint(1, FLAG_STRING); + return paint_esh_single_string(state); + } else if (charat() == '"') { + paint(1, FLAG_STRING); + return paint_esh_string(state); + } else if (match_and_paint(state, "export", FLAG_KEYWORD, esh_keyword_qualifier)) { + while (charat() == ' ') skip(); + while (esh_keyword_qualifier(charat())) paint(1, FLAG_TYPE); + return 0; + } else if (match_and_paint(state, "export-cmd", FLAG_KEYWORD, esh_keyword_qualifier)) { + while (charat() == ' ') skip(); + while (esh_keyword_qualifier(charat())) paint(1, FLAG_TYPE); + return 0; + } else if (find_keywords(state, esh_keywords, FLAG_KEYWORD, esh_keyword_qualifier)) { + return 0; + } else if (find_keywords(state, shell_commands, FLAG_KEYWORD, esh_keyword_qualifier)) { + return 0; + } else if (isdigit(charat())) { + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + return 0; + } else if (charat() != -1) { + skip(); + return 0; + } + return -1; +} + +char * syn_py_keywords[] = { + "class","def","return","del","if","else","elif","for","while","continue", + "break","assert","as","and","or","except","finally","from","global", + "import","in","is","lambda","with","nonlocal","not","pass","raise","try","yield", + NULL +}; + +char * syn_py_types[] = { + /* built-in functions */ + "abs","all","any","ascii","bin","bool","breakpoint","bytes", + "bytearray","callable","compile","complex","delattr","chr", + "dict","dir","divmod","enumerate","eval","exec","filter","float", + "format","frozenset","getattr","globals","hasattr","hash","help", + "hex","id","input","int","isinstance","issubclass","iter","len", + "list","locals","map","max","memoryview","min","next","object", + "oct","open","ord","pow","print","property","range","repr","reverse", + "round","set","setattr","slice","sorted","staticmethod","str","sum", + "super","tuple","type","vars","zip", + NULL +}; + +char * syn_py_special[] = { + "True","False","None", + NULL +}; + +int paint_py_triple_double(struct syntax_state * state) { + while (charat() != -1) { + if (charat() == '"') { + paint(1, FLAG_STRING); + if (charat() == '"' && nextchar() == '"') { + paint(2, FLAG_STRING); + return 0; + } + } else { + paint(1, FLAG_STRING); + } + } + return 1; /* continues */ +} + +int paint_py_triple_single(struct syntax_state * state) { + while (charat() != -1) { + if (charat() == '\'') { + paint(1, FLAG_STRING); + if (charat() == '\'' && nextchar() == '\'') { + paint(2, FLAG_STRING); + return 0; + } + } else { + paint(1, FLAG_STRING); + } + } + return 2; /* continues */ +} + +int paint_py_single_string(struct syntax_state * state) { + paint(1, FLAG_STRING); + while (charat() != -1) { + if (charat() == '\\' && nextchar() == '\'') { + paint(2, FLAG_ESCAPE); + } else if (charat() == '\'') { + paint(1, FLAG_STRING); + return 0; + } else if (charat() == '\\') { + paint(2, FLAG_ESCAPE); + } else { + paint(1, FLAG_STRING); + } + } + return 0; +} + +int paint_py_numeral(struct syntax_state * state) { + if (charat() == '0' && (nextchar() == 'x' || nextchar() == 'X')) { + paint(2, FLAG_NUMERAL); + while (isxdigit(charat())) paint(1, FLAG_NUMERAL); + } else if (charat() == '0' && nextchar() == '.') { + paint(2, FLAG_NUMERAL); + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + if ((charat() == '+' || charat() == '-') && (nextchar() == 'e' || nextchar() == 'E')) { + paint(2, FLAG_NUMERAL); + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + } else if (charat() == 'e' || charat() == 'E') { + paint(1, FLAG_NUMERAL); + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + } + if (charat() == 'j') paint(1, FLAG_NUMERAL); + return 0; + } else { + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + if (charat() == '.') { + paint(1, FLAG_NUMERAL); + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + if ((charat() == '+' || charat() == '-') && (nextchar() == 'e' || nextchar() == 'E')) { + paint(2, FLAG_NUMERAL); + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + } else if (charat() == 'e' || charat() == 'E') { + paint(1, FLAG_NUMERAL); + while (isdigit(charat())) paint(1, FLAG_NUMERAL); + } + if (charat() == 'j') paint(1, FLAG_NUMERAL); + return 0; + } + if (charat() == 'j') paint(1, FLAG_NUMERAL); + } + while (charat() == 'l' || charat() == 'L') paint(1, FLAG_NUMERAL); + return 0; +} + +void paint_py_format_string(struct syntax_state * state, char type) { + paint(1, FLAG_STRING); + while (charat() != -1) { + if (charat() == '\\' && nextchar() == type) { + paint(2, FLAG_ESCAPE); + } else if (charat() == type) { + paint(1, FLAG_STRING); + return; + } else if (charat() == '\\') { + paint(2, FLAG_ESCAPE); + } else if (charat() == '{') { + paint(1, FLAG_NUMERAL); + if (charat() == '}') { + state->i--; + paint(2, FLAG_ERROR); /* Can't do that. */ + } else { + while (charat() != -1 && charat() != '}') { + paint(1, FLAG_NUMERAL); + } + paint(1, FLAG_NUMERAL); + } + } else { + paint(1, FLAG_STRING); + } + } +} + +void paint_simple_string(struct syntax_state * state) { + /* Assumes you came in from a check of charat() == '"' */ + paint(1, FLAG_STRING); + while (charat() != -1) { + if (charat() == '\\' && nextchar() == '"') { + paint(2, FLAG_ESCAPE); + } else if (charat() == '"') { + paint(1, FLAG_STRING); + return; + } else if (charat() == '\\') { + paint(2, FLAG_ESCAPE); + } else { + paint(1, FLAG_STRING); + } + } +} + +int syn_py_calculate(struct syntax_state * state) { + switch (state->state) { + case -1: + case 0: + if (charat() == '#') { + paint_comment(state); + } else if (state->i == 0 && match_and_paint(state, "import", FLAG_PRAGMA, c_keyword_qualifier)) { + return 0; + } else if (charat() == '@') { + paint(1, FLAG_PRAGMA); + while (c_keyword_qualifier(charat())) paint(1, FLAG_PRAGMA); + return 0; + } else if (charat() == '"') { + if (nextchar() == '"' && charrel(2) == '"') { + paint(3, FLAG_STRING); + return paint_py_triple_double(state); + } else if (lastchar() == 'f') { + /* I don't like backtracking like this, but it makes this parse easier */ + state->i--; + paint(1,FLAG_TYPE); + paint_py_format_string(state,'"'); + return 0; + } else { + paint_simple_string(state); + return 0; + } + } else if (find_keywords(state, syn_py_keywords, FLAG_KEYWORD, c_keyword_qualifier)) { + return 0; + } else if (lastchar() != '.' && find_keywords(state, syn_py_types, FLAG_TYPE, c_keyword_qualifier)) { + return 0; + } else if (find_keywords(state, syn_py_special, FLAG_NUMERAL, c_keyword_qualifier)) { + return 0; + } else if (charat() == '\'') { + if (nextchar() == '\'' && charrel(2) == '\'') { + paint(3, FLAG_STRING); + return paint_py_triple_single(state); + } else if (lastchar() == 'f') { + /* I don't like backtracking like this, but it makes this parse easier */ + state->i--; + paint(1,FLAG_TYPE); + paint_py_format_string(state,'\''); + return 0; + } else { + return paint_py_single_string(state); + } + } else if (!c_keyword_qualifier(lastchar()) && isdigit(charat())) { + paint_py_numeral(state); + return 0; + } else if (charat() != -1) { + skip(); + return 0; + } + break; + case 1: /* multiline """ string */ + return paint_py_triple_double(state); + case 2: /* multiline ''' string */ + return paint_py_triple_single(state); + } + return -1; +} + +#endif + +void rline_redraw(rline_context_t * context) { + if (context->quiet) return; + printf("\033[u%s\033[K", context->buffer); + for (int i = context->offset; i < context->collected; ++i) { + printf("\033[D"); + } + fflush(stdout); +} + + +/** + * Convert syntax hilighting flag to color code + */ +static const char * flag_to_color(int _flag) { + int flag = _flag & 0xF; + switch (flag) { + case FLAG_KEYWORD: + return COLOR_KEYWORD; + case FLAG_STRING: + return COLOR_STRING; + case FLAG_COMMENT: + return COLOR_COMMENT; + case FLAG_TYPE: + return COLOR_TYPE; + case FLAG_NUMERAL: + return COLOR_NUMERAL; + case FLAG_PRAGMA: + return COLOR_PRAGMA; + case FLAG_DIFFPLUS: + return COLOR_GREEN; + case FLAG_DIFFMINUS: + return COLOR_RED; +// case FLAG_BOLD: +// return COLOR_BOLD; +// case FLAG_LINK: +// return COLOR_LINK; + case FLAG_ESCAPE: + return COLOR_ESCAPE; + default: + return COLOR_FG; + } +} + +struct syntax_definition { + char * name; + int (*calculate)(struct syntax_state *); + int tabIndents; +} syntaxes[] = { + {"krk",syn_krk_calculate, 1}, + {"krk-dbg",syn_krk_dbg_calculate, 1}, +#ifdef __toaru__ + {"python",syn_py_calculate, 1}, + {"esh",syn_esh_calculate, 0}, +#endif + {NULL, NULL, 0}, +}; + +static struct syntax_definition * syntax; + +int rline_exp_set_syntax(char * name) { + if (!name) { + syntax = NULL; + return 0; + } + for (struct syntax_definition * s = syntaxes; s->name; ++s) { + if (!strcmp(name,s->name)) { + syntax = s; + return 0; + } + } + return 1; +} + +/** + * Syntax highlighting + * Slimmed down from the bim implementation a bit, + * but generally compatible with the same definitions. + * + * Type highlighting has been removed as the sh highlighter + * didn't use it. This should be made pluggable again, and + * the bim syntax highlighters should probably be broken + * out into dynamically-loaded libraries? + */ +static void recalculate_syntax(line_t * line) { + /* Clear syntax for this line first */ + int line_no = 0; + //int is_original = 1; + while (1) { + for (int i = 0; i < line->actual; ++i) { + line->text[i].flags = 0; + } + + if (!syntax) { + return; + } + + /* Start from the line's stored in initial state */ + struct syntax_state state; + state.line = line; + state.line_no = line_no; + state.state = line->istate; + state.i = 0; + + while (1) { + state.state = syntax->calculate(&state); + + if (state.state != 0) { + /* TODO: Figure out a way to make this work for multiline input */ +#if 0 + if (line_no == -1) return; + if (!is_original) { + redraw_line(line_no); + } + if (line_no + 1 < env->line_count && env->lines[line_no+1]->istate != state.state) { + line_no++; + line = env->lines[line_no]; + line->istate = state.state; + if (env->loading) return; + is_original = 0; + goto _next; + } +#endif + return; + } + } +//_next: +// (void)0; + } +} + +/** + * Set colors + */ +static void set_colors(const char * fg, const char * bg) { + printf("\033[22;23;"); + if (*bg == '@') { + int _bg = atoi(bg+1); + if (_bg < 10) { + printf("4%d;", _bg); + } else { + printf("10%d;", _bg-10); + } + } else { + printf("48;%s;", bg); + } + if (*fg == '@') { + int _fg = atoi(fg+1); + if (_fg < 10) { + printf("3%dm", _fg); + } else { + printf("9%dm", _fg-10); + } + } else { + printf("38;%sm", fg); + } + fflush(stdout); +} + +/** + * Set just the foreground color + * + * (See set_colors above) + */ +static void set_fg_color(const char * fg) { + printf("\033[22;23;"); + if (*fg == '@') { + int _fg = atoi(fg+1); + if (_fg < 10) { + printf("3%dm", _fg); + } else { + printf("9%dm", _fg-10); + } + } else { + printf("38;%sm", fg); + } + fflush(stdout); +} + +void rline_set_colors(rline_style_t style) { + switch (style) { + case RLINE_STYLE_MAIN: + set_colors(COLOR_FG, COLOR_BG); + break; + case RLINE_STYLE_ALT: + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + break; + case RLINE_STYLE_KEYWORD: + set_fg_color(COLOR_KEYWORD); + break; + case RLINE_STYLE_STRING: + set_fg_color(COLOR_STRING); + break; + case RLINE_STYLE_COMMENT: + set_fg_color(COLOR_COMMENT); + break; + case RLINE_STYLE_TYPE: + set_fg_color(COLOR_TYPE); + break; + case RLINE_STYLE_PRAGMA: + set_fg_color(COLOR_PRAGMA); + break; + case RLINE_STYLE_NUMERAL: + set_fg_color(COLOR_NUMERAL); + break; + } +} + +/** + * Mostly copied from bim, but with some minor + * alterations and removal of selection support. + */ +static void render_line(void) { + printf("\033[?25l"); + if (show_left_side) { + printf("\033[0m\r%s", prompt); + } else { + printf("\033[0m\r$"); + } + + if (offset && prompt_width_calc) { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("\b<"); + } + + int i = 0; /* Offset in char_t line data entries */ + int j = 0; /* Offset in terminal cells */ + + const char * last_color = NULL; + int was_searching = 0; + + /* Set default text colors */ + set_colors(COLOR_FG, COLOR_BG); + + /* + * When we are rendering in the middle of a wide character, + * we render -'s to fill the remaining amount of the + * charater's width + */ + int remainder = 0; + + int is_spaces = 1; + + line_t * line = the_line; + + /* For each character in the line ... */ + while (i < line->actual) { + + /* If there is remaining text... */ + if (remainder) { + + /* If we should be drawing by now... */ + if (j >= offset) { + /* Fill remainder with -'s */ + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("-"); + set_colors(COLOR_FG, COLOR_BG); + } + + /* One less remaining width cell to fill */ + remainder--; + + /* Terminal offset moves forward */ + j++; + + /* + * If this was the last remaining character, move to + * the next codepoint in the line + */ + if (remainder == 0) { + i++; + } + + continue; + } + + /* Get the next character to draw */ + char_t c = line->text[i]; + if (c.codepoint != ' ') is_spaces = 0; + + /* If we should be drawing by now... */ + if (j >= offset) { + + /* If this character is going to fall off the edge of the screen... */ + if (j - offset + c.display_width >= width - prompt_width_calc) { + /* We draw this with special colors so it isn't ambiguous */ + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + + /* If it's wide, draw ---> as needed */ + while (j - offset < width - prompt_width_calc - 1) { + printf("-"); + j++; + } + + /* End the line with a > to show it overflows */ + printf(">"); + set_colors(COLOR_FG, COLOR_BG); + j++; + break; + } + + /* Syntax hilighting */ + const char * color = flag_to_color(c.flags); + if (c.flags & FLAG_SELECT) { + set_colors(color, COLOR_BG); + fprintf(stdout,"\033[7m"); + was_searching = 1; + } else if (c.flags == FLAG_NOTICE) { + set_colors(COLOR_SEARCH_FG, COLOR_SEARCH_BG); + was_searching = 1; + } else if (was_searching) { + fprintf(stdout,"\033[0m"); + set_colors(color, COLOR_BG); + last_color = color; + } else if (!last_color || strcmp(color, last_color)) { + set_fg_color(color); + last_color = color; + } + + /* Render special characters */ + if (c.codepoint == '\t') { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("»"); + for (int i = 1; i < c.display_width; ++i) { + printf("·"); + } + set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); + } else if (c.codepoint < 32) { + /* Codepoints under 32 to get converted to ^@ escapes */ + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("^%c", '@' + c.codepoint); + set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); + } else if (c.codepoint == 0x7f) { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("^?"); + set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); + } else if (c.codepoint > 0x7f && c.codepoint < 0xa0) { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("<%2x>", c.codepoint); + set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); + } else if (c.codepoint == 0xa0) { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("_"); + set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); + } else if (c.display_width == 8) { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("[U+%04x]", c.codepoint); + set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); + } else if (c.display_width == 10) { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("[U+%06x]", c.codepoint); + set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); +#if 0 + } else if (c.codepoint == ' ' && i == line->actual - 1) { + /* Special case: space at end of line */ + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("·"); + set_colors(COLOR_FG, COLOR_BG); +#endif + } else if (i > 0 && is_spaces && c.codepoint == ' ' && !(i % 4)) { + set_colors(COLOR_ALT_FG, COLOR_BG); /* Normal background so this is more subtle */ + printf("▏"); + set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); + } else { + /* Normal characters get output */ + char tmp[7]; /* Max six bytes, use 7 to ensure last is always nil */ + to_eight(c.codepoint, tmp); + printf("%s", tmp); + } + + /* Advance the terminal cell offset by the render width of this character */ + j += c.display_width; + + /* Advance to the next character */ + i++; + } else if (c.display_width > 1) { + /* + * If this is a wide character but we aren't ready to render yet, + * we may need to draw some filler text for the remainder of its + * width to ensure we don't jump around when horizontally scrolling + * past wide characters. + */ + remainder = c.display_width - 1; + j++; + } else { + /* Regular character, not ready to draw, advance without doing anything */ + j++; + i++; + } + } + + set_colors(COLOR_FG, COLOR_BG); + + if (show_right_side && prompt_right_width) { + /* Fill to end right hand side */ + for (; j < width + offset - prompt_width_calc; ++j) { + printf(" "); + } + + /* Print right hand side */ + printf("\033[0m%s", prompt_right); + } else { + printf("\033[0K"); + } + fflush(stdout); +} + +/** + * Create a line_t + */ +static line_t * line_create(void) { + line_t * line = malloc(sizeof(line_t) + sizeof(char_t) * 32); + line->available = 32; + line->actual = 0; + line->istate = 0; + return line; +} + +/** + * Insert a character into a line + */ +static line_t * line_insert(line_t * line, char_t c, int offset) { + + /* If there is not enough space... */ + if (line->actual == line->available) { + /* Expand the line buffer */ + line->available *= 2; + line = realloc(line, sizeof(line_t) + sizeof(char_t) * line->available); + } + + /* If this was not the last character, then shift remaining characters forward. */ + if (offset < line->actual) { + memmove(&line->text[offset+1], &line->text[offset], sizeof(char_t) * (line->actual - offset)); + } + + /* Insert the new character */ + line->text[offset] = c; + + /* There is one new character in the line */ + line->actual += 1; + + if (!loading) { + recalculate_tabs(line); + recalculate_syntax(line); + } + + return line; +} + +/** + * Update terminal size + * + * We don't listen for sigwinch for various reasons... + */ +static void get_size(void) { +#ifndef _WIN32 + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + rline_terminal_width = w.ws_col; +#else + CONSOLE_SCREEN_BUFFER_INFO csbi; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); + rline_terminal_width = csbi.srWindow.Right - csbi.srWindow.Left + 1; +#endif + if (rline_terminal_width - prompt_right_width - prompt_width > MINIMUM_SIZE) { + show_right_side = 1; + show_left_side = 1; + prompt_width_calc = prompt_width; + width = rline_terminal_width - prompt_right_width; + } else { + show_right_side = 0; + if (rline_terminal_width - prompt_width > MINIMUM_SIZE) { + show_left_side = 1; + prompt_width_calc = prompt_width; + } else { + show_left_side = 0; + prompt_width_calc = 1; + } + width = rline_terminal_width; + } +} + +/** + * Place the cursor within the line + */ +void rline_place_cursor(void) { + int x = prompt_width_calc + 1 - offset; + for (int i = 0; i < column; ++i) { + char_t * c = &the_line->text[i]; + x += c->display_width; + } + + if (x > width - 1) { + /* Adjust the offset appropriately to scroll horizontally */ + int diff = x - (width - 1); + offset += diff; + x -= diff; + render_line(); + } + + /* Same for scrolling horizontally to the left */ + if (x < prompt_width_calc + 1) { + int diff = (prompt_width_calc + 1) - x; + offset -= diff; + x += diff; + render_line(); + } + + printf("\033[?25h\033[%dG", x); + fflush(stdout); +} + +/** + * Delete a character + */ +static void line_delete(line_t * line, int offset) { + + /* Can't delete character before start of line. */ + if (offset == 0) return; + + /* If this isn't the last character, we need to move all subsequent characters backwards */ + if (offset < line->actual) { + memmove(&line->text[offset-1], &line->text[offset], sizeof(char_t) * (line->actual - offset)); + } + + /* The line is one character shorter */ + line->actual -= 1; + + if (!loading) { + recalculate_tabs(line); + recalculate_syntax(line); + } +} + +/** + * Backspace from the cursor position + */ +static void delete_at_cursor(void) { + if (column > 0) { + if (the_line->text[column-1].codepoint == ' ') { + /* Delete this space */ + line_delete(the_line, column); + column--; + if (offset > 0) offset--; + + while (column > 0 && the_line->text[column-1].codepoint == ' ' && (column % 4 != 0)) { + line_delete(the_line, column); + column--; + if (offset > 0) offset--; + } + } else { + line_delete(the_line, column); + column--; + if (offset > 0) offset--; + } + } +} + +/** + * Delete whole word + */ +static void delete_word(void) { + if (!the_line->actual) return; + if (!column) return; + + do { + if (column > 0) { + line_delete(the_line, column); + column--; + if (offset > 0) offset--; + } + } while (column && the_line->text[column-1].codepoint != ' '); +} + +/** + * Insert at cursor position + */ +static void insert_char(uint32_t c) { + char_t _c; + _c.codepoint = c; + _c.flags = 0; + _c.display_width = codepoint_width(c); + + the_line = line_insert(the_line, _c, column); + + column++; +} + +char * paren_pairs = "()[]{}<>"; + +int is_paren(int c) { + char * p = paren_pairs; + while (*p) { + if (c == *p) return 1; + p++; + } + return 0; +} + +void find_matching_paren(int * out_col, int in_col) { + if (column - in_col > the_line->actual) { + return; /* Invalid cursor position */ + } + + int paren_match = 0; + int direction = 0; + int start = the_line->text[column-in_col].codepoint; + int flags = the_line->text[column-in_col].flags & 0x1F; + int count = 0; + + /* TODO what about unicode parens? */ + for (int i = 0; paren_pairs[i]; ++i) { + if (start == paren_pairs[i]) { + direction = (i % 2 == 0) ? 1 : -1; + paren_match = paren_pairs[(i % 2 == 0) ? (i+1) : (i-1)]; + break; + } + } + + if (!paren_match) return; + + /* Scan for match */ + int col = column - in_col; + + while (col > -1 && col < the_line->actual) { + /* Only match on same syntax */ + if ((the_line->text[col].flags & 0x1F) == flags) { + /* Count up on same direction */ + if (the_line->text[col].codepoint == start) count++; + /* Count down on opposite direction */ + if (the_line->text[col].codepoint == paren_match) { + count--; + /* When count == 0 we have a match */ + if (count == 0) goto _match_found; + } + } + col += direction; + } + +_match_found: + *out_col = col; +} + +void redraw_matching_paren(int col) { + for (int j = 0; j < the_line->actual; ++j) { + if (j == col) { + the_line->text[j].flags |= FLAG_SELECT; + } else { + the_line->text[j].flags &= ~(FLAG_SELECT); + } + } +} + +void highlight_matching_paren(void) { + int col = -1; + if (is_paren(the_line->text[column].codepoint)) { + find_matching_paren(&col, 0); + } else if (column > 0 && is_paren(the_line->text[column-1].codepoint)) { + find_matching_paren(&col, 1); + } + redraw_matching_paren(col); +} + + +/** + * Move cursor left + */ +static void cursor_left(void) { + if (column > 0) column--; + rline_place_cursor(); +} + +/** + * Move cursor right + */ +static void cursor_right(void) { + if (column < the_line->actual) column++; + rline_place_cursor(); +} + +/** + * Move cursor one whole word left + */ +static void word_left(void) { + if (column == 0) return; + column--; + while (column && the_line->text[column].codepoint == ' ') { + column--; + } + while (column > 0) { + if (the_line->text[column-1].codepoint == ' ') break; + column--; + } + rline_place_cursor(); +} + +/** + * Move cursor one whole word right + */ +static void word_right(void) { + while (column < the_line->actual && the_line->text[column].codepoint == ' ') { + column++; + } + while (column < the_line->actual) { + column++; + if (column < the_line->actual && the_line->text[column].codepoint == ' ') break; + } + rline_place_cursor(); +} + +/** + * Move cursor to start of line + */ +static void cursor_home(void) { + column = 0; + rline_place_cursor(); +} + +/* + * Move cursor to end of line + */ +static void cursor_end(void) { + column = the_line->actual; + rline_place_cursor(); +} + +/** + * Temporary buffer for holding utf-8 data + */ +static char temp_buffer[1024]; + +/** + * Cycle to previous history entry + */ +static void history_previous(void) { + if (rline_scroll == 0) { + /* Convert to temporaary buffer */ + unsigned int off = 0; + memset(temp_buffer, 0, sizeof(temp_buffer)); + for (int j = 0; j < the_line->actual; j++) { + char_t c = the_line->text[j]; + off += to_eight(c.codepoint, &temp_buffer[off]); + } + } + + if (rline_scroll < rline_history_count) { + rline_scroll++; + + /* Copy in from history */ + the_line->actual = 0; + column = 0; + loading = 1; + unsigned char * buf = (unsigned char *)rline_history_prev(rline_scroll); + uint32_t istate = 0, c = 0; + for (unsigned int i = 0; i < strlen((char *)buf); ++i) { + if (!decode(&istate, &c, buf[i])) { + insert_char(c); + } + } + loading = 0; + } + /* Set cursor at end */ + column = the_line->actual; + offset = 0; + recalculate_tabs(the_line); + recalculate_syntax(the_line); + render_line(); + rline_place_cursor(); +} + +/** + * Cycle to next history entry + */ +static void history_next(void) { + if (rline_scroll > 1) { + rline_scroll--; + + /* Copy in from history */ + the_line->actual = 0; + column = 0; + loading = 1; + unsigned char * buf = (unsigned char *)rline_history_prev(rline_scroll); + uint32_t istate = 0, c = 0; + for (unsigned int i = 0; i < strlen((char *)buf); ++i) { + if (!decode(&istate, &c, buf[i])) { + insert_char(c); + } + } + loading = 0; + } else if (rline_scroll == 1) { + /* Copy in from temp */ + rline_scroll = 0; + + the_line->actual = 0; + column = 0; + loading = 1; + char * buf = temp_buffer; + uint32_t istate = 0, c = 0; + for (unsigned int i = 0; i < strlen(buf); ++i) { + if (!decode(&istate, &c, buf[i])) { + insert_char(c); + } + } + loading = 0; + } + /* Set cursor at end */ + column = the_line->actual; + offset = 0; + recalculate_tabs(the_line); + recalculate_syntax(the_line); + render_line(); + rline_place_cursor(); +} + +/** + * Handle escape sequences (arrow keys, etc.) + */ +static int handle_escape(int * this_buf, int * timeout, int c) { + if (*timeout >= 1 && this_buf[*timeout-1] == '\033' && c == '\033') { + this_buf[0]= c; + *timeout = 1; + return 1; + } + if (*timeout >= 1 && this_buf[*timeout-1] == '\033' && c == '[') { + *timeout = 1; + this_buf[*timeout] = c; + (*timeout)++; + return 0; + } + if (*timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == '[' && + (isdigit(c) || c == ';')) { + this_buf[*timeout] = c; + (*timeout)++; + return 0; + } + if (*timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == '[') { + switch (c) { + case 'A': // up + history_previous(); + break; + case 'B': // down + history_next(); + break; + case 'C': // right + if (this_buf[*timeout-1] == '5') { + word_right(); + } else { + cursor_right(); + } + break; + case 'D': // left + if (this_buf[*timeout-1] == '5') { + word_left(); + } else { + cursor_left(); + } + break; + case 'H': // home + cursor_home(); + break; + case 'F': // end + cursor_end(); + break; + case '~': + switch (this_buf[*timeout-1]) { + case '1': + cursor_home(); + break; + case '3': + /* Delete forward */ + if (column < the_line->actual) { + line_delete(the_line, column+1); + if (offset > 0) offset--; + } + break; + case '4': + cursor_end(); + break; + } + break; + default: + break; + } + *timeout = 0; + return 0; + } + + *timeout = 0; + return 0; +} + +#ifndef _WIN32 +static unsigned int _INTR, _EOF; +static struct termios old; +static void get_initial_termios(void) { + tcgetattr(STDOUT_FILENO, &old); + _INTR = old.c_cc[VINTR]; + _EOF = old.c_cc[VEOF]; +} +static void set_unbuffered(void) { + struct termios new = old; + new.c_lflag &= (~ICANON & ~ECHO & ~ISIG); + tcsetattr(STDOUT_FILENO, TCSAFLUSH, &new); +} + +static void set_buffered(void) { + tcsetattr(STDOUT_FILENO, TCSAFLUSH, &old); +} +#else +static unsigned int _INTR = 3; +static unsigned int _EOF = 4; +static void get_initial_termios(void) { +} +static void set_unbuffered(void) { + /* Disables line input, echo, ^C processing, and a few others. */ + SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_VIRTUAL_TERMINAL_INPUT); + SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_WRAP_AT_EOL_OUTPUT); +} +static void set_buffered(void) { + /* These are the defaults */ + SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), + ENABLE_ECHO_INPUT | + ENABLE_INSERT_MODE | + ENABLE_LINE_INPUT | + ENABLE_MOUSE_INPUT | + ENABLE_PROCESSED_INPUT | + ENABLE_QUICK_EDIT_MODE | + ENABLE_VIRTUAL_TERMINAL_INPUT + ); +} +#endif + +static int tabbed; + +static void dummy_redraw(rline_context_t * context) { + /* Do nothing */ +} + +/** + * Juggle our buffer with an rline context so we can + * call original rline functions such as a tab-completion callback + * or reverse search. + */ +static void call_rline_func(rline_callback_t func, rline_context_t * context) { + /* Unicode parser state */ + uint32_t istate = 0; + uint32_t c; + + /* Don't let rline draw things */ + context->quiet = 1; + + /* Allocate a temporary buffer */ + context->buffer = malloc(buf_size_max); + memset(context->buffer,0,buf_size_max); + + /* Convert current data to utf-8 */ + unsigned int off = 0; + for (int j = 0; j < the_line->actual; j++) { + if (j == column) { + /* Track cursor position */ + context->offset = off; + } + char_t c = the_line->text[j]; + off += to_eight(c.codepoint, &context->buffer[off]); + } + + /* If the cursor was at the end, the loop above didn't catch it */ + if (column == the_line->actual) context->offset = off; + + /* + * Did we just press tab before this? This is actually managed + * by the tab-completion function. + */ + context->tabbed = tabbed; + + /* Empty callbacks */ + rline_callbacks_t tmp = {0}; + /* + * Because some clients expect this to be set... + * (we don't need it, we'll redraw ourselves later) + */ + tmp.redraw_prompt = dummy_redraw; + + /* Setup context */ + context->callbacks = &tmp; + context->collected = off; + context->buffer[off] = '\0'; + context->requested = 1024; + + /* Reset colors (for tab completion candidates, etc. */ + printf("\033[0m"); + + /* Call the function */ + func(context); + + /* Now convert back */ + loading = 1; + int final_column = 0; + the_line->actual = 0; + column = 0; + istate = 0; + for (int i = 0; i < context->collected; ++i) { + if (i == context->offset) { + final_column = column; + } + if (!decode(&istate, &c, ((unsigned char *)context->buffer)[i])) { + insert_char(c); + } + } + + free(context->buffer); + + /* Position cursor */ + if (context->offset == context->collected) { + column = the_line->actual; + } else { + column = final_column; + } + tabbed = context->tabbed; + loading = 0; + + /* Recalculate + redraw */ + recalculate_tabs(the_line); + recalculate_syntax(the_line); + render_line(); + rline_place_cursor(); +} + +char * rline_preload = NULL; + +/** + * Perform actual interactive line editing. + * + * This is mostly a reimplementation of bim's + * INSERT mode, but with some cleanups and fixes + * to work on a single line and to add some new + * key bindings we don't have in bim. + */ +static int read_line(void) { + int cin; + uint32_t c = 0; + int timeout = 0; + int this_buf[20]; + uint32_t istate = 0; + + /* Let's disable this under Windows... */ + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + fprintf(stdout, "◄\033[0m"); /* TODO: This could be retrieved from an envvar */ + for (int i = 0; i < rline_terminal_width - 1; ++i) { + fprintf(stdout, " "); + } + + if (rline_preload) { + char * c = rline_preload; + while (*c) { + insert_char(*c); + c++; + } + free(rline_preload); + rline_preload = NULL; + } + + render_line(); + rline_place_cursor(); + + while ((cin = getch(timeout))) { + if (cin == -1) continue; + get_size(); + if (!decode(&istate, &c, cin)) { + if (timeout == 0) { + if (c != '\t') tabbed = 0; + if (_INTR && c == _INTR) { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("^%c", (int)('@' + c)); + printf("\033[0m"); + loading = 1; + the_line->actual = 0; + column = 0; + insert_char('\n'); + raise(SIGINT); + return 1; + } + if (_EOF && c == _EOF) { + if (column == 0 && the_line->actual == 0) { + for (char *_c = rline_exit_string; *_c; ++_c) { + insert_char(*_c); + } + redraw_matching_paren(-1); + render_line(); + rline_place_cursor(); + if (!*rline_exit_string) { + set_colors(COLOR_ALT_FG, COLOR_ALT_BG); + printf("^D\033[0m"); + } + return 1; + } else { /* Otherwise act like delete */ + if (column < the_line->actual) { + line_delete(the_line, column+1); + if (offset > 0) offset--; + } + continue; + } + } + switch (c) { + case '\033': + if (timeout == 0) { + this_buf[timeout] = c; + timeout++; + } + break; + case DELETE_KEY: + case BACKSPACE_KEY: + delete_at_cursor(); + break; + case 13: + case ENTER_KEY: + /* Finished */ + loading = 1; + column = the_line->actual; + redraw_matching_paren(-1); + render_line(); + insert_char('\n'); + return 1; + case 22: /* ^V */ + /* Don't bother with unicode, just take the next byte */ + rline_place_cursor(); + printf("^\b"); + insert_char(getc(stdin)); + break; + case 23: /* ^W */ + delete_word(); + break; + case 12: /* ^L - Repaint the whole screen */ + printf("\033[2J\033[H"); + render_line(); + rline_place_cursor(); + break; + case 11: /* ^K - Clear to end */ + the_line->actual = column; + break; + case 21: /* ^U - Kill to beginning */ + while (column) { + delete_at_cursor(); + } + break; + case '\t': + if ((syntax && syntax->tabIndents) && (column == 0 || the_line->text[column-1].codepoint == ' ')) { + /* Insert tab character */ + insert_char(' '); + insert_char(' '); + insert_char(' '); + insert_char(' '); + } else if (tab_complete_func) { + /* Tab complete */ + rline_context_t context = {0}; + call_rline_func(tab_complete_func, &context); + continue; + } + break; + default: + insert_char(c); + break; + } + } else { + if (handle_escape(this_buf,&timeout,c)) { + render_line(); + rline_place_cursor(); + continue; + } + } + highlight_matching_paren(); + render_line(); + rline_place_cursor(); + } else if (istate == UTF8_REJECT) { + istate = 0; + } + } + return 0; +} + +/** + * Read a line of text with interactive editing. + */ +int rline(char * buffer, int buf_size) { +#ifndef _WIN32 + setlocale(LC_ALL, ""); +#else + setlocale(LC_ALL, "C.UTF-8"); +#endif + get_initial_termios(); + set_unbuffered(); + get_size(); + + column = 0; + offset = 0; + buf_size_max = buf_size; + + char * theme = getenv("RLINE_THEME"); + if (theme && !strcmp(theme,"sunsmoke")) { /* TODO bring back theme tables */ + rline_exp_load_colorscheme_sunsmoke(); + } else { + rline_exp_load_colorscheme_default(); + } + + the_line = line_create(); + loading = 0; + read_line(); + printf("\r\033[?25h\033[0m\n"); + + unsigned int off = 0; + for (int j = 0; j < the_line->actual; j++) { + char_t c = the_line->text[j]; + off += to_eight(c.codepoint, &buffer[off]); + } + + free(the_line); + + set_buffered(); + + return strlen(buffer); +} + +void rline_insert(rline_context_t * context, const char * what) { + size_t insertion_length = strlen(what); + + if (context->collected + (int)insertion_length > context->requested) { + insertion_length = context->requested - context->collected; + } + + /* Move */ + memmove(&context->buffer[context->offset + insertion_length], &context->buffer[context->offset], context->collected - context->offset); + memcpy(&context->buffer[context->offset], what, insertion_length); + context->collected += insertion_length; + context->offset += insertion_length; } diff --git a/lib/rline_exp.c b/lib/rline_exp.c index 46a2bffe..44c6de27 100644 --- a/lib/rline_exp.c +++ b/lib/rline_exp.c @@ -1,1770 +1,6 @@ -/* vim: tabstop=4 shiftwidth=4 noexpandtab - * This file is part of ToaruOS and is released under the terms - * of the NCSA / University of Illinois License - see LICENSE.md - * Copyright (C) 2018 K. Lange - * - * Experimental rline replacement with syntax highlighting, based - * on bim's highlighting and line editing. - * - */ -#define _XOPEN_SOURCE -#define _DEFAULT_SOURCE -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include - -#include #include - -#define ENTER_KEY '\n' -#define BACKSPACE_KEY 0x08 -#define DELETE_KEY 0x7F -#define MINIMUM_SIZE 10 - -/** - * Same structures as in bim. - * A single character has: - * - A codepoint (Unicode) of up to 21 bits. - * - Flags for syntax highlighting. - * - A display width for rendering. - */ -typedef struct { - uint32_t display_width:4; - uint32_t flags:7; - uint32_t codepoint:21; -} __attribute__((packed)) char_t; - -/** - * We generally only have the one line, - * but this matches bim for compatibility reasons. - */ -typedef struct { - int available; - int actual; - int istate; - char_t text[0]; -} line_t; - -/** - * We operate on a single line of text. - * Maybe we can expand this in the future - * for continuations of edits such as when - * a quote is unclosed? - */ -static line_t * the_line = NULL; - -/** - * Line editor state - */ -static int loading = 0; -static int column = 0; -static int offset = 0; -static int width = 0; -static int full_width = 0; -static int show_right_side = 0; -static int show_left_side = 0; -static int prompt_width_calc = 0; -static int buf_size_max = 0; - -/** - * Prompt strings. - * Defaults to just a "> " prompt with no right side. - * Support for right side prompts is important - * for the ToaruOS shell. - */ -static int prompt_width = 2; -static char * prompt = "> "; -static int prompt_right_width = 0; -static char * prompt_right = ""; - -int rline_exp_set_prompts(char * left, char * right, int left_width, int right_width) { - prompt = left; - prompt_right = right; - prompt_width = left_width; - prompt_right_width = right_width; - return 0; -} - -/** - * Extra shell commands to highlight as keywords. - * These are basically just copied from the - * shell's tab completion database on startup. - */ -static char ** shell_commands = {0}; -static int shell_commands_len = 0; - -int rline_exp_set_shell_commands(char ** cmds, int len) { - shell_commands = cmds; - shell_commands_len = len; - return 0; -} - -/** - * Tab completion callback. - * Compatible with the original rline version. - */ -static rline_callback_t tab_complete_func = NULL; - -int rline_exp_set_tab_complete_func(rline_callback_t func) { - tab_complete_func = func; - return 0; -} - -static int _unget = -1; -static void _ungetc(int c) { - _unget = c; -} - -static int getch(int immediate) { - if (_unget != -1) { - int out = _unget; - _unget = -1; - return out; - } - if (immediate) { - return getc(stdin); - } - struct pollfd fds[1]; - fds[0].fd = STDIN_FILENO; - fds[0].events = POLLIN; - int ret = poll(fds,1,10); - if (ret > 0 && fds[0].revents & POLLIN) { - unsigned char buf[1]; - read(STDIN_FILENO, buf, 1); - return buf[0]; - } else { - return -1; - } -} - -/** - * Convert from Unicode string to utf-8. - */ -static int to_eight(uint32_t codepoint, char * out) { - memset(out, 0x00, 7); - - if (codepoint < 0x0080) { - out[0] = (char)codepoint; - } else if (codepoint < 0x0800) { - out[0] = 0xC0 | (codepoint >> 6); - out[1] = 0x80 | (codepoint & 0x3F); - } else if (codepoint < 0x10000) { - out[0] = 0xE0 | (codepoint >> 12); - out[1] = 0x80 | ((codepoint >> 6) & 0x3F); - out[2] = 0x80 | (codepoint & 0x3F); - } else if (codepoint < 0x200000) { - out[0] = 0xF0 | (codepoint >> 18); - out[1] = 0x80 | ((codepoint >> 12) & 0x3F); - out[2] = 0x80 | ((codepoint >> 6) & 0x3F); - out[3] = 0x80 | ((codepoint) & 0x3F); - } else if (codepoint < 0x4000000) { - out[0] = 0xF8 | (codepoint >> 24); - out[1] = 0x80 | (codepoint >> 18); - out[2] = 0x80 | ((codepoint >> 12) & 0x3F); - out[3] = 0x80 | ((codepoint >> 6) & 0x3F); - out[4] = 0x80 | ((codepoint) & 0x3F); - } else { - out[0] = 0xF8 | (codepoint >> 30); - out[1] = 0x80 | ((codepoint >> 24) & 0x3F); - out[2] = 0x80 | ((codepoint >> 18) & 0x3F); - out[3] = 0x80 | ((codepoint >> 12) & 0x3F); - out[4] = 0x80 | ((codepoint >> 6) & 0x3F); - out[5] = 0x80 | ((codepoint) & 0x3F); - } - - return strlen(out); -} - -/** - * Obtain codepoint display width. - * - * This is copied from bim. Supports a few useful - * things like rendering escapes as codepoints. - */ -static int codepoint_width(wchar_t codepoint) { - if (codepoint == '\t') { - return 1; /* Recalculate later */ - } - if (codepoint < 32) { - /* We render these as ^@ */ - return 2; - } - if (codepoint == 0x7F) { - /* Renders as ^? */ - return 2; - } - if (codepoint > 0x7f && codepoint < 0xa0) { - /* Upper control bytes */ - return 4; - } - if (codepoint == 0xa0) { - /* Non-breaking space _ */ - return 1; - } - /* Skip wcwidth for anything under 256 */ - if (codepoint > 256) { - /* Higher codepoints may be wider (eg. Japanese) */ - int out = wcwidth(codepoint); - if (out >= 1) return out; - /* Invalid character, render as [U+ABCD] or [U+ABCDEF] */ - return (codepoint < 0x10000) ? 8 : 10; - } - return 1; -} - -void recalculate_tabs(line_t * line) { - int j = 0; - for (int i = 0; i < line->actual; ++i) { - if (line->text[i].codepoint == '\t') { - line->text[i].display_width = 4 - (j % 4); - } - j += line->text[i].display_width; - } -} - - -/** - * Color themes have also been copied from bim. - * - * Slimmed down to only the ones we use for syntax - * highlighting; the UI colors have been removed. - */ -static const char * COLOR_FG = "@9"; -static const char * COLOR_BG = "@9"; -static const char * COLOR_ALT_FG = "@5"; -static const char * COLOR_ALT_BG = "@9"; -static const char * COLOR_KEYWORD = "@4"; -static const char * COLOR_STRING = "@2"; -static const char * COLOR_COMMENT = "@5"; -static const char * COLOR_TYPE = "@3"; -static const char * COLOR_PRAGMA = "@1"; -static const char * COLOR_NUMERAL = "@1"; -static const char * COLOR_RED = "@1"; -static const char * COLOR_GREEN = "@2"; -static const char * COLOR_ESCAPE = "@2"; -static const char * COLOR_SEARCH_FG = "@0"; -static const char * COLOR_SEARCH_BG = "@3"; - -/** - * Themes are selected from the $RLINE_THEME - * environment variable. - */ -static void rline_exp_load_colorscheme_default(void) { - COLOR_FG = "@9"; - COLOR_BG = "@9"; - COLOR_ALT_FG = "@10"; - COLOR_ALT_BG = "@9"; - COLOR_KEYWORD = "@14"; - COLOR_STRING = "@2"; - COLOR_COMMENT = "@10"; - COLOR_TYPE = "@3"; - COLOR_PRAGMA = "@1"; - COLOR_NUMERAL = "@1"; - COLOR_RED = "@1"; - COLOR_GREEN = "@2"; - COLOR_ESCAPE = "@12"; - COLOR_SEARCH_FG = "@0"; - COLOR_SEARCH_BG = "@13"; -} - -static void rline_exp_load_colorscheme_sunsmoke(void) { - COLOR_FG = "2;230;230;230"; - COLOR_BG = "@9"; - COLOR_ALT_FG = "2;122;122;122"; - COLOR_ALT_BG = "2;46;43;46"; - COLOR_KEYWORD = "2;51;162;230"; - COLOR_STRING = "2;72;176;72"; - COLOR_COMMENT = "2;158;153;129;3"; - COLOR_TYPE = "2;230;206;110"; - COLOR_PRAGMA = "2;194;70;54"; - COLOR_NUMERAL = "2;230;43;127"; - COLOR_RED = "2;222;53;53"; - COLOR_GREEN = "2;55;167;0"; - COLOR_ESCAPE = "2;113;203;173"; - COLOR_SEARCH_FG = "5;234"; - COLOR_SEARCH_BG = "5;226"; -} - -/** - * Syntax highlighting flags. - */ -#define FLAG_NONE 0 -#define FLAG_KEYWORD 1 -#define FLAG_STRING 2 -#define FLAG_COMMENT 3 -#define FLAG_TYPE 4 -#define FLAG_PRAGMA 5 -#define FLAG_NUMERAL 6 -#define FLAG_ERROR 7 -#define FLAG_DIFFPLUS 8 -#define FLAG_DIFFMINUS 9 -#define FLAG_NOTICE 10 -#define FLAG_BOLD 11 -#define FLAG_LINK 12 -#define FLAG_ESCAPE 13 - -#define FLAG_SELECT (1 << 5) -#define FLAG_SEARCH (1 << 6) - -struct syntax_state { - line_t * line; - int line_no; - int state; - int i; -}; - -#define paint(length, flag) do { for (int i = 0; i < (length) && state->i < state->line->actual; i++, state->i++) { state->line->text[state->i].flags = (flag); } } while (0) -#define charat() (state->i < state->line->actual ? state->line->text[(state->i)].codepoint : -1) -#define nextchar() (state->i + 1 < state->line->actual ? state->line->text[(state->i+1)].codepoint : -1) -#define lastchar() (state->i - 1 >= 0 ? state->line->text[(state->i-1)].codepoint : -1) -#define skip() (state->i++) -#define charrel(x) (state->i + (x) < state->line->actual ? state->line->text[(state->i+(x))].codepoint : -1) - -/** - * Match and paint a single keyword. Returns 1 if the keyword was matched and 0 otherwise, - * so it can be used for prefix checking for things that need further special handling. - */ -int match_and_paint(struct syntax_state * state, const char * keyword, int flag, int (*keyword_qualifier)(int c)) { - if (keyword_qualifier(lastchar())) return 0; - if (!keyword_qualifier(charat())) return 0; - int i = state->i; - int slen = 0; - while (i < state->line->actual || *keyword == '\0') { - if (*keyword == '\0' && (i >= state->line->actual || !keyword_qualifier(state->line->text[i].codepoint))) { - for (int j = 0; j < slen; ++j) { - paint(1, flag); - } - return 1; - } - if (*keyword != state->line->text[i].codepoint) return 0; - - i++; - keyword++; - slen++; - } - return 0; -} - -/** - * Find keywords from a list and paint them, assuming they aren't in the middle of other words. - * Returns 1 if a keyword from the last was found, otherwise 0. - */ -int find_keywords(struct syntax_state * state, char ** keywords, int flag, int (*keyword_qualifier)(int c)) { - if (keyword_qualifier(lastchar())) return 0; - if (!keyword_qualifier(charat())) return 0; - for (char ** keyword = keywords; *keyword; ++keyword) { - int d = 0; - while (state->i + d < state->line->actual && state->line->text[state->i+d].codepoint == (*keyword)[d]) d++; - if ((*keyword)[d] == '\0' && (state->i + d >= state->line->actual || !keyword_qualifier(state->line->text[state->i+d].codepoint))) { - paint((int)strlen(*keyword), flag); - return 1; - } - } - - return 0; -} - -/** - * This is a basic character matcher for "keyword" characters. - */ -int simple_keyword_qualifier(int c) { - return isalnum(c) || (c == '_'); -} - - -int common_comment_buzzwords(struct syntax_state * state) { - if (match_and_paint(state, "TODO", FLAG_NOTICE, simple_keyword_qualifier)) { return 1; } - else if (match_and_paint(state, "XXX", FLAG_NOTICE, simple_keyword_qualifier)) { return 1; } - else if (match_and_paint(state, "FIXME", FLAG_ERROR, simple_keyword_qualifier)) { return 1; } - return 0; -} - -/** - * Paint a comment until end of line, assumes this comment can not continue. - * (Some languages have comments that can continue with a \ - don't use this!) - * Assumes you've already painted your comment start characters. - */ -int paint_comment(struct syntax_state * state) { - while (charat() != -1) { - if (common_comment_buzzwords(state)) continue; - else { paint(1, FLAG_COMMENT); } - } - return -1; -} - -int c_keyword_qualifier(int c) { - return isalnum(c) || (c == '_'); -} - -int esh_variable_qualifier(int c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '_'); -} - -int paint_esh_variable(struct syntax_state * state) { - if (charat() == '{') { - paint(1, FLAG_TYPE); - while (charat() != '}' && charat() != -1) paint(1, FLAG_TYPE); - if (charat() == '}') paint(1, FLAG_TYPE); - } else { - if (charat() == '?' || charat() == '$' || charat() == '#') { - paint(1, FLAG_TYPE); - } else { - while (esh_variable_qualifier(charat())) paint(1, FLAG_TYPE); - } - } - return 0; -} - -int paint_esh_string(struct syntax_state * state) { - int last = -1; - while (charat() != -1) { - if (last != '\\' && charat() == '"') { - paint(1, FLAG_STRING); - return 0; - } else if (charat() == '$') { - paint(1, FLAG_TYPE); - paint_esh_variable(state); - last = -1; - } else if (charat() != -1) { - last = charat(); - paint(1, FLAG_STRING); - } - } - return 2; -} - -int paint_esh_single_string(struct syntax_state * state) { - int last = -1; - while (charat() != -1) { - if (last != '\\' && charat() == '\'') { - paint(1, FLAG_STRING); - return 0; - } else if (charat() != -1) { - last = charat(); - paint(1, FLAG_STRING); - } - } - return 1; -} - -int esh_keyword_qualifier(int c) { - return (isalnum(c) || c == '?' || c == '_' || c == '-'); /* technically anything that isn't a space should qualify... */ -} - -char * esh_keywords[] = { - "cd","exit","export","help","history","if","empty?", - "equals?","return","export-cmd","source","exec","not","while", - "then","else","echo", - NULL -}; - -int syn_esh_calculate(struct syntax_state * state) { - if (state->state == 1) { - return paint_esh_single_string(state); - } else if (state->state == 2) { - return paint_esh_string(state); - } - if (charat() == '#') { - while (charat() != -1) { - if (common_comment_buzzwords(state)) continue; - else paint(1, FLAG_COMMENT); - } - return -1; - } else if (charat() == '$') { - paint(1, FLAG_TYPE); - paint_esh_variable(state); - return 0; - } else if (charat() == '\'') { - paint(1, FLAG_STRING); - return paint_esh_single_string(state); - } else if (charat() == '"') { - paint(1, FLAG_STRING); - return paint_esh_string(state); - } else if (match_and_paint(state, "export", FLAG_KEYWORD, esh_keyword_qualifier)) { - while (charat() == ' ') skip(); - while (esh_keyword_qualifier(charat())) paint(1, FLAG_TYPE); - return 0; - } else if (match_and_paint(state, "export-cmd", FLAG_KEYWORD, esh_keyword_qualifier)) { - while (charat() == ' ') skip(); - while (esh_keyword_qualifier(charat())) paint(1, FLAG_TYPE); - return 0; - } else if (find_keywords(state, esh_keywords, FLAG_KEYWORD, esh_keyword_qualifier)) { - return 0; - } else if (find_keywords(state, shell_commands, FLAG_KEYWORD, esh_keyword_qualifier)) { - return 0; - } else if (isdigit(charat())) { - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - return 0; - } else if (charat() != -1) { - skip(); - return 0; - } - return -1; -} - -void paint_simple_string(struct syntax_state * state) { - /* Assumes you came in from a check of charat() == '"' */ - paint(1, FLAG_STRING); - while (charat() != -1) { - if (charat() == '\\' && nextchar() == '"') { - paint(2, FLAG_ESCAPE); - } else if (charat() == '"') { - paint(1, FLAG_STRING); - return; - } else if (charat() == '\\') { - paint(2, FLAG_ESCAPE); - } else { - paint(1, FLAG_STRING); - } - } -} - -char * syn_py_keywords[] = { - "class","def","return","del","if","else","elif","for","while","continue", - "break","assert","as","and","or","except","finally","from","global", - "import","in","is","lambda","with","nonlocal","not","pass","raise","try","yield", - NULL -}; - -char * syn_py_types[] = { - /* built-in functions */ - "abs","all","any","ascii","bin","bool","breakpoint","bytes", - "bytearray","callable","compile","complex","delattr","chr", - "dict","dir","divmod","enumerate","eval","exec","filter","float", - "format","frozenset","getattr","globals","hasattr","hash","help", - "hex","id","input","int","isinstance","issubclass","iter","len", - "list","locals","map","max","memoryview","min","next","object", - "oct","open","ord","pow","print","property","range","repr","reverse", - "round","set","setattr","slice","sorted","staticmethod","str","sum", - "super","tuple","type","vars","zip", - NULL -}; - -char * syn_py_special[] = { - "True","False","None", - NULL -}; - -int paint_py_triple_double(struct syntax_state * state) { - while (charat() != -1) { - if (charat() == '"') { - paint(1, FLAG_STRING); - if (charat() == '"' && nextchar() == '"') { - paint(2, FLAG_STRING); - return 0; - } - } else { - paint(1, FLAG_STRING); - } - } - return 1; /* continues */ -} - -int paint_py_triple_single(struct syntax_state * state) { - while (charat() != -1) { - if (charat() == '\'') { - paint(1, FLAG_STRING); - if (charat() == '\'' && nextchar() == '\'') { - paint(2, FLAG_STRING); - return 0; - } - } else { - paint(1, FLAG_STRING); - } - } - return 2; /* continues */ -} - -int paint_py_single_string(struct syntax_state * state) { - paint(1, FLAG_STRING); - while (charat() != -1) { - if (charat() == '\\' && nextchar() == '\'') { - paint(2, FLAG_ESCAPE); - } else if (charat() == '\'') { - paint(1, FLAG_STRING); - return 0; - } else if (charat() == '\\') { - paint(2, FLAG_ESCAPE); - } else { - paint(1, FLAG_STRING); - } - } - return 0; -} - -int paint_py_numeral(struct syntax_state * state) { - if (charat() == '0' && (nextchar() == 'x' || nextchar() == 'X')) { - paint(2, FLAG_NUMERAL); - while (isxdigit(charat())) paint(1, FLAG_NUMERAL); - } else if (charat() == '0' && nextchar() == '.') { - paint(2, FLAG_NUMERAL); - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - if ((charat() == '+' || charat() == '-') && (nextchar() == 'e' || nextchar() == 'E')) { - paint(2, FLAG_NUMERAL); - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - } else if (charat() == 'e' || charat() == 'E') { - paint(1, FLAG_NUMERAL); - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - } - if (charat() == 'j') paint(1, FLAG_NUMERAL); - return 0; - } else { - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - if (charat() == '.') { - paint(1, FLAG_NUMERAL); - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - if ((charat() == '+' || charat() == '-') && (nextchar() == 'e' || nextchar() == 'E')) { - paint(2, FLAG_NUMERAL); - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - } else if (charat() == 'e' || charat() == 'E') { - paint(1, FLAG_NUMERAL); - while (isdigit(charat())) paint(1, FLAG_NUMERAL); - } - if (charat() == 'j') paint(1, FLAG_NUMERAL); - return 0; - } - if (charat() == 'j') paint(1, FLAG_NUMERAL); - } - while (charat() == 'l' || charat() == 'L') paint(1, FLAG_NUMERAL); - return 0; -} - -void paint_py_format_string(struct syntax_state * state, char type) { - paint(1, FLAG_STRING); - while (charat() != -1) { - if (charat() == '\\' && nextchar() == type) { - paint(2, FLAG_ESCAPE); - } else if (charat() == type) { - paint(1, FLAG_STRING); - return; - } else if (charat() == '\\') { - paint(2, FLAG_ESCAPE); - } else if (charat() == '{') { - paint(1, FLAG_NUMERAL); - if (charat() == '}') { - state->i--; - paint(2, FLAG_ERROR); /* Can't do that. */ - } else { - while (charat() != -1 && charat() != '}') { - paint(1, FLAG_NUMERAL); - } - paint(1, FLAG_NUMERAL); - } - } else { - paint(1, FLAG_STRING); - } - } -} - -int syn_py_calculate(struct syntax_state * state) { - switch (state->state) { - case -1: - case 0: - if (charat() == '#') { - paint_comment(state); - } else if (state->i == 0 && match_and_paint(state, "import", FLAG_PRAGMA, c_keyword_qualifier)) { - return 0; - } else if (charat() == '@') { - paint(1, FLAG_PRAGMA); - while (c_keyword_qualifier(charat())) paint(1, FLAG_PRAGMA); - return 0; - } else if (charat() == '"') { - if (nextchar() == '"' && charrel(2) == '"') { - paint(3, FLAG_STRING); - return paint_py_triple_double(state); - } else if (lastchar() == 'f') { - /* I don't like backtracking like this, but it makes this parse easier */ - state->i--; - paint(1,FLAG_TYPE); - paint_py_format_string(state,'"'); - return 0; - } else { - paint_simple_string(state); - return 0; - } - } else if (find_keywords(state, syn_py_keywords, FLAG_KEYWORD, c_keyword_qualifier)) { - return 0; - } else if (lastchar() != '.' && find_keywords(state, syn_py_types, FLAG_TYPE, c_keyword_qualifier)) { - return 0; - } else if (find_keywords(state, syn_py_special, FLAG_NUMERAL, c_keyword_qualifier)) { - return 0; - } else if (charat() == '\'') { - if (nextchar() == '\'' && charrel(2) == '\'') { - paint(3, FLAG_STRING); - return paint_py_triple_single(state); - } else if (lastchar() == 'f') { - /* I don't like backtracking like this, but it makes this parse easier */ - state->i--; - paint(1,FLAG_TYPE); - paint_py_format_string(state,'\''); - return 0; - } else { - return paint_py_single_string(state); - } - } else if (!c_keyword_qualifier(lastchar()) && isdigit(charat())) { - paint_py_numeral(state); - return 0; - } else if (charat() != -1) { - skip(); - return 0; - } - break; - case 1: /* multiline """ string */ - return paint_py_triple_double(state); - case 2: /* multiline ''' string */ - return paint_py_triple_single(state); - } - return -1; -} - -/** - * Convert syntax hilighting flag to color code - */ -static const char * flag_to_color(int _flag) { - int flag = _flag & 0xF; - switch (flag) { - case FLAG_KEYWORD: - return COLOR_KEYWORD; - case FLAG_STRING: - return COLOR_STRING; - case FLAG_COMMENT: - return COLOR_COMMENT; - case FLAG_TYPE: - return COLOR_TYPE; - case FLAG_NUMERAL: - return COLOR_NUMERAL; - case FLAG_PRAGMA: - return COLOR_PRAGMA; - case FLAG_DIFFPLUS: - return COLOR_GREEN; - case FLAG_DIFFMINUS: - return COLOR_RED; - case FLAG_SELECT: - return COLOR_FG; -// case FLAG_BOLD: -// return COLOR_BOLD; -// case FLAG_LINK: -// return COLOR_LINK; - case FLAG_ESCAPE: - return COLOR_ESCAPE; - default: - return COLOR_FG; - } -} - -struct syntax_definition { - char * name; - int (*calculate)(struct syntax_state *); -} syntaxes[] = { - {"esh",syn_esh_calculate}, - {"python",syn_py_calculate}, - {NULL, NULL}, -}; - -static struct syntax_definition * syntax; - -int rline_exp_set_syntax(char * name) { - for (struct syntax_definition * s = syntaxes; s->name; ++s) { - if (!strcmp(name,s->name)) { - syntax = s; - return 0; - } - } - return 1; -} - -/** - * Syntax highlighting - * Slimmed down from the bim implementation a bit, - * but generally compatible with the same definitions. - * - * Type highlighting has been removed as the sh highlighter - * didn't use it. This should be made pluggable again, and - * the bim syntax highlighters should probably be broken - * out into dynamically-loaded libraries? - */ -static void recalculate_syntax(line_t * line) { - /* Clear syntax for this line first */ - int line_no = 0; - //int is_original = 1; - while (1) { - for (int i = 0; i < line->actual; ++i) { - line->text[i].flags = 0; - } - - if (!syntax) { - return; - } - - /* Start from the line's stored in initial state */ - struct syntax_state state; - state.line = line; - state.line_no = line_no; - state.state = line->istate; - state.i = 0; - - while (1) { - state.state = syntax->calculate(&state); - - if (state.state != 0) { - /* TODO: Figure out a way to make this work for multiline input */ -#if 0 - if (line_no == -1) return; - if (!is_original) { - redraw_line(line_no); - } - if (line_no + 1 < env->line_count && env->lines[line_no+1]->istate != state.state) { - line_no++; - line = env->lines[line_no]; - line->istate = state.state; - if (env->loading) return; - is_original = 0; - goto _next; - } -#endif - return; - } - } -//_next: -// (void)0; - } -} - -/** - * Set colors - */ -static void set_colors(const char * fg, const char * bg) { - printf("\033[22;23;"); - if (*bg == '@') { - int _bg = atoi(bg+1); - if (_bg < 10) { - printf("4%d;", _bg); - } else { - printf("10%d;", _bg-10); - } - } else { - printf("48;%s;", bg); - } - if (*fg == '@') { - int _fg = atoi(fg+1); - if (_fg < 10) { - printf("3%dm", _fg); - } else { - printf("9%dm", _fg-10); - } - } else { - printf("38;%sm", fg); - } - fflush(stdout); -} - -/** - * Set just the foreground color - * - * (See set_colors above) - */ -static void set_fg_color(const char * fg) { - printf("\033[22;23;"); - if (*fg == '@') { - int _fg = atoi(fg+1); - if (_fg < 10) { - printf("3%dm", _fg); - } else { - printf("9%dm", _fg-10); - } - } else { - printf("38;%sm", fg); - } - fflush(stdout); -} - -/** - * Mostly copied from bim, but with some minor - * alterations and removal of selection support. - */ -static void render_line(void) { - printf("\033[?25l"); - if (show_left_side) { - printf("\033[0m\r%s", prompt); - } else { - printf("\033[0m\r$"); - } - - if (offset && prompt_width_calc) { - set_colors(COLOR_ALT_FG, COLOR_ALT_BG); - printf("\b<"); - } - - int i = 0; /* Offset in char_t line data entries */ - int j = 0; /* Offset in terminal cells */ - - const char * last_color = NULL; - int was_searching = 0; - - /* Set default text colors */ - set_colors(COLOR_FG, COLOR_BG); - - /* - * When we are rendering in the middle of a wide character, - * we render -'s to fill the remaining amount of the - * charater's width - */ - int remainder = 0; - - line_t * line = the_line; - - /* For each character in the line ... */ - while (i < line->actual) { - - /* If there is remaining text... */ - if (remainder) { - - /* If we should be drawing by now... */ - if (j >= offset) { - /* Fill remainder with -'s */ - set_colors(COLOR_ALT_FG, COLOR_ALT_BG); - printf("-"); - set_colors(COLOR_FG, COLOR_BG); - } - - /* One less remaining width cell to fill */ - remainder--; - - /* Terminal offset moves forward */ - j++; - - /* - * If this was the last remaining character, move to - * the next codepoint in the line - */ - if (remainder == 0) { - i++; - } - - continue; - } - - /* Get the next character to draw */ - char_t c = line->text[i]; - - /* If we should be drawing by now... */ - if (j >= offset) { - - /* If this character is going to fall off the edge of the screen... */ - if (j - offset + c.display_width >= width - prompt_width_calc) { - /* We draw this with special colors so it isn't ambiguous */ - set_colors(COLOR_ALT_FG, COLOR_ALT_BG); - - /* If it's wide, draw ---> as needed */ - while (j - offset < width - prompt_width_calc - 1) { - printf("-"); - j++; - } - - /* End the line with a > to show it overflows */ - printf(">"); - set_colors(COLOR_FG, COLOR_BG); - j++; - break; - } - - /* Syntax hilighting */ - const char * color = flag_to_color(c.flags); - if ((c.flags & FLAG_SEARCH) || (c.flags == FLAG_NOTICE)) { - set_colors(COLOR_SEARCH_FG, COLOR_SEARCH_BG); - was_searching = 1; - } else if (was_searching) { - set_colors(color, COLOR_BG); - last_color = color; - } else if (!last_color || strcmp(color, last_color)) { - set_fg_color(color); - last_color = color; - } - - /* Render special characters */ - if (c.codepoint == '\t') { - set_colors(COLOR_ALT_FG, COLOR_ALT_BG); - printf("»"); - for (int i = 1; i < c.display_width; ++i) { - printf("·"); - } - set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); - } else if (c.codepoint < 32) { - /* Codepoints under 32 to get converted to ^@ escapes */ - set_colors(COLOR_ALT_FG, COLOR_ALT_BG); - printf("^%c", '@' + c.codepoint); - set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); - } else if (c.codepoint == 0x7f) { - set_colors(COLOR_ALT_FG, COLOR_ALT_BG); - printf("^?"); - set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); - } else if (c.codepoint > 0x7f && c.codepoint < 0xa0) { - set_colors(COLOR_ALT_FG, COLOR_ALT_BG); - printf("<%2x>", c.codepoint); - set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); - } else if (c.codepoint == 0xa0) { - set_colors(COLOR_ALT_FG, COLOR_ALT_BG); - printf("_"); - set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); - } else if (c.display_width == 8) { - set_colors(COLOR_ALT_FG, COLOR_ALT_BG); - printf("[U+%04x]", c.codepoint); - set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); - } else if (c.display_width == 10) { - set_colors(COLOR_ALT_FG, COLOR_ALT_BG); - printf("[U+%06x]", c.codepoint); - set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); -#if 0 - } else if (c.codepoint == ' ' && i == line->actual - 1) { - /* Special case: space at end of line */ - set_colors(COLOR_ALT_FG, COLOR_ALT_BG); - printf("·"); - set_colors(COLOR_FG, COLOR_BG); -#endif - } else { - /* Normal characters get output */ - char tmp[7]; /* Max six bytes, use 7 to ensure last is always nil */ - to_eight(c.codepoint, tmp); - printf("%s", tmp); - } - - /* Advance the terminal cell offset by the render width of this character */ - j += c.display_width; - - /* Advance to the next character */ - i++; - } else if (c.display_width > 1) { - /* - * If this is a wide character but we aren't ready to render yet, - * we may need to draw some filler text for the remainder of its - * width to ensure we don't jump around when horizontally scrolling - * past wide characters. - */ - remainder = c.display_width - 1; - j++; - } else { - /* Regular character, not ready to draw, advance without doing anything */ - j++; - i++; - } - } - - set_colors(COLOR_FG, COLOR_BG); - - /* Fill to end right hand side */ - for (; j < width + offset - prompt_width_calc; ++j) { - printf(" "); - } - - /* Print right hand side */ - if (show_right_side) { - printf("\033[0m%s", prompt_right); - } -} - -/** - * Create a line_t - */ -static line_t * line_create(void) { - line_t * line = malloc(sizeof(line_t) + sizeof(char_t) * 32); - line->available = 32; - line->actual = 0; - line->istate = 0; - return line; -} - -/** - * Insert a character into a line - */ -static line_t * line_insert(line_t * line, char_t c, int offset) { - - /* If there is not enough space... */ - if (line->actual == line->available) { - /* Expand the line buffer */ - line->available *= 2; - line = realloc(line, sizeof(line_t) + sizeof(char_t) * line->available); - } - - /* If this was not the last character, then shift remaining characters forward. */ - if (offset < line->actual) { - memmove(&line->text[offset+1], &line->text[offset], sizeof(char_t) * (line->actual - offset)); - } - - /* Insert the new character */ - line->text[offset] = c; - - /* There is one new character in the line */ - line->actual += 1; - - if (!loading) { - recalculate_tabs(line); - recalculate_syntax(line); - } - - return line; -} - -/** - * Update terminal size - * - * We don't listen for sigwinch for various reasons... - */ -static void get_size(void) { - struct winsize w; - ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); - full_width = w.ws_col; - if (full_width - prompt_right_width - prompt_width > MINIMUM_SIZE) { - show_right_side = 1; - show_left_side = 1; - prompt_width_calc = prompt_width; - width = full_width - prompt_right_width; - } else { - show_right_side = 0; - if (full_width - prompt_width > MINIMUM_SIZE) { - show_left_side = 1; - prompt_width_calc = prompt_width; - } else { - show_left_side = 0; - prompt_width_calc = 1; - } - width = full_width; - } -} - -/** - * Place the cursor within the line - */ -static void place_cursor_actual(void) { - int x = prompt_width_calc + 1 - offset; - for (int i = 0; i < column; ++i) { - char_t * c = &the_line->text[i]; - x += c->display_width; - } - - if (x > width - 1) { - /* Adjust the offset appropriately to scroll horizontally */ - int diff = x - (width - 1); - offset += diff; - x -= diff; - render_line(); - } - - /* Same for scrolling horizontally to the left */ - if (x < prompt_width_calc + 1) { - int diff = (prompt_width_calc + 1) - x; - offset -= diff; - x += diff; - render_line(); - } - - printf("\033[?25h\033[%dG", x); - fflush(stdout); -} - -/** - * Delete a character - */ -static void line_delete(line_t * line, int offset) { - - /* Can't delete character before start of line. */ - if (offset == 0) return; - - /* If this isn't the last character, we need to move all subsequent characters backwards */ - if (offset < line->actual) { - memmove(&line->text[offset-1], &line->text[offset], sizeof(char_t) * (line->actual - offset)); - } - - /* The line is one character shorter */ - line->actual -= 1; - - if (!loading) { - recalculate_tabs(line); - recalculate_syntax(line); - } -} - -/** - * Backspace from the cursor position - */ -static void delete_at_cursor(void) { - if (column > 0) { - line_delete(the_line, column); - column--; - if (offset > 0) offset--; - } -} - -/** - * Delete whole word - */ -static void delete_word(void) { - if (!the_line->actual) return; - if (!column) return; - - do { - if (column > 0) { - line_delete(the_line, column); - column--; - if (offset > 0) offset--; - } - } while (column && the_line->text[column-1].codepoint != ' '); -} - -/** - * Insert at cursor position - */ -static void insert_char(uint32_t c) { - char_t _c; - _c.codepoint = c; - _c.flags = 0; - _c.display_width = codepoint_width(c); - - the_line = line_insert(the_line, _c, column); - - column++; -} - -/** - * Move cursor left - */ -static void cursor_left(void) { - if (column > 0) column--; - place_cursor_actual(); -} - -/** - * Move cursor right - */ -static void cursor_right(void) { - if (column < the_line->actual) column++; - place_cursor_actual(); -} - -/** - * Move cursor one whole word left - */ -static void word_left(void) { - if (column == 0) return; - column--; - while (column && the_line->text[column].codepoint == ' ') { - column--; - } - while (column > 0) { - if (the_line->text[column-1].codepoint == ' ') break; - column--; - } - place_cursor_actual(); -} - -/** - * Move cursor one whole word right - */ -static void word_right(void) { - while (column < the_line->actual && the_line->text[column].codepoint == ' ') { - column++; - } - while (column < the_line->actual) { - column++; - if (the_line->text[column].codepoint == ' ') break; - } - place_cursor_actual(); -} - -/** - * Move cursor to start of line - */ -static void cursor_home(void) { - column = 0; - place_cursor_actual(); -} - -/* - * Move cursor to end of line - */ -static void cursor_end(void) { - column = the_line->actual; - place_cursor_actual(); -} - -/** - * Temporary buffer for holding utf-8 data - */ -static char temp_buffer[1024]; - -/** - * Cycle to previous history entry - */ -static void history_previous(void) { - if (rline_scroll == 0) { - /* Convert to temporaary buffer */ - unsigned int off = 0; - memset(temp_buffer, 0, sizeof(temp_buffer)); - for (int j = 0; j < the_line->actual; j++) { - char_t c = the_line->text[j]; - off += to_eight(c.codepoint, &temp_buffer[off]); - } - } - - if (rline_scroll < rline_history_count) { - rline_scroll++; - - /* Copy in from history */ - the_line->actual = 0; - column = 0; - loading = 1; - unsigned char * buf = (unsigned char *)rline_history_prev(rline_scroll); - uint32_t istate = 0, c = 0; - for (unsigned int i = 0; i < strlen((char *)buf); ++i) { - if (!decode(&istate, &c, buf[i])) { - insert_char(c); - } - } - loading = 0; - } - /* Set cursor at end */ - column = the_line->actual; - offset = 0; - recalculate_tabs(the_line); - recalculate_syntax(the_line); - render_line(); - place_cursor_actual(); -} - -/** - * Cycle to next history entry - */ -static void history_next(void) { - if (rline_scroll > 1) { - rline_scroll--; - - /* Copy in from history */ - the_line->actual = 0; - column = 0; - loading = 1; - unsigned char * buf = (unsigned char *)rline_history_prev(rline_scroll); - uint32_t istate = 0, c = 0; - for (unsigned int i = 0; i < strlen((char *)buf); ++i) { - if (!decode(&istate, &c, buf[i])) { - insert_char(c); - } - } - loading = 0; - } else if (rline_scroll == 1) { - /* Copy in from temp */ - rline_scroll = 0; - - the_line->actual = 0; - column = 0; - loading = 1; - char * buf = temp_buffer; - uint32_t istate = 0, c = 0; - for (unsigned int i = 0; i < strlen(buf); ++i) { - if (!decode(&istate, &c, buf[i])) { - insert_char(c); - } - } - loading = 0; - } - /* Set cursor at end */ - column = the_line->actual; - offset = 0; - recalculate_tabs(the_line); - recalculate_syntax(the_line); - render_line(); - place_cursor_actual(); -} - -/** - * Handle escape sequences (arrow keys, etc.) - */ -static int handle_escape(int * this_buf, int * timeout, int c) { - if (*timeout >= 1 && this_buf[*timeout-1] == '\033' && c == '\033') { - this_buf[*timeout] = c; - (*timeout)++; - return 1; - } - if (*timeout >= 1 && this_buf[*timeout-1] == '\033' && c != '[') { - *timeout = 0; - _ungetc(c); - return 1; - } - if (*timeout >= 1 && this_buf[*timeout-1] == '\033' && c == '[') { - *timeout = 1; - this_buf[*timeout] = c; - (*timeout)++; - return 0; - } - if (*timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == '[' && - (isdigit(c) || c == ';')) { - this_buf[*timeout] = c; - (*timeout)++; - return 0; - } - if (*timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == '[') { - switch (c) { - case 'A': // up - history_previous(); - break; - case 'B': // down - history_next(); - break; - case 'C': // right - if (this_buf[*timeout-1] == '5') { - word_right(); - } else { - cursor_right(); - } - break; - case 'D': // left - if (this_buf[*timeout-1] == '5') { - word_left(); - } else { - cursor_left(); - } - break; - case 'H': // home - cursor_home(); - break; - case 'F': // end - cursor_end(); - break; - case '~': - switch (this_buf[*timeout-1]) { - case '1': - cursor_home(); - break; - case '3': - /* Delete forward */ - if (column < the_line->actual) { - line_delete(the_line, column+1); - if (offset > 0) offset--; - } - break; - case '4': - cursor_end(); - break; - } - break; - default: - break; - } - *timeout = 0; - return 0; - } - - *timeout = 0; - return 0; -} - -static unsigned int _INTR, _EOF; -static struct termios old; -static void get_initial_termios(void) { - tcgetattr(STDOUT_FILENO, &old); - _INTR = old.c_cc[VINTR]; - _EOF = old.c_cc[VEOF]; -} - -static void set_unbuffered(void) { - struct termios new = old; - new.c_lflag &= (~ICANON & ~ECHO & ~ISIG); - tcsetattr(STDOUT_FILENO, TCSAFLUSH, &new); -} - -static void set_buffered(void) { - tcsetattr(STDOUT_FILENO, TCSAFLUSH, &old); -} - -static int tabbed; - -static void dummy_redraw(rline_context_t * context) { - /* Do nothing */ -} - -/** - * Juggle our buffer with an rline context so we can - * call original rline functions such as a tab-completion callback - * or reverse search. - */ -static void call_rline_func(rline_callback_t func, rline_context_t * context) { - /* Unicode parser state */ - uint32_t istate = 0; - uint32_t c; - - /* Don't let rline draw things */ - context->quiet = 1; - - /* Allocate a temporary buffer */ - context->buffer = malloc(buf_size_max); - memset(context->buffer,0,buf_size_max); - - /* Convert current data to utf-8 */ - unsigned int off = 0; - for (int j = 0; j < the_line->actual; j++) { - if (j == column) { - /* Track cursor position */ - context->offset = off; - } - char_t c = the_line->text[j]; - off += to_eight(c.codepoint, &context->buffer[off]); - } - - /* If the cursor was at the end, the loop above didn't catch it */ - if (column == the_line->actual) context->offset = off; - - /* - * Did we just press tab before this? This is actually managed - * by the tab-completion function. - */ - context->tabbed = tabbed; - - /* Empty callbacks */ - rline_callbacks_t tmp = {0}; - /* - * Because some clients expect this to be set... - * (we don't need it, we'll redraw ourselves later) - */ - tmp.redraw_prompt = dummy_redraw; - - /* Setup context */ - context->callbacks = &tmp; - context->collected = off; - context->buffer[off] = '\0'; - context->requested = 1024; - - /* Reset colors (for tab completion candidates, etc. */ - printf("\033[0m"); - - /* Call the function */ - func(context); - - /* Now convert back */ - loading = 1; - int final_column = 0; - the_line->actual = 0; - column = 0; - istate = 0; - for (int i = 0; i < context->collected; ++i) { - if (i == context->offset) { - final_column = column; - } - if (!decode(&istate, &c, ((unsigned char *)context->buffer)[i])) { - insert_char(c); - } - } - - free(context->buffer); - - /* Position cursor */ - if (context->offset == context->collected) { - column = the_line->actual; - } else { - column = final_column; - } - tabbed = context->tabbed; - loading = 0; - - /* Recalculate + redraw */ - recalculate_tabs(the_line); - recalculate_syntax(the_line); - render_line(); - place_cursor_actual(); -} - -/** - * Perform actual interactive line editing. - * - * This is mostly a reimplementation of bim's - * INSERT mode, but with some cleanups and fixes - * to work on a single line and to add some new - * key bindings we don't have in bim. - */ -static int read_line(void) { - int cin; - uint32_t c; - int timeout = 0; - int this_buf[20]; - uint32_t istate = 0; - int immediate = 1; - - set_colors(COLOR_ALT_FG, COLOR_ALT_BG); - fprintf(stdout, "◄\033[0m"); /* TODO: This could be retrieved from an envvar */ - for (int i = 0; i < full_width - 1; ++i) { - fprintf(stdout, " "); - } - render_line(); - place_cursor_actual(); - - while ((cin = getch(immediate))) { - if (cin == -1) { - immediate = 1; - render_line(); - place_cursor_actual(); - continue; - } - get_size(); - if (!decode(&istate, &c, cin)) { - if (timeout == 0) { - if (c != '\t') tabbed = 0; - if (_INTR && c == _INTR) { - set_colors(COLOR_ALT_FG, COLOR_ALT_BG); - printf("^%c", (int)('@' + c)); - printf("\033[0m"); - loading = 1; - the_line->actual = 0; - column = 0; - insert_char('\n'); - immediate = 0; - raise(SIGINT); - return 1; - } - if (_EOF && c == _EOF) { - if (column == 0 && the_line->actual == 0) { - for (char *_c = rline_exit_string; *_c; ++_c) { - insert_char(*_c); - } - render_line(); - place_cursor_actual(); - return 1; - } else { /* Otherwise act like delete */ - if (column < the_line->actual) { - line_delete(the_line, column+1); - if (offset > 0) offset--; - immediate = 0; - } - continue; - } - } - switch (c) { - case '\033': - if (timeout == 0) { - this_buf[timeout] = c; - timeout++; - } - break; - case DELETE_KEY: - case BACKSPACE_KEY: - delete_at_cursor(); - immediate = 0; - break; - case ENTER_KEY: - /* Finished */ - loading = 1; - column = the_line->actual; - render_line(); - insert_char('\n'); - immediate = 0; - return 1; - case 22: /* ^V */ - /* Don't bother with unicode, just take the next byte */ - place_cursor_actual(); - printf("^\b"); - insert_char(getc(stdin)); - immediate = 0; - break; - case 23: /* ^W */ - delete_word(); - immediate = 0; - break; - case 12: /* ^L - Repaint the whole screen */ - printf("\033[2J\033[H"); - render_line(); - place_cursor_actual(); - break; - case 11: /* ^K - Clear to end */ - the_line->actual = column; - immediate = 0; - break; - case 21: /* ^U - Kill to beginning */ - while (column) { - delete_at_cursor(); - } - immediate = 0; - break; - case '\t': - if (tab_complete_func) { - /* Tab complete */ - rline_context_t context = {0}; - call_rline_func(tab_complete_func, &context); - immediate = 0; - } else { - /* Insert tab character */ - insert_char('\t'); - immediate = 0; - } - break; - case 18: - { - rline_context_t context = {0}; - call_rline_func(rline_reverse_search, &context); - if (!context.cancel) { - return 1; - } - immediate = 0; - } - break; - default: - insert_char(c); - immediate = 0; - break; - } - } else { - if (handle_escape(this_buf,&timeout,c)) { - continue; - } - immediate = 0; - } - } else if (istate == UTF8_REJECT) { - istate = 0; - } - } - return 0; -} - -/** - * Read a line of text with interactive editing. - */ -int rline_experimental(char * buffer, int buf_size) { - get_initial_termios(); - set_unbuffered(); - get_size(); - - column = 0; - offset = 0; - buf_size_max = buf_size; - - char * theme = getenv("RLINE_THEME"); - if (theme && !strcmp(theme,"sunsmoke")) { /* TODO bring back theme tables */ - rline_exp_load_colorscheme_sunsmoke(); - } else { - rline_exp_load_colorscheme_default(); - } - - the_line = line_create(); - loading = 0; - read_line(); - printf("\r\033[?25h\033[0m\n"); - - unsigned int off = 0; - for (int j = 0; j < the_line->actual; j++) { - char_t c = the_line->text[j]; - off += to_eight(c.codepoint, &buffer[off]); - } - - free(the_line); - - set_buffered(); - - return strlen(buffer); -} +/* dummy lib */ void * rline_exp_for_python(void * _stdin, void * _stdout, char * prompt) { @@ -1775,9 +11,12 @@ void * rline_exp_for_python(void * _stdin, void * _stdout, char * prompt) { rline_exp_set_syntax("python"); rline_exit_string = ""; - rline_experimental(buf, 1024); + rline(buf, 1024); rline_history_insert(strdup(buf)); rline_scroll = 0; return buf; } + +char * rline_exit_string; +int rline_history_count; diff --git a/libc/main.c b/libc/main.c index 8458d1b5..89f9c531 100644 --- a/libc/main.c +++ b/libc/main.c @@ -30,8 +30,11 @@ void _exit(int val){ __builtin_unreachable(); } +extern void __make_tls(void); + __attribute__((constructor)) static void _libc_init(void) { + __make_tls(); __stdio_init_buffers(); unsigned int x = 0; diff --git a/libc/pthread/pthread.c b/libc/pthread/pthread.c index 9e03857e..c5a86b2a 100644 --- a/libc/pthread/pthread.c +++ b/libc/pthread/pthread.c @@ -11,6 +11,7 @@ #include #include +#include DEFN_SYSCALL3(clone, SYS_CLONE, uintptr_t, uintptr_t, void *); DEFN_SYSCALL0(gettid, SYS_GETTID); @@ -24,11 +25,39 @@ int gettid() { return syscall_gettid(); /* never fails */ } +struct pthread { + void * (*entry)(void *); + void * arg; +}; + +void * ___tls_get_addr(void* input) { + return NULL; +} + +void __make_tls(void) { + char * tlsSpace = calloc(1,4096); + char ** tlsSelf = (char **)(tlsSpace + 4096 - sizeof(char *)); + *tlsSelf = (char*)tlsSelf; + sysfunc(TOARU_SYS_FUNC_SETGSBASE, (char*[]){(char*)tlsSelf}); +} + +void * __thread_start(void * thread) { + __make_tls(); + struct pthread * me = ((pthread_t *)thread)->ret_val; + ((pthread_t *)thread)->ret_val = 0; + return me->entry(me->arg); +} + int pthread_create(pthread_t * thread, pthread_attr_t * attr, void *(*start_routine)(void *), void * arg) { char * stack = malloc(PTHREAD_STACK_SIZE); uintptr_t stack_top = (uintptr_t)stack + PTHREAD_STACK_SIZE; + thread->stack = stack; - thread->id = clone(stack_top, (uintptr_t)start_routine, arg); + struct pthread * data = malloc(sizeof(struct pthread)); + data->entry = start_routine; + data->arg = arg; + thread->ret_val = data; + thread->id = clone(stack_top, (uintptr_t)__thread_start, thread); return 0; } diff --git a/libc/pthread/pthread_rwlock.c b/libc/pthread/pthread_rwlock.c new file mode 100644 index 00000000..a6f9460c --- /dev/null +++ b/libc/pthread/pthread_rwlock.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ACQUIRE_LOCK() do { while (__sync_lock_test_and_set(&lock->atomic_lock, 0x01)) { syscall_yield(); } } while (0) +#define RELEASE_LOCK() do { __sync_lock_release(&lock->atomic_lock); } while (0) + +int pthread_rwlock_init(pthread_rwlock_t * lock, void * args) { + lock->readers = 0; + lock->atomic_lock = 0; + if (args != NULL) { + fprintf(stderr, "pthread: pthread_rwlock_init arg unsupported\n"); + return 1; + } + return 0; +} + +int pthread_rwlock_wrlock(pthread_rwlock_t * lock) { + ACQUIRE_LOCK(); + while (1) { + if (lock->readers == 0) { + lock->readers = -1; + lock->writerPid = syscall_getpid(); + RELEASE_LOCK(); + return 0; + } + syscall_yield(); + } +} + +int pthread_rwlock_rdlock(pthread_rwlock_t * lock) { + ACQUIRE_LOCK(); + while (1) { + if (lock->readers >= 0) { + lock->readers++; + RELEASE_LOCK(); + return 0; + } + syscall_yield(); + } +} + +int pthread_rwlock_unlock(pthread_rwlock_t * lock) { + ACQUIRE_LOCK(); + if (lock->readers > 0) lock->readers--; + else if (lock->readers < 0) lock->readers = 0; + else fprintf(stderr, "pthread: bad lock state detected\n"); + RELEASE_LOCK(); + return 0; +} + +int pthread_rwlock_destroy(pthread_rwlock_t * lock) { + return 0; +} diff --git a/libc/stdio/printf.c b/libc/stdio/printf.c index 0c37ee7e..915524e1 100644 --- a/libc/stdio/printf.c +++ b/libc/stdio/printf.c @@ -2,11 +2,14 @@ #include #include -static void print_dec(unsigned int value, unsigned int width, char * buf, int * ptr, int fill_zero, int align_right) { +static void print_dec(unsigned int value, unsigned int width, char * buf, int * ptr, int fill_zero, int align_right, int precision) { unsigned int n_width = 1; unsigned int i = 9; + if (precision == -1) precision = 1; - if (value < 10UL) { + if (value == 0) { + n_width = 0; + } else if (value < 10UL) { n_width = 1; } else if (value < 100UL) { n_width = 2; @@ -28,6 +31,8 @@ static void print_dec(unsigned int value, unsigned int width, char * buf, int * n_width = 10; } + if (n_width < (unsigned int)precision) n_width = precision; + int printed = 0; if (align_right) { while (n_width + printed < width) { @@ -212,6 +217,7 @@ int xvasprintf(char * buf, const char * fmt, va_list args) { case 'p': if (!arg_width) { arg_width = 8; + alt = 1; } case 'x': /* Hexadecimal number */ if (alt) { @@ -232,7 +238,6 @@ int xvasprintf(char * buf, const char * fmt, va_list args) { break; case 'i': case 'd': /* Decimal number */ - i = b - buf; { long long val; if (big == 2) { @@ -242,15 +247,14 @@ int xvasprintf(char * buf, const char * fmt, va_list args) { } if (val < 0) { *b++ = '-'; - buf++; val = -val; } else if (always_sign) { *b++ = '+'; - buf++; } - print_dec(val, arg_width, buf, &i, fill_zero, align); + i = b - buf; + print_dec(val, arg_width, buf, &i, fill_zero, align, precision); + b = buf + i; } - b = buf + i; break; case 'u': /* Unsigned ecimal number */ i = b - buf; @@ -261,7 +265,7 @@ int xvasprintf(char * buf, const char * fmt, va_list args) { } else { val = (unsigned long)va_arg(args, unsigned long); } - print_dec(val, arg_width, buf, &i, fill_zero, align); + print_dec(val, arg_width, buf, &i, fill_zero, align, precision); } b = buf + i; break; @@ -269,21 +273,19 @@ int xvasprintf(char * buf, const char * fmt, va_list args) { case 'f': { double val = (double)va_arg(args, double); - i = b - buf; if (val < 0) { *b++ = '-'; - buf++; val = -val; } - print_dec((long)val, arg_width, buf, &i, fill_zero, align); - b = buf + i; i = b - buf; + print_dec((long)val, arg_width, buf, &i, fill_zero, align, 1); + b = buf + i; *b++ = '.'; - buf++; + i = b - buf; for (int j = 0; j < ((precision > -1 && precision < 8) ? precision : 8); ++j) { if ((int)(val * 100000.0) % 100000 == 0 && j != 0) break; val *= 10.0; - print_dec((int)(val) % 10, 0, buf, &i, 0, 0); + print_dec((int)(val) % 10, 0, buf, &i, 0, 0, 1); } b = buf + i; } diff --git a/libc/stdlib/realpath.c b/libc/stdlib/realpath.c index dc8d13c3..5050be0c 100644 --- a/libc/stdlib/realpath.c +++ b/libc/stdlib/realpath.c @@ -41,9 +41,7 @@ char *realpath(const char *path, char *resolved_path) { } if (!resolved_path) { - /* Can't support this yet. */ - errno = -EINVAL; - return NULL; + resolved_path = malloc(PATH_MAX+1); } /* If we're lucky, we can do this with no allocations, so let's start here... */ diff --git a/libc/stdlib/strtoul.c b/libc/stdlib/strtoul.c index 52b1d4db..9f5da791 100644 --- a/libc/stdlib/strtoul.c +++ b/libc/stdlib/strtoul.c @@ -6,12 +6,11 @@ static int is_valid(int base, char c) { if (c < '0') return 0; if (base <= 10) { - return c < ('0' + base - 1); + return c < ('0' + base); } - if (c > '9' && c < 'a') return 0; - if (c > 'a' + (base - 10) && c < 'A') return 1; - if (c > 'A' + (base - 10)) return 1; + if (c >= 'a' && c < 'a' + (base - 10)) return 1; + if (c >= 'A' && c < 'A' + (base - 10)) return 1; if (c >= '0' && c <= '9') return 1; return 0; } diff --git a/libc/sys/network.c b/libc/sys/network.c index 0c149cba..28eef78e 100644 --- a/libc/sys/network.c +++ b/libc/sys/network.c @@ -123,4 +123,7 @@ int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t return -1; } - +int shutdown(int sockfd, int how) { + UNIMPLEMENTED; + return -1; +} diff --git a/libc/time/clock_gettime.c b/libc/time/clock_gettime.c new file mode 100644 index 00000000..5d69a0d6 --- /dev/null +++ b/libc/time/clock_gettime.c @@ -0,0 +1,17 @@ +#include +#include +#include + +int clock_gettime(clockid_t clk_id, struct timespec *tp) { + if (clk_id < 0 || clk_id > 1) { + errno = EINVAL; + return -1; + } + struct timeval t; + gettimeofday(&t, NULL); + + tp->tv_sec = t.tv_sec; + tp->tv_nsec = t.tv_usec * 1000; + + return 0; +} diff --git a/libc/unistd/sync.c b/libc/unistd/sync.c new file mode 100644 index 00000000..784990d2 --- /dev/null +++ b/libc/unistd/sync.c @@ -0,0 +1,8 @@ +#include +#include +#include + +void sync(void) { + /* TODO */ +} + diff --git a/libc/unistd/truncate.c b/libc/unistd/truncate.c new file mode 100644 index 00000000..2016fda9 --- /dev/null +++ b/libc/unistd/truncate.c @@ -0,0 +1,9 @@ +#include +#include +#include + +int truncate(const char * path, off_t length) { + errno = EINVAL; + return -1; +} + diff --git a/linker/linker.c b/linker/linker.c index cd5c6a61..a404f168 100644 --- a/linker/linker.c +++ b/linker/linker.c @@ -75,6 +75,8 @@ typedef int (*entry_point_t)(int, char *[], char**); static hashmap_t * dumb_symbol_table; static hashmap_t * glob_dat; static hashmap_t * objects_map; +static hashmap_t * tls_map; +static size_t current_tls_offset = 0; /* Used for dlerror */ static char * last_error = NULL; @@ -388,6 +390,7 @@ static int need_symbol_for_type(unsigned char type) { case 5: case 6: case 7: + case 14: return 1; default: return 0; @@ -471,6 +474,20 @@ static int object_relocate(elf_t * object) { case 5: /* COPY */ memcpy((void *)(table->r_offset + object->base), (void *)x, sym->st_size); break; + case 14: /* TLS_TPOFF */ + x = *((ssize_t *)(table->r_offset + object->base)); + if (!hashmap_has(tls_map, symname)) { + if (!sym->st_size) { + fprintf(stderr, "Haven't placed %s in static TLS yet but don't know its size?\n", symname); + } + current_tls_offset += sym->st_size; /* TODO alignment restrictions */ + hashmap_set(tls_map, symname, (void*)(current_tls_offset)); + x -= current_tls_offset; + } else { + x -= (size_t)hashmap_get(tls_map, symname); + } + memcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t)); + break; default: TRACE_LD("Unknown relocation type: %d", type); } @@ -607,6 +624,58 @@ static void * do_actual_load(const char * filename, elf_t * lib, int flags) { return (void *)lib; } +static uintptr_t end_addr = 0; +/** + * Half loads an object using the dumb allocator and performs dependency + * resolution. This is a separate process from do_actual_load and dlopen_ld + * to avoid problems with malloc and library functions while loading. + * Preloaded objects will be fully loaded everything is relocated. + */ +static elf_t * preload(hashmap_t * libs, list_t * load_libs, char * lib_name) { + /* Find and open the library */ + elf_t * lib = open_object(lib_name); + + if (!lib) { + fprintf(stderr, "Failed to load dependency '%s'.\n", lib_name); + return NULL; + } + + /* Skip already loaded libraries */ + if (lib->loaded) return lib; + + /* Mark this library available */ + hashmap_set(libs, lib_name, lib); + + TRACE_LD("Loading %s at 0x%x", lib_name, end_addr); + + /* Adjust dumb allocator */ + while (end_addr & 0xFFF) { + end_addr++; + } + + /* Load PHDRs */ + end_addr = object_load(lib, end_addr); + + /* Extract information */ + object_postload(lib); + + /* Mark loaded */ + lib->loaded = 1; + + /* Verify dependencies are loaded before we relocate */ + foreach(node, lib->dependencies) { + if (!hashmap_has(libs, node->value)) { + TRACE_LD("Need unloaded dependency %s", node->value); + preload(libs, load_libs, node->value); + } + } + + /* Add this to the (forward scan) list of libraries to finish loading */ + list_insert(load_libs, lib); + + return lib; +} + /* exposed dlopen() method */ static void * dlopen_ld(const char * filename, int flags) { TRACE_LD("dlopen(%s,0x%x)", filename, flags); @@ -699,6 +768,7 @@ int main(int argc, char * argv[]) { dumb_symbol_table = hashmap_create(10); glob_dat = hashmap_create(10); objects_map = hashmap_create(10); + tls_map = hashmap_create(10); /* Setup symbols for built-in exports */ ld_exports_t * ex = ld_builtin_exports; @@ -731,7 +801,7 @@ int main(int argc, char * argv[]) { } /* Load the main object */ - uintptr_t end_addr = object_load(main_obj, 0x0); + end_addr = object_load(main_obj, 0x0); object_postload(main_obj); object_find_copy_relocations(main_obj); @@ -742,33 +812,36 @@ int main(int argc, char * argv[]) { end_addr++; } - list_t * ctor_libs = list_create(); - list_t * init_libs = list_create(); - + /* Load dependent libraries, recursively. */ TRACE_LD("Loading dependencies."); + list_t * load_libs = list_create(); node_t * item; while ((item = list_pop(main_obj->dependencies))) { - while (end_addr & 0xFFF) { - end_addr++; - } - char * lib_name = item->value; - /* Reject libg.so */ + + /* Skip libg.so which is a fake library that doesn't really exist. + * XXX: Only binaries should depend on this I think? */ if (!strcmp(lib_name, "libg.so")) goto nope; - elf_t * lib = open_object(lib_name); - if (!lib) { - fprintf(stderr, "Failed to load dependency '%s'.\n", lib_name); - return 1; - } - hashmap_set(libs, lib_name, lib); + /* Preload library */ + elf_t * lib = preload(libs, load_libs, lib_name); + + /* Failed to load */ + if (!lib) return 1; + +nope: + free(item); + } - TRACE_LD("Loading %s at 0x%x", lib_name, end_addr); - end_addr = object_load(lib, end_addr); - object_postload(lib); - TRACE_LD("Relocating %s", lib_name); + list_t * ctor_libs = list_create(); + list_t * init_libs = list_create(); + while ((item = list_dequeue(load_libs))) { + elf_t * lib = item->value; + + /* Complete relocation */ object_relocate(lib); + /* Close the underlying file */ fclose(lib->file); /* Store constructors for later execution */ @@ -779,9 +852,6 @@ int main(int argc, char * argv[]) { list_insert(init_libs, lib); } - lib->loaded = 1; - -nope: free(item); } diff --git a/modules/vmware.c b/modules/vmware.c index 2e1c6a02..65cb373c 100644 --- a/modules/vmware.c +++ b/modules/vmware.c @@ -140,7 +140,7 @@ static void mouse_absolute(void) { vmware_send(&cmd); } -volatile int8_t vmware_mouse_byte; +volatile int8_t vmware_mouse_byte = 0; static void vmware_mouse(void) { /* unused, but we need to read the fake mouse event bytes from the PS/2 device. */ diff --git a/util/auto-dep.py b/util/auto-dep.py index dbe00f04..773c38ab 100755 --- a/util/auto-dep.py +++ b/util/auto-dep.py @@ -19,10 +19,11 @@ class Classifier(object): '': (None, '-ltoaru_pex', []), '': (None, '-ltoaru_auth', []), '': (None, '-ltoaru_graphics', []), + '': (None, '-ltoaru_inflate', []), '': (None, '-ltoaru_drawstring', ['']), '': (None, '-ltoaru_jpeg', ['']), + '': (None, '-ltoaru_png', ['','']), '': (None, '-ltoaru_rline', ['']), - '': (None, '-ltoaru_rline_exp', ['']), '': (None, '-ltoaru_confreader', ['']), '': (None, '-ltoaru_markup', ['']), '': (None, '-ltoaru_json', ['']), @@ -34,6 +35,8 @@ class Classifier(object): '': (None, '-ltoaru_menu', ['', '', '', '', '']), '': (None, '-ltoaru_textregion', ['', '','', '']), '': (None, '-ltoaru_button', ['','', '']), + # Kuroko + '': (None, '-lkuroko', []), # OPTIONAL third-party libraries, for extensions / ports '': ('freetype2', '-lfreetype', []), '': ('pixman-1', '-lpixman-1', []), @@ -99,6 +102,8 @@ def todep(name): name = name.replace("-l","",1) if name.startswith('toaru'): return (True, "%s/lib%s.so" % ('base/lib', name)) + elif name.startswith('kuroko'): + return (True, "%s/lib%s.so" % ('base/lib', name)) else: return (True, "%s/lib%s.so" % ('base/usr/lib', name)) else: @@ -171,4 +176,15 @@ def toheader(name): libraries=" ".join([x for x in _libs]), includes=" ".join([x for x in c.includes if x is not None]) )) + elif command == "--makekurokomod": + libname = os.path.basename(filename).replace(".c","").replace("module_","") + _libs = [x for x in c.libs if not x.startswith('-ltoaru_') or x.replace("-ltoaru_","") != libname] + print("base/lib/kuroko/{lib}.so: {source} {headers} util/auto-dep.py | {libraryfiles} $(LC)\n\t$(CC) $(CFLAGS) -DDEBUG {includes} -shared -fPIC -o $@ $< {libraries}".format( + lib=libname, + source=filename, + headers=" ".join([toheader(x) for x in c.libs]), + libraryfiles=" ".join([todep(x)[1] for x in _libs]), + libraries=" ".join([x for x in _libs]), + includes=" ".join([x for x in c.includes if x is not None]) + )) diff --git a/util/build-travis.sh b/util/build-in-docker.sh similarity index 100% rename from util/build-travis.sh rename to util/build-in-docker.sh diff --git a/util/createramdisk.py b/util/createramdisk.py index 08f88b7c..1da24935 100644 --- a/util/createramdisk.py +++ b/util/createramdisk.py @@ -55,6 +55,7 @@ def file_filter(tarinfo): ramdisk.add('libc',arcname='/src/libc',filter=file_filter) ramdisk.add('boot',arcname='/src/boot',filter=file_filter) ramdisk.add('modules',arcname='/src/modules',filter=file_filter) + ramdisk.add('kuroko/src',arcname='/src/kuroko',filter=file_filter) if os.path.exists('tags'): ramdisk.add('tags',arcname='/src/tags',filter=file_filter) ramdisk.add('util/build-the-world.py',arcname='/usr/bin/build-the-world.py',filter=file_filter) diff --git a/util/generate-release-notes.sh b/util/generate-release-notes.sh new file mode 100644 index 00000000..010600cf --- /dev/null +++ b/util/generate-release-notes.sh @@ -0,0 +1,39 @@ +#!/bin/bash +VERSION=$(git describe --exact-match --tags) +LAST=$(git describe --abbrev=0 --tags ${VERSION}^) +CHANGELOG=$(git log --pretty=format:%s ${LAST}..HEAD | grep ':' | sed -re 's/(.*)\:/- \`\1\`\:/' | sort) +cat <= png_size: + print(f"{i}: keeping png") + os.remove(i) + else: + print(f"{i}: keeping bmp") + os.remove(i.replace(".bmp",".png")) + +