diff options
author | pommicket <pommicket@gmail.com> | 2023-03-03 20:34:16 -0500 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2023-03-03 20:35:44 -0500 |
commit | 1b383e6884d12d4098c4b6631e9c3e10cba0ffaf (patch) | |
tree | bfd88b9ffd8514b44f49e52d71cffd02e5d5c897 | |
parent | 2bd3bf463ec253ef7c12187b3beafc22599e41be (diff) |
configurable hover/highlight key + better key stuff
scancodes are no longer used for anything
-rw-r--r-- | buffer.c | 6 | ||||
-rw-r--r-- | config.c | 137 | ||||
-rw-r--r-- | ide-highlights.c | 4 | ||||
-rw-r--r-- | ide-hover.c | 4 | ||||
-rw-r--r-- | main.c | 26 | ||||
-rw-r--r-- | ted.c | 49 | ||||
-rw-r--r-- | ted.cfg | 6 | ||||
-rw-r--r-- | ted.h | 38 |
8 files changed, 181 insertions, 89 deletions
@@ -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); } @@ -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; @@ -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); } @@ -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); @@ -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. @@ -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 |