Created
May 22, 2020 23:27
-
-
Save etscrivner/1aae18c28315788aefe67c5ae865b1eb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if !defined(EMACS_COMMAND_MAP_H) | |
#define EMACS_COMMAND_MAP_H | |
// Create custom ID for our global emacs state data. | |
CUSTOM_ID(attachment, view_emacs_state_id); | |
CUSTOM_COMMAND_SIG(close_all_other_panels) | |
CUSTOM_DOC("Closes all other panels but the currently active one") | |
{ | |
View_ID current_active_view_id = get_active_view(app, Access_Always); | |
if (current_active_view_id == 0) { | |
return; | |
} | |
// Close the build footer panel if it's open | |
close_build_footer_panel(app); | |
View_ID view_id = current_active_view_id; | |
do { | |
view_id = get_next_view_looped_all_panels(app, view_id, Access_Always); | |
view_close(app, view_id); | |
} while(view_id != current_active_view_id); | |
} | |
enum Emacs_Modifier { | |
EmacsModifier_None = 0, | |
EmacsModifier_Ctrl = (1 << 0), | |
EmacsModifier_Alt = (1 << 1) | |
}; | |
struct Emacs_Key_Chord { | |
i32 modifier; | |
b32 shift; | |
Key_Code key; | |
}; | |
struct Emacs_Key_Chord_Or_Key { | |
b32 is_chord; | |
union { | |
Emacs_Key_Chord chord; | |
Key_Code key; | |
}; | |
}; | |
struct Emacs_Key_Sequence { | |
Emacs_Key_Chord chord; | |
u32 seq_count; | |
Emacs_Key_Chord_Or_Key seq[2]; | |
}; | |
struct Emacs_Key_Binding { | |
Emacs_Key_Sequence seq; | |
Command_Binding binding; | |
}; | |
struct Emacs_Global_State { | |
b32 initialized; | |
String_Const_u8 emacs_buffer_name; | |
Emacs_Key_Sequence key_sequence; | |
Buffer_ID emacs_buffer; | |
Cursor emacs_buffer_cursor; | |
}; | |
static bool operator == (Emacs_Key_Chord left, Emacs_Key_Chord right) | |
{ | |
return(left.modifier == right.modifier && left.shift == right.shift && left.key == right.key); | |
} | |
static bool operator != (Emacs_Key_Chord left, Emacs_Key_Chord right) | |
{ | |
return(left.modifier != right.modifier || left.shift != right.shift || left.key != right.key); | |
} | |
static void key_sequence_reset(Emacs_Key_Sequence* key_sequence) | |
{ | |
key_sequence->chord.modifier = EmacsModifier_None; | |
key_sequence->chord.shift = false; | |
key_sequence->chord.key = 0; | |
key_sequence->seq_count = 0; | |
for (u32 i = 0; i < ArrayCount(key_sequence->seq); ++i) | |
{ | |
key_sequence->seq[i] = {}; | |
} | |
} | |
static b32 emacs_key_sequence_chord_match(Emacs_Key_Sequence left, Emacs_Key_Sequence right) | |
{ | |
return(left.chord == right.chord); | |
} | |
static b32 emacs_key_sequence_match(Emacs_Key_Sequence left, Emacs_Key_Sequence right) | |
{ | |
if (left.chord != right.chord) | |
{ | |
return(false); | |
} | |
if (left.seq_count > 0 && left.seq_count == right.seq_count) | |
{ | |
for (u32 i = 0; i < left.seq_count; ++i) | |
{ | |
if (left.seq[i].is_chord != right.seq[i].is_chord) | |
{ | |
return(false); | |
} | |
if (left.seq[i].is_chord && | |
left.seq[i].chord != right.seq[i].chord) | |
{ | |
return(false); | |
} | |
else if (!left.seq[i].is_chord && | |
left.seq[i].key != right.seq[i].key) | |
{ | |
return(false); | |
} | |
} | |
return(true); | |
} | |
else if (left.seq_count == right.seq_count) | |
{ | |
// Chord matches and there's nothing else to match on | |
return(true); | |
} | |
return(false); | |
} | |
static b32 emacs_key_sequence_partial_match(Emacs_Key_Sequence left, Emacs_Key_Sequence right) | |
{ | |
if (left.chord != right.chord) | |
{ | |
return(false); | |
} | |
if (left.seq_count > 0 && right.seq_count > left.seq_count) | |
{ | |
for (u32 i = 0; i < left.seq_count; ++i) | |
{ | |
if (left.seq[i].is_chord != right.seq[i].is_chord) | |
{ | |
return(false); | |
} | |
if (left.seq[i].is_chord && | |
left.seq[i].chord != right.seq[i].chord) | |
{ | |
return(false); | |
} | |
else if (!left.seq[i].is_chord && | |
left.seq[i].key != right.seq[i].key) | |
{ | |
return(false); | |
} | |
} | |
return(true); | |
} | |
else if (right.seq_count > left.seq_count) | |
{ | |
return(true); | |
} | |
return(false); | |
} | |
static b32 emacs_key_chord_valid(Emacs_Key_Chord chord) | |
{ | |
return(chord.modifier != EmacsModifier_None && chord.key != 0); | |
} | |
static b32 emacs_key_chord_is_key(Emacs_Key_Chord chord) | |
{ | |
return(!emacs_key_chord_valid(chord) && chord.key != 0); | |
} | |
static b32 emacs_key_sequence_has_chord(Emacs_Key_Sequence key_sequence) | |
{ | |
return(emacs_key_chord_valid(key_sequence.chord)); | |
} | |
#define EmacsBindChord(SeqMod, SeqKey, Fn) { .seq = { .chord = { SeqMod, false, SeqKey }, .seq_count = 0 }, .binding = { .custom = Fn }} | |
#define EmacsBindChordShift(SeqMod, SeqKey, Fn) { .seq = { .chord = { SeqMod, true, SeqKey }, .seq_count = 0 }, .binding = { .custom = Fn }} | |
#define EmacsBindSequence(SeqMod, SeqKey, Key, Fn) { .seq = { .chord = { SeqMod, false, SeqKey }, .seq_count = 1, .seq = { [0] = {.is_chord = true, .chord = {SeqMod, false, Key}} } }, .binding = { .custom = Fn }} | |
#define EmacsChord(ChMod, ChShift, ChKey) { ChMod, ChShift, ChKey } | |
#define EmacsChordArgs(ChMod, ChShift, ChKey) ChMod, ChShift, ChKey | |
#define EmacsSequenceChord(N, Chord) [N] = { .is_chord = true, .chord = Chord } | |
#define EmacsSequenceKey(N, Key) [N] = { .is_chord = false, .key = Key } | |
#define EmacsBind0(Chord, Fn) { .seq = { .chord = Chord, .seq_count = 0 }, binding = { .custom = Fn } } | |
#define EmacsBind1(Chord, Seq1, Fn) { .seq = { .chord = Chord, .seq_count = 1, .seq = { Seq1 } }, .binding = { .custom = Fn } } | |
#define EmacsBind2(Chord, Seq1, Seq2, Fn) { .seq = { .chord = Chord, .seq_count = 2, .seq = { Seq1, Seq2 } }, .binding = { .custom = Fn } } | |
static Emacs_Key_Binding emacs_bindings[] = { | |
EmacsBindChord(EmacsModifier_Alt, KeyCode_X, command_lister), | |
EmacsBindChord(EmacsModifier_Ctrl, KeyCode_Backspace, backspace_alpha_numeric_boundary), | |
EmacsBindChord(EmacsModifier_Ctrl, KeyCode_P, move_up), | |
EmacsBindChord(EmacsModifier_Alt, KeyCode_P, move_up_to_blank_line_end), | |
EmacsBindChord(EmacsModifier_Ctrl, KeyCode_N, move_down), | |
EmacsBindChord(EmacsModifier_Alt, KeyCode_N, move_down_to_blank_line_end), | |
EmacsBindChord(EmacsModifier_Ctrl, KeyCode_F, move_right), | |
EmacsBindChord(EmacsModifier_Alt, KeyCode_F, move_right_whitespace_boundary), | |
EmacsBindChord(EmacsModifier_Ctrl, KeyCode_B, move_left), | |
EmacsBindChord(EmacsModifier_Alt, KeyCode_B, move_left_whitespace_boundary), | |
EmacsBindChord(EmacsModifier_Ctrl, KeyCode_D, delete_char), | |
EmacsBindChord(EmacsModifier_Ctrl, KeyCode_A, seek_beginning_of_line), | |
EmacsBindChord(EmacsModifier_Ctrl, KeyCode_E, seek_end_of_line), | |
EmacsBindChord(EmacsModifier_Ctrl, KeyCode_R, reverse_search), | |
EmacsBindChord(EmacsModifier_Ctrl, KeyCode_S, search), | |
EmacsBindChord(EmacsModifier_Ctrl, KeyCode_Z, undo), | |
EmacsBindChord(EmacsModifier_Alt, KeyCode_W, copy), | |
EmacsBindChord(EmacsModifier_Ctrl, KeyCode_W, cut), | |
EmacsBindChord(EmacsModifier_Ctrl, KeyCode_Y, paste_and_indent), | |
EmacsBindChord(EmacsModifier_Alt, KeyCode_G, goto_line), | |
EmacsBindChord(EmacsModifier_Alt, KeyCode_D, delete_alpha_numeric_boundary), | |
EmacsBindChordShift(EmacsModifier_Alt, KeyCode_5, query_replace), | |
EmacsBindSequence(EmacsModifier_Ctrl, KeyCode_X, KeyCode_F, interactive_open_or_new), | |
EmacsBindSequence(EmacsModifier_Ctrl, KeyCode_X, KeyCode_C, exit_4coder), | |
EmacsBindSequence(EmacsModifier_Ctrl, KeyCode_X, KeyCode_S, save), | |
EmacsBind1(EmacsChord(EmacsModifier_Ctrl, false, KeyCode_X), EmacsSequenceKey(0, KeyCode_H), select_all), | |
EmacsBind1(EmacsChord(EmacsModifier_Ctrl, false, KeyCode_C), EmacsSequenceKey(0, KeyCode_J), jump_to_definition), | |
EmacsBind1(EmacsChord(EmacsModifier_Ctrl, false, KeyCode_X), EmacsSequenceKey(0, KeyCode_0), close_panel), | |
EmacsBind1(EmacsChord(EmacsModifier_Ctrl, false, KeyCode_X), EmacsSequenceKey(0, KeyCode_1), close_all_other_panels), | |
EmacsBind1(EmacsChord(EmacsModifier_Ctrl, false, KeyCode_X), EmacsSequenceKey(0, KeyCode_2), open_panel_hsplit), | |
EmacsBind1(EmacsChord(EmacsModifier_Ctrl, false, KeyCode_X), EmacsSequenceKey(0, KeyCode_3), open_panel_vsplit), | |
EmacsBind1(EmacsChord(EmacsModifier_Ctrl, false, KeyCode_X), EmacsSequenceKey(0, KeyCode_O), change_active_panel), | |
EmacsBind1(EmacsChord(EmacsModifier_Ctrl, false, KeyCode_X), EmacsSequenceKey(0, KeyCode_B), interactive_switch_buffer), | |
EmacsBind1(EmacsChord(EmacsModifier_Ctrl, false, KeyCode_X), EmacsSequenceKey(0, KeyCode_K), kill_buffer), | |
EmacsBind1(EmacsChord(EmacsModifier_Ctrl, false, KeyCode_C), EmacsSequenceKey(0, KeyCode_P), load_project), | |
EmacsBind1( | |
EmacsChord(EmacsModifier_Ctrl, false, KeyCode_C), | |
EmacsSequenceChord(0, EmacsChord(EmacsModifier_Ctrl, false, KeyCode_C)), | |
comment_line | |
) | |
}; | |
struct Key_Code_To_Emacs_Modifier { | |
Key_Code code; | |
Emacs_Modifier emacs_modifier; | |
}; | |
static Key_Code_To_Emacs_Modifier KeyCodeToModifierTable[] = { | |
{ .code = KeyCode_Control, .emacs_modifier = EmacsModifier_Ctrl }, | |
{ .code = KeyCode_Alt, .emacs_modifier = EmacsModifier_Alt }, | |
{ .code = 0, .emacs_modifier = EmacsModifier_None } | |
}; | |
static Emacs_Modifier modifier_to_emacs_modifier(Key_Code mod) | |
{ | |
for (u32 i = 0; i < ArrayCount(KeyCodeToModifierTable); ++i) | |
{ | |
if (KeyCodeToModifierTable[i].code == mod) | |
{ | |
return(KeyCodeToModifierTable[i].emacs_modifier); | |
} | |
} | |
return(EmacsModifier_None); | |
} | |
static b32 emacs_process_next_chord_or_key(Emacs_Key_Chord_Or_Key* result, Input_Event* event) | |
{ | |
Emacs_Key_Chord chord = {}; | |
switch (event->kind) | |
{ | |
case InputEventKind_KeyStroke: | |
{ | |
for (i32 i = 0; i < event->key.modifiers.count; ++i) | |
{ | |
if (event->key.modifiers.mods[i] == KeyCode_Shift) | |
{ | |
chord.shift = true; | |
} | |
Emacs_Modifier emacs_equiv = modifier_to_emacs_modifier(event->key.modifiers.mods[i]); | |
if (emacs_equiv != EmacsModifier_None) | |
{ | |
chord.modifier |= emacs_equiv; | |
} | |
} | |
// Avoid counting shift modifier unless it is actually being | |
// applied to a modifier key. | |
if (chord.modifier == EmacsModifier_None) | |
{ | |
chord.shift = false; | |
} | |
switch (event->key.code) | |
{ | |
case KeyCode_Control: | |
case KeyCode_Shift: | |
case KeyCode_Alt: | |
case KeyCode_Command: | |
// Skip modifier keys already counted. | |
break; | |
default: | |
{ | |
chord.key = event->key.code; | |
} | |
break; | |
} | |
} | |
break; | |
default: break; | |
} | |
if (emacs_key_chord_valid(chord)) | |
{ | |
result->is_chord = true; | |
result->chord = chord; | |
return(true); | |
} | |
else if (emacs_key_chord_is_key(chord)) | |
{ | |
result->is_chord = false; | |
result->key = chord.key; | |
return(true); | |
} | |
return(false); | |
} | |
static void emacs_find_start_chord(Emacs_Key_Sequence* key_sequence, Input_Event event) | |
{ | |
switch (event.kind) { | |
case InputEventKind_KeyStroke: | |
{ | |
for (i32 i = 0; i < event.key.modifiers.count; ++i) | |
{ | |
if (event.key.modifiers.mods[i] == KeyCode_Shift) | |
{ | |
key_sequence->chord.shift = true; | |
} | |
Emacs_Modifier emacs_equiv = modifier_to_emacs_modifier(event.key.modifiers.mods[i]); | |
if (emacs_equiv != EmacsModifier_None) | |
{ | |
key_sequence->chord.modifier |= emacs_equiv; | |
} | |
} | |
// Avoid counting shift modifier unless it is actually being | |
// applied to a modifier key. | |
if (key_sequence->chord.modifier == EmacsModifier_None) | |
{ | |
key_sequence->chord.shift = false; | |
return; | |
} | |
switch (event.key.code) | |
{ | |
case KeyCode_Control: | |
case KeyCode_Shift: | |
case KeyCode_Alt: | |
case KeyCode_Command: | |
// Skip modifier keys already counted. | |
break; | |
default: | |
{ | |
if (key_sequence->chord.modifier != EmacsModifier_None) | |
{ | |
key_sequence->chord.key = event.key.code; | |
} | |
} | |
break; | |
} | |
} | |
break; | |
default: break; | |
} | |
// If we didn't get a complete chord, reset | |
if (!emacs_key_sequence_has_chord(*key_sequence)) | |
{ | |
key_sequence_reset(key_sequence); | |
} | |
} | |
static String_Const_u8 emacs_key_chord_const_u8(Scratch_Block* scratch, Emacs_Key_Chord chord) | |
{ | |
List_String_Const_u8 result = {}; | |
if (chord.modifier & EmacsModifier_Ctrl) { | |
string_list_pushf(scratch->arena, &result, "C-"); | |
} | |
if (chord.modifier & EmacsModifier_Alt) { | |
string_list_pushf(scratch->arena, &result, "M-"); | |
} | |
if (chord.shift) { | |
string_list_pushf(scratch->arena, &result, "S-"); | |
} | |
string_list_pushf(scratch->arena, &result, "%s", key_code_name[chord.key]); | |
return(string_list_flatten(scratch->arena, result, StringFill_NullTerminate)); | |
} | |
static String_Const_u8 emacs_key_sequence_const_u8(Scratch_Block* scratch, Emacs_Key_Sequence seq) | |
{ | |
List_String_Const_u8 result = {}; | |
string_list_pushf(scratch->arena, &result, "%s", emacs_key_chord_const_u8(scratch, seq.chord)); | |
for (u32 i = 0; i < seq.seq_count; ++i) | |
{ | |
string_list_pushf(scratch->arena, &result, " "); | |
if (seq.seq[i].is_chord) { | |
string_list_pushf(scratch->arena, &result, "%s", emacs_key_chord_const_u8(scratch, seq.seq[i].chord)); | |
} else { | |
string_list_pushf(scratch->arena, &result, "%s", key_code_name[seq.seq[i].key]); | |
} | |
} | |
return(string_list_flatten(scratch->arena, result, StringFill_NullTerminate)); | |
} | |
static Command_Binding emacs_get_binding(Application_Links *app, Emacs_Global_State* emacs_state, Command_Map_ID map_id, Input_Event* event) | |
{ | |
Scratch_Block scratch(app); | |
Command_Binding result = {}; | |
// NOTE(eric): Emacs key-chord detection | |
if (!emacs_key_sequence_has_chord(emacs_state->key_sequence)) | |
{ | |
emacs_find_start_chord(&emacs_state->key_sequence, *event); | |
} | |
else | |
{ | |
if (emacs_process_next_chord_or_key(&emacs_state->key_sequence.seq[emacs_state->key_sequence.seq_count], event)) | |
{ | |
++emacs_state->key_sequence.seq_count; | |
} | |
if (event->key.code == KeyCode_Escape) | |
{ | |
key_sequence_reset(&emacs_state->key_sequence); | |
} | |
} | |
// No chord, so we can fail the lookup early | |
if (!emacs_key_sequence_has_chord(emacs_state->key_sequence)) | |
{ | |
return(result); | |
} | |
b32 PartialMatchFound = false; | |
for (u32 i = 0; i < ArrayCount(emacs_bindings); ++i) | |
{ | |
if (emacs_key_sequence_match(emacs_state->key_sequence, emacs_bindings[i].seq)) | |
{ | |
// Print matched key sequence debug | |
#if 0 | |
{ | |
Buffer_Insertion emacs_out = begin_buffer_insertion_at(app, emacs_state->emacs_buffer, 0); | |
{ | |
String_Const_u8 seq = emacs_key_sequence_const_u8(&scratch, emacs_state->key_sequence); | |
insert_string(&emacs_out, seq); | |
insertf(&emacs_out, "\n"); | |
} | |
end_buffer_insertion(&emacs_out); | |
} | |
#endif | |
key_sequence_reset(&emacs_state->key_sequence); | |
result = emacs_bindings[i].binding; | |
PartialMatchFound = true; | |
break; | |
} | |
else if (emacs_key_sequence_partial_match(emacs_state->key_sequence, emacs_bindings[i].seq)) | |
{ | |
PartialMatchFound = true; | |
} | |
} | |
// If we didn't find anything with a matching starting chord then reset the | |
// sequence, as the current sequence is invalid. | |
if (!PartialMatchFound) | |
{ | |
key_sequence_reset(&emacs_state->key_sequence); | |
} | |
return(result); | |
} | |
CUSTOM_COMMAND_SIG(emacs_view_input_handler) | |
CUSTOM_DOC("Input handler for emacs-style chord input system") | |
{ | |
Scratch_Block scratch(app); | |
default_input_handler_init(app, scratch); | |
View_ID view = get_this_ctx_view(app, Access_Always); | |
Managed_Scope scope = view_get_managed_scope(app, view); | |
Managed_Scope global_scope = get_global_managed_scope(app); | |
// Emacs initialization | |
Emacs_Global_State *emacs_state = scope_attachment(app, global_scope, view_emacs_state_id, Emacs_Global_State); | |
if (!emacs_state->initialized) | |
{ | |
*emacs_state = {}; | |
#if 0 | |
emacs_state->emacs_buffer_name = string_u8_litexpr("*emacs*"); | |
emacs_state->emacs_buffer = create_buffer(app, emacs_state->emacs_buffer_name, BufferCreate_NeverAttachToFile|BufferCreate_AlwaysNew); | |
buffer_set_setting(app, emacs_state->emacs_buffer, BufferSetting_Unimportant, true); | |
buffer_set_setting(app, emacs_state->emacs_buffer, BufferSetting_ReadOnly, true); | |
emacs_state->emacs_buffer_cursor = make_cursor(push_array(scratch, u8, KB(256)), KB(256)); | |
#endif | |
emacs_state->initialized = true; | |
} | |
for (;;){ | |
// NOTE(allen): Get input | |
User_Input input = get_next_input(app, EventPropertyGroup_Any, 0); | |
if (input.abort){ | |
break; | |
} | |
ProfileScopeNamed(app, "before view input", view_input_profile); | |
// NOTE(allen): Mouse Suppression | |
Event_Property event_properties = get_event_properties(&input.event); | |
if (suppressing_mouse && (event_properties & EventPropertyGroup_AnyMouseEvent) != 0){ | |
continue; | |
} | |
// NOTE(allen): Get map_id | |
Command_Map_ID map_id = default_get_map_id(app, view); | |
// NOTE(eric): Get emacs binding | |
Command_Binding emacs_binding = emacs_get_binding(app, emacs_state, map_id, &input.event); | |
// NOTE(allen): Get binding | |
Command_Binding binding = map_get_binding_recursive(&framework_mapping, map_id, &input.event); | |
if ((binding.custom == 0 && emacs_binding.custom == 0) || (emacs_binding.custom == 0 && emacs_key_sequence_has_chord(emacs_state->key_sequence))){ | |
leave_current_input_unhandled(app); | |
continue; | |
} | |
// NOTE(allen): Run the command and pre/post command stuff | |
default_pre_command(app, scope); | |
ProfileCloseNow(view_input_profile); | |
if (emacs_binding.custom != 0) { | |
emacs_binding.custom(app); | |
} else if (!emacs_key_sequence_has_chord(emacs_state->key_sequence)) { | |
binding.custom(app); | |
} | |
ProfileScope(app, "after view input"); | |
default_post_command(app, scope); | |
} | |
} | |
#endif // EMACS_COMMAND_MAP_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment