From 1b383e6884d12d4098c4b6631e9c3e10cba0ffaf Mon Sep 17 00:00:00 2001
From: pommicket <pommicket@gmail.com>
Date: Fri, 3 Mar 2023 20:34:16 -0500
Subject: configurable hover/highlight key + better key stuff

scancodes are no longer used for anything
---
 buffer.c         |   6 +--
 config.c         | 137 ++++++++++++++++++++++++++++++++++---------------------
 ide-highlights.c |   4 +-
 ide-hover.c      |   4 +-
 main.c           |  26 ++++-------
 ted.c            |  49 ++++++++++++++++++--
 ted.cfg          |   6 ++-
 ted.h            |  38 +++++++++++----
 8 files changed, 181 insertions(+), 89 deletions(-)

diff --git a/buffer.c b/buffer.c
index bb7fabf..12295e7 100644
--- a/buffer.c
+++ b/buffer.c
@@ -2612,7 +2612,7 @@ bool buffer_handle_click(Ted *ted, TextBuffer *buffer, vec2 click, u8 times) {
 			ted_switch_to_buffer(ted, buffer);
 		}
 		if (buffer == ted->active_buffer) {
-			switch (ted->key_modifier) {
+			switch (ted_get_key_modifier(ted)) {
 			case KEY_MODIFIER_SHIFT:
 				// select to position
 				buffer_select_to_pos(buffer, buffer_pos);
@@ -2624,9 +2624,9 @@ bool buffer_handle_click(Ted *ted, TextBuffer *buffer, vec2 click, u8 times) {
 					// go to definition/declaration
 					buffer_cursor_move_to_pos(buffer, buffer_pos);
 					GotoType type = GOTO_DEFINITION;
-					if (ted->key_modifier & KEY_MODIFIER_SHIFT)
+					if (ted_is_shift_down(ted))
 						type = GOTO_DECLARATION;
-					else if (ted->key_modifier & KEY_MODIFIER_ALT)
+					else if (ted_is_alt_down(ted))
 						type = GOTO_TYPE_DEFINITION;
 					buffer_goto_word_at_cursor(buffer, type);
 				}
diff --git a/config.c b/config.c
index 1fd2675..46dc258 100644
--- a/config.c
+++ b/config.c
@@ -45,6 +45,11 @@ typedef struct {
 	size_t buf_size;
 	bool per_language;
 } SettingString;
+typedef struct {
+	const char *name;
+	const KeyCombo *control;
+	bool per_language;
+} SettingKeyCombo;
 
 typedef enum {
 	SETTING_BOOL = 1,
@@ -52,7 +57,8 @@ typedef enum {
 	SETTING_U16,
 	SETTING_U32,
 	SETTING_FLOAT,
-	SETTING_STRING
+	SETTING_STRING,
+	SETTING_KEY_COMBO
 } SettingType;
 typedef struct {
 	SettingType type;
@@ -65,12 +71,13 @@ typedef struct {
 		SettingU32 _u32;
 		SettingFloat _float;
 		SettingString _string;
+		SettingKeyCombo _key;
 	} u;
 } SettingAny;
 
 // core settings
 static const Settings settings_zero = {0};
-static SettingBool const settings_bool[] = {
+static const SettingBool settings_bool[] = {
 	{"auto-indent", &settings_zero.auto_indent, true},
 	{"auto-add-newline", &settings_zero.auto_add_newline, true},
 	{"auto-reload", &settings_zero.auto_reload, true},
@@ -91,7 +98,7 @@ static SettingBool const settings_bool[] = {
 	{"highlight-enabled", &settings_zero.highlight_enabled, true},
 	{"highlight-auto", &settings_zero.highlight_auto, true},
 };
-static SettingU8 const settings_u8[] = {
+static const SettingU8 settings_u8[] = {
 	{"tab-width", &settings_zero.tab_width, 1, 100, true},
 	{"cursor-width", &settings_zero.cursor_width, 1, 100, true},
 	{"undo-save-time", &settings_zero.undo_save_time, 1, 200, true},
@@ -100,23 +107,23 @@ static SettingU8 const settings_u8[] = {
 	{"scrolloff", &settings_zero.scrolloff, 1, 100, true},
 	{"tags-max-depth", &settings_zero.tags_max_depth, 1, 100, false},
 };
-static SettingU16 const settings_u16[] = {
+static const SettingU16 settings_u16[] = {
 	{"text-size", &settings_zero.text_size, TEXT_SIZE_MIN, TEXT_SIZE_MAX, false},
 	{"max-menu-width", &settings_zero.max_menu_width, 10, U16_MAX, false},
 	{"error-display-time", &settings_zero.error_display_time, 0, U16_MAX, false},
 	{"framerate-cap", &settings_zero.framerate_cap, 3, 1000, false},
 };
-static SettingU32 const settings_u32[] = {
+static const SettingU32 settings_u32[] = {
 	{"max-file-size", &settings_zero.max_file_size, 100, 2000000000, false},
 	{"max-file-size-view-only", &settings_zero.max_file_size_view_only, 100, 2000000000, false},
 };
-static SettingFloat const settings_float[] = {
+static const SettingFloat settings_float[] = {
 	{"cursor-blink-time-on", &settings_zero.cursor_blink_time_on, 0, 1000, true},
 	{"cursor-blink-time-off", &settings_zero.cursor_blink_time_off, 0, 1000, true},
 	{"hover-time", &settings_zero.hover_time, 0, INFINITY, true},
 	{"ctrl-scroll-adjust-text-size", &settings_zero.ctrl_scroll_adjust_text_size, -10, 10, true},
 };
-static SettingString const settings_string[] = {
+static const SettingString settings_string[] = {
 	{"build-default-command", settings_zero.build_default_command, sizeof settings_zero.build_default_command, true},
 	{"build-command", settings_zero.build_command, sizeof settings_zero.build_command, true},
 	{"root-identifiers", settings_zero.root_identifiers, sizeof settings_zero.root_identifiers, true},
@@ -125,6 +132,10 @@ static SettingString const settings_string[] = {
 	{"comment-start", settings_zero.comment_start, sizeof settings_zero.comment_start, true},
 	{"comment-end", settings_zero.comment_end, sizeof settings_zero.comment_end, true},
 };
+static const SettingKeyCombo settings_key_combo[] = {
+	{"hover-key", &settings_zero.hover_key, true},
+	{"highlight-key", &settings_zero.highlight_key, true},
+};
 
 
 static void setting_bool_set(Settings *settings, const SettingBool *set, bool value) {
@@ -150,6 +161,10 @@ static void setting_string_set(Settings *settings, const SettingString *set, con
 	char *control = (char *)settings + (set->control - (char*)&settings_zero);
 	str_cpy(control, set->buf_size, value);
 }
+static void setting_key_combo_set(Settings *settings, const SettingKeyCombo *set, KeyCombo value) {
+	KeyCombo *control = (KeyCombo *)((char *)settings + ((char*)set->control - (char*)&settings_zero));
+	*control = value;
+}
 
 
 typedef struct {
@@ -244,29 +259,68 @@ static void config_part_free(ConfigPart *part) {
 	memset(part, 0, sizeof *part);
 }
 
+static SDL_Keycode config_parse_key(ConfigReader *cfg, const char *str) {
+	SDL_Keycode keycode = SDL_GetKeyFromName(str);
+	if (keycode != SDLK_UNKNOWN)
+		return keycode;
+	typedef struct {
+		const char *keyname1;
+		const char *keyname2; // alternate key name
+		SDL_Keycode keycode;
+	} KeyName;
+	static KeyName const key_names[] = {
+		{"X1", NULL, KEYCODE_X1},
+		{"X2", NULL, KEYCODE_X2},
+		{"Enter", NULL, SDLK_RETURN},
+		{"Equals", "Equal", SDLK_EQUALS},
+	};
+	for (size_t i = 0; i < arr_count(key_names); ++i) {
+		KeyName const *k = &key_names[i];
+		if (streq_case_insensitive(str, k->keyname1) || (k->keyname2 && streq_case_insensitive(str, k->keyname2))) {
+			keycode = k->keycode;
+			break;
+		}
+	}
+	if (keycode != SDLK_UNKNOWN)
+		return keycode;
+		
+	if (isdigit(str[0])) { // direct keycode numbers, e.g. Ctrl+24 or Ctrl+08
+		char *endp;
+		long n = strtol(str, &endp, 10);
+		if (*endp == '\0' && n > 0) {
+			return (SDL_Keycode)n;
+		} else {
+			config_err(cfg, "Invalid keycode number: %s", str);
+			return 0;
+		}
+	} else {
+		config_err(cfg, "Unrecognized key name: %s.", str);
+		return 0;
+	}
+}
 // Returns the key combination described by str.
-static u32 config_parse_key_combo(ConfigReader *cfg, const char *str) {
+static KeyCombo config_parse_key_combo(ConfigReader *cfg, const char *str) {
 	u32 modifier = 0;
 	// read modifier
 	while (true) {
 		if (str_has_prefix(str, "Ctrl+")) {
 			if (modifier & KEY_MODIFIER_CTRL) {
 				config_err(cfg, "Ctrl+ written twice");
-				return 0;
+				return (KeyCombo){0};
 			}
 			modifier |= KEY_MODIFIER_CTRL;
 			str += strlen("Ctrl+");
 		} else if (str_has_prefix(str, "Shift+")) {
 			if (modifier & KEY_MODIFIER_SHIFT) {
 				config_err(cfg, "Shift+ written twice");
-				return 0;
+				return (KeyCombo){0};
 			}
 			modifier |= KEY_MODIFIER_SHIFT;
 			str += strlen("Shift+");
 		} else if (str_has_prefix(str, "Alt+")) {
 			if (modifier & KEY_MODIFIER_ALT) {
 				config_err(cfg, "Alt+ written twice");
-				return 0;
+				return (KeyCombo){0};
 			}
 			modifier |= KEY_MODIFIER_ALT;
 			str += strlen("Alt+");
@@ -274,42 +328,9 @@ static u32 config_parse_key_combo(ConfigReader *cfg, const char *str) {
 	}
 
 	// read key
-	SDL_Keycode keycode = SDL_GetKeyFromName(str);
-	if (keycode == SDLK_UNKNOWN) {
-		typedef struct {
-			const char *keyname1;
-			const char *keyname2; // alternate key name
-			SDL_Keycode keycode;
-		} KeyName;
-		static KeyName const key_names[] = {
-			{"X1", NULL, KEYCODE_X1},
-			{"X2", NULL, KEYCODE_X2},
-			{"Enter", NULL, SDLK_RETURN},
-			{"Equals", "Equal", SDLK_EQUALS},
-		};
-		for (size_t i = 0; i < arr_count(key_names); ++i) {
-			KeyName const *k = &key_names[i];
-			if (streq_case_insensitive(str, k->keyname1) || (k->keyname2 && streq_case_insensitive(str, k->keyname2))) {
-				keycode = k->keycode;
-				break;
-			}
-		}
-		if (keycode == SDLK_UNKNOWN) {
-			if (isdigit(str[0])) { // direct keycode numbers, e.g. Ctrl+24 or Ctrl+08
-				char *endp;
-				long n = strtol(str, &endp, 10);
-				if (*endp == '\0' && n > 0) {
-					keycode = (SDL_Keycode)n;
-				} else {
-					config_err(cfg, "Invalid keycode number: %s", str);
-					return 0;
-				}
-			} else {
-				config_err(cfg, "Unrecognized key name: %s.", str);
-				return 0;
-			}
-		}
-	}
+	SDL_Keycode keycode = config_parse_key(cfg, str);
+	if (keycode == SDLK_UNKNOWN)
+		return (KeyCombo){0};
 	return KEY_COMBO(modifier, keycode);
 }
 
@@ -430,6 +451,13 @@ static void config_init_settings(void) {
 		opt->u._string = settings_string[i];
 		++opt;
 	}
+	for (size_t i = 0; i < arr_count(settings_key_combo); ++i) {
+		opt->type = SETTING_KEY_COMBO;
+		opt->name = settings_key_combo[i].name;
+		opt->per_language = settings_key_combo[i].per_language;
+		opt->u._key = settings_key_combo[i];
+		++opt;
+	}
 	settings_initialized = true;
 }
 
@@ -753,7 +781,7 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings,
 	} break;
 	case SECTION_KEYBOARD: {
 		// lines like Ctrl+Down = 10 :down
-		u32 key_combo = config_parse_key_combo(cfg, key);
+		KeyCombo key_combo = config_parse_key_combo(cfg, key);
 		KeyAction action = {0};
 		action.key_combo = key_combo;
 		llong argument = 1; // default argument = 1
@@ -798,7 +826,7 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings,
 			bool have = false;
 			// check if we already have an action for this key combo
 			arr_foreach_ptr(settings->key_actions, KeyAction, act) {
-				if (act->key_combo == key_combo) {
+				if (act->key_combo.value == key_combo.value) {
 					*act = action;
 					have = true;
 					break;
@@ -961,6 +989,13 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings,
 					setting_string_set(settings, setting, value);
 				}
 			} break;
+			case SETTING_KEY_COMBO: {
+				const SettingKeyCombo *setting = &setting_any->u._key;
+				KeyCombo combo = config_parse_key_combo(cfg, value);
+				if (combo.value) {
+					setting_key_combo_set(settings, setting, combo);
+				}
+			} break;
 			}
 		}
 		
@@ -970,9 +1005,9 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings,
 
 static int key_action_qsort_cmp_combo(const void *av, const void *bv) {
 	const KeyAction *a = av, *b = bv;
-	if (a->key_combo < b->key_combo)
+	if (a->key_combo.value < b->key_combo.value)
 		return -1;
-	if (a->key_combo > b->key_combo)
+	if (a->key_combo.value > b->key_combo.value)
 		return 1;
 	return 0;
 }
diff --git a/ide-highlights.c b/ide-highlights.c
index 4188f4e..8c58657 100644
--- a/ide-highlights.c
+++ b/ide-highlights.c
@@ -52,9 +52,9 @@ void highlights_frame(Ted *ted) {
 		return;
 	}
 	const Settings *settings = buffer_settings(buffer);
-	bool f2_down = SDL_GetKeyboardState(NULL)[SDL_SCANCODE_F2];
+	bool key_down = ted_is_key_combo_down(ted, settings->highlight_key);
 	if (!settings->highlight_enabled
-		|| (!settings->highlight_auto && !f2_down)) {
+		|| (!settings->highlight_auto && !key_down)) {
 		highlights_close(ted);
 		return;
 	}
diff --git a/ide-hover.c b/ide-hover.c
index b88a960..4c6c5f2 100644
--- a/ide-hover.c
+++ b/ide-hover.c
@@ -89,9 +89,9 @@ void hover_frame(Ted *ted, double dt) {
 		return;
 	Hover *hover = &ted->hover;
 	
-	bool f1_down = SDL_GetKeyboardState(NULL)[SDL_SCANCODE_F1];
+	bool key_down = ted_is_key_combo_down(ted, settings->hover_key);
 	
-	bool open_hover = f1_down || hover->time >= settings->hover_time;
+	bool open_hover = key_down || hover->time >= settings->hover_time;
 	
 	hover->time += dt;
 	
diff --git a/main.c b/main.c
index 8d15c1b..974bc8a 100644
--- a/main.c
+++ b/main.c
@@ -5,7 +5,6 @@ FUTURE FEATURES:
 - better undo chaining (dechain on backspace?)
 - manual.md
 - regenerate tags for completion too if there are no results
-- make go-to-definition/hover/highlight modifier key configurable
 - font setting & support for multiple fonts to cover more characters
 - support for variable-width fonts
 - robust find (results shouldn't move around when you type things)
@@ -602,28 +601,21 @@ int main(int argc, char **argv) {
 		double frame_start = time_get_seconds();
 		ted->frame_time = frame_start;
 
-		SDL_Event event;
-		Uint8 const *keyboard_state = SDL_GetKeyboardState(NULL);
-		
+		SDL_PumpEvents();
+		u32 key_modifier = ted_get_key_modifier(ted);		
 		{ // get mouse position
 			int mouse_x = 0, mouse_y = 0;
 			ted->mouse_state = SDL_GetMouseState(&mouse_x, &mouse_y);
 			ted->mouse_pos = Vec2((float)mouse_x, (float)mouse_y);
 		}
-		bool ctrl_down = keyboard_state[SDL_SCANCODE_LCTRL] || keyboard_state[SDL_SCANCODE_RCTRL];
-		bool shift_down = keyboard_state[SDL_SCANCODE_LSHIFT] || keyboard_state[SDL_SCANCODE_RSHIFT];
-		bool alt_down = keyboard_state[SDL_SCANCODE_LALT] || keyboard_state[SDL_SCANCODE_RALT];
 
 		memset(ted->nmouse_clicks, 0, sizeof ted->nmouse_clicks);
 		memset(ted->nmouse_releases, 0, sizeof ted->nmouse_releases);
 		ted->scroll_total_x = ted->scroll_total_y = 0;
 
 		ted_update_window_dimensions(ted);
-		u32 key_modifier = (u32)ctrl_down << KEY_MODIFIER_CTRL_BIT
-				| (u32)shift_down << KEY_MODIFIER_SHIFT_BIT
-				| (u32)alt_down << KEY_MODIFIER_ALT_BIT;
-		ted->key_modifier = key_modifier;
 		
+		SDL_Event event;
 		while (SDL_PollEvent(&event)) {
 			TextBuffer *buffer = ted->active_buffer;
 			
@@ -632,7 +624,7 @@ int main(int argc, char **argv) {
 				command_execute(ted, CMD_QUIT, 1);
 				break;
 			case SDL_MOUSEWHEEL: {
-				if (ctrl_down) {
+				if (ted_is_ctrl_down(ted)) {
 					// adjust text size with ctrl+scroll
 					Settings *settings = ted_active_settings(ted);
 					scroll_wheel_text_size_change += settings->ctrl_scroll_adjust_text_size * event.wheel.preciseY;
@@ -840,16 +832,16 @@ int main(int argc, char **argv) {
 		TextBuffer *active_buffer = ted->active_buffer;
 		if (active_buffer && key_modifier == KEY_MODIFIER_ALT) {
 			// alt + arrow keys to scroll
-			double scroll_speed = 20.0;
+			double scroll_speed = 40.0;
 			double scroll_amount_x = scroll_speed * frame_dt * 1.5; // characters are taller than they are wide
 			double scroll_amount_y = scroll_speed * frame_dt;
-			if (keyboard_state[SDL_SCANCODE_UP])
+			if (ted_is_key_down(ted, SDLK_UP))
 				buffer_scroll(active_buffer, 0, -scroll_amount_y);
-			if (keyboard_state[SDL_SCANCODE_DOWN])
+			if (ted_is_key_down(ted, SDLK_DOWN))
 				buffer_scroll(active_buffer, 0, +scroll_amount_y);
-			if (keyboard_state[SDL_SCANCODE_LEFT])
+			if (ted_is_key_down(ted, SDLK_LEFT))
 				buffer_scroll(active_buffer, -scroll_amount_x, 0);
-			if (keyboard_state[SDL_SCANCODE_RIGHT])
+			if (ted_is_key_down(ted, SDLK_RIGHT))
 				buffer_scroll(active_buffer, +scroll_amount_x, 0);
 		}
 		
diff --git a/ted.c b/ted.c
index d5335d4..4eb43be 100644
--- a/ted.c
+++ b/ted.c
@@ -35,6 +35,49 @@ static void ted_vset_message(Ted *ted, MessageType type, const char *fmt, va_lis
 	}
 }
 
+bool ted_is_key_down(Ted *ted, SDL_Keycode key) {
+	// not currently used but there might be a reason for it in the future
+	(void)ted;
+	
+	const Uint8 *kbd_state = SDL_GetKeyboardState(NULL);
+	for (int i = 0; i < SDL_NUM_SCANCODES; ++i) {
+		if (kbd_state[i] && SDL_GetKeyFromScancode((SDL_Scancode)i) == key) {
+			return true;
+		}
+	}
+	return false;
+}
+
+bool ted_is_key_combo_down(Ted *ted, KeyCombo combo) {
+	if (!ted_is_key_down(ted, KEY_COMBO_KEY(combo)))
+		return false;
+	if (KEY_COMBO_MODIFIER(combo) != ted_get_key_modifier(ted))
+		return false;
+	return true;
+}
+
+bool ted_is_ctrl_down(Ted *ted) {
+	return ted_is_key_down(ted, SDLK_LCTRL) || ted_is_key_down(ted, SDLK_RCTRL);
+}
+
+bool ted_is_shift_down(Ted *ted) {
+	return ted_is_key_down(ted, SDLK_LSHIFT) || ted_is_key_down(ted, SDLK_RSHIFT);
+}
+
+bool ted_is_alt_down(Ted *ted) {
+	return ted_is_key_down(ted, SDLK_LALT) || ted_is_key_down(ted, SDLK_RALT);
+}
+
+u32 ted_get_key_modifier(Ted *ted) {
+	u32 ctrl_down = ted_is_ctrl_down(ted);
+	u32 shift_down = ted_is_shift_down(ted);
+	u32 alt_down = ted_is_alt_down(ted);
+	return ctrl_down << KEY_MODIFIER_CTRL_BIT
+		| shift_down << KEY_MODIFIER_SHIFT_BIT
+		| alt_down << KEY_MODIFIER_ALT_BIT;
+}
+
+
 void ted_set_message(Ted *ted, MessageType type, const char *fmt, ...) {
 	va_list args;
 	va_start(args, fmt);
@@ -559,7 +602,7 @@ void ted_load_configs(Ted *ted, bool reloading) {
 }
 
 void ted_press_key(Ted *ted, SDL_Keycode keycode, SDL_Keymod modifier) {
-	u32 key_combo = KEY_COMBO(
+	KeyCombo key_combo = KEY_COMBO(
 		(u32)((modifier & (KMOD_LCTRL|KMOD_RCTRL)) != 0) << KEY_MODIFIER_CTRL_BIT |
 		(u32)((modifier & (KMOD_LSHIFT|KMOD_RSHIFT)) != 0) << KEY_MODIFIER_SHIFT_BIT |
 		(u32)((modifier & (KMOD_LALT|KMOD_RALT)) != 0) << KEY_MODIFIER_ALT_BIT,
@@ -570,9 +613,9 @@ void ted_press_key(Ted *ted, SDL_Keycode keycode, SDL_Keymod modifier) {
 	u32 hi = arr_len(key_actions);
 	while (lo < hi) {
 		u32 mid = (lo + hi) / 2;
-		if (key_actions[mid].key_combo < key_combo) {
+		if (key_actions[mid].key_combo.value < key_combo.value) {
 			lo = mid + 1;
-		} else if (key_actions[mid].key_combo > key_combo) {
+		} else if (key_actions[mid].key_combo.value > key_combo.value) {
 			hi = mid;
 		} else {
 			command_execute(ted, key_actions[mid].command, key_actions[mid].argument);
diff --git a/ted.cfg b/ted.cfg
index dbcc9d7..375901c 100644
--- a/ted.cfg
+++ b/ted.cfg
@@ -51,12 +51,16 @@ lsp-log = off
 # display function signature help? (only with LSP running)
 # this is the thing at the bottom of ted which shows the parameters to the function you're calling
 signature-help-enabled = yes
-# display hover info when F1 key is pressed? (only with LSP running)
+# display hover info when the F1 key is pressed? (only with LSP running)
 hover-enabled = yes
+# key used to show hover info
+hover-key = F1
 # if this is set to x, then hover info will be displayed without F1 key after x seconds (with LSP running)
 hover-time = 1e10
 # highlight instances of the variable under the cursor when the F2 key is pressed? (only with LSP running)
 highlight-enabled = yes
+# key used to highlight variable under cursor
+highlight-key = F2
 # don't require F2 key for highlighting
 highlight-auto = no
 # maximum editable file size.
diff --git a/ted.h b/ted.h
index 12dfcb5..25dfd0a 100644
--- a/ted.h
+++ b/ted.h
@@ -155,8 +155,6 @@ enum {
 	KEYCODE_X2
 };
 /// see \ref KEY_COMBO
-#define KEY_COMBO_COUNT (SCANCODE_COUNT << 3)
-/// see \ref KEY_COMBO
 enum {
 	KEY_MODIFIER_CTRL_BIT,
 	KEY_MODIFIER_SHIFT_BIT,
@@ -168,16 +166,24 @@ enum {
 #define KEY_MODIFIER_SHIFT ((u32)1<<KEY_MODIFIER_SHIFT_BIT)
 /// see \ref KEY_COMBO
 #define KEY_MODIFIER_ALT ((u32)1<<KEY_MODIFIER_ALT_BIT)
-/// Create "key combo" from modifier and key.
-///
 /// a "key combo" is some subset of {control, shift, alt} + some key.
-#define KEY_COMBO(modifier, key) ((u32)(modifier) \
-	| ((u32)(key >> 30) << 3)\
-	| ((u32)(key) & ~(1u<<30)) << 4) // annoyingly SDL sets bit 30 for some keycodes
+typedef struct {
+	/// high 32 bits = SDL_Keycode\n
+	/// low 8 bits = key modifier (see e.g. \ref KEY_MODIFIER_SHIFT)\n
+	/// the remaining 24 bits are currently reserved and should be 0.
+	u64 value;
+} KeyCombo;
+/// Create \ref KeyCombo from modifier and key.
+#define KEY_COMBO(modifier, key) ((KeyCombo){.value = (u64)(modifier) \
+	| ((u64)(key) << 32)})
+/// extract `SDL_Keycode` from \ref KeyCombo
+#define KEY_COMBO_KEY(combo) ((SDL_Keycode)((combo.value) >> 32))
+/// extract key modifier from \ref KeyCombo
+#define KEY_COMBO_MODIFIER(combo) ((u32)((combo.value) & 0xff))
 
 /// Thing to do when a key combo is pressed.
 typedef struct {
-	u32 key_combo;
+	KeyCombo key_combo;
 	Command command;
 	i64 argument;
 } KeyAction;
@@ -255,6 +261,8 @@ typedef struct {
 	bool highlight_enabled;
 	bool highlight_auto;
 	bool vsync;
+	KeyCombo hover_key;
+	KeyCombo highlight_key;
 	u8 tab_width;
 	u8 cursor_width;
 	u8 undo_save_time;
@@ -697,8 +705,6 @@ typedef struct Ted {
 	/// settings to use when no buffer is open
 	Settings *default_settings;
 	float window_width, window_height;
-	/// which of shift, alt, ctrl are down right now.
-	u32 key_modifier;
 	vec2 mouse_pos;
 	u32 mouse_state;
 	/// `nmouse_clicks[i]` = length of `mouse_clicks[i]`
@@ -1535,6 +1541,18 @@ SymbolInfo *tags_get_symbols(Ted *ted);
 // === ted.c ===
 /// for fatal errors
 void die(PRINTF_FORMAT_STRING const char *fmt, ...) ATTRIBUTE_PRINTF(1, 2);
+/// returns `true` if the given key is down
+bool ted_is_key_down(Ted *ted, SDL_Keycode key);
+/// returns `true` if the given \ref KeyCombo is down
+bool ted_is_key_combo_down(Ted *ted, KeyCombo key_combo);
+/// returns `true` if either ctrl key is down
+bool ted_is_ctrl_down(Ted *ted);
+/// returns `true` if either shift key is down
+bool ted_is_shift_down(Ted *ted);
+/// returns `true` if either alt key is down
+bool ted_is_alt_down(Ted *ted);
+/// see \ref KEY_MODIFIER_CTRL, etc.
+u32 ted_get_key_modifier(Ted *ted);
 /// display a message to the user
 void ted_set_message(Ted *ted, MessageType type, PRINTF_FORMAT_STRING const char *fmt, ...) ATTRIBUTE_PRINTF(3, 4);
 /// display an error to the user
-- 
cgit v1.2.3