Skip to content

Instantly share code, notes, and snippets.

@etscrivner
Created May 22, 2020 23:27
Show Gist options
  • Save etscrivner/1aae18c28315788aefe67c5ae865b1eb to your computer and use it in GitHub Desktop.
Save etscrivner/1aae18c28315788aefe67c5ae865b1eb to your computer and use it in GitHub Desktop.
#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