Customization layer - getting started ( 4coder 4.1 )

Content

This article is a small introduction to the 4coder customization layer for 4coder version starting with 4coder 4.1. The goal is to help new users to setup their custom layer, define their key bindings, set their hooks, and add keywords to the syntax highlighting. At the time of writing the current 4coder version was 4.1.5. The article has been updated to try to take into account changes from version 4.1.7.

Requirements

  • 4coder 4.1.x;
  • To know some C/Cpp;
  • Have a compiler installed: Visual Studio on Windows, GCC or Clang on Mac and Linux.

Installation

Installing boils down to unzipping the archive where you want.

On windows, the path must not contain any spaces (as of version 4.1.5). And if you rely on the build script to setup the Visual Studio environment (calling vcvarsall.bat) the path must not contain parenthesis ( or ). There are some fixes for those issues.

Building

The custom layer is built into a shared library which is called 4coder_custom.dll on Windows and 4coder_custom.so on Linux and Mac. You can't build the custom layer while you're using 4coder as the library will be in used. So before you build, make sure 4coder isn't running. If you get an error similar to "cannot open file 'custom_4coder.dll'" you probably need to close 4coder and rebuild.

To build 4coder's default custom layer, you need to use one of the scripts provided in the custom\bin folder:

  • For Linux 64 bit: buildsuper_x64-linux.sh
  • For Mac 64 bit: buildsuper_x64-mac.sh
  • For Windows 64 bit: buildsuper_x64-win.bat;
  • For Linux 32 bit: buildsuper_x86-linux.sh;
  • For Windows 32 bit: buildsuper_x86-win.bat.

You can double click the script corresponding to your platform, or launch it from a console. The later is preferred as you will see the eventual errors. You can pass a file name as the first argument to that script and the file will be compiled instead of the default one (which is custom\4coder_default_bindings.cpp).

On windows, the build script will try to call the Visual Studio script, vcvarsall.bat, that sets up the compiler if it detects that it hasn't been setup yet. If you have several versions of Visual Studio installed, the version that will be used depends on the order defined in the file custom\bin\setup_cl_generic.bat. Supported version are Visual Studio 2010 and up, meaning that the script can call vcvarsall.bat from any of those version. But if you compile with older versions, there might be some errors or warning. For example Visual Studio 2012 doesn't compile 4coder out of the box.

If the build was successful you should have a custom_4coder.dll and custom_4coder.pdb in the current working directory (custom_4coder.so on Linux and Mac). If you didn't compile from the 4coder root directory, you'll need to copy the library (.dll or .so) and the debug information (.pdb only on Windows) in the 4coder root directory.

Don't forget to rebuild after making changes in the code.

Making it easier to update

To make updating to a new version of 4coder as simple as dropping the new files in the directory and not caring about overwriting your modifications, make a copy of custom\4coder_default_bindings.cpp (called custom_layer.cpp in the rest of this article) and only edit the copy. The custom code doesn't need to be at a specific location as long as you pass the correct file path to the build script. Creating a build script in 4coder's root directory that will call the main build script with your custom file name will make it easy to compile your custom code.

For Windows: build.bat

@echo off
call custom\bin\buildsuper_x64-win.bat custom_layer.cpp %*

For Linux: build.sh

#!/bin/bash
custom/bin/buildsuper_x64-linux.sh custom_layer.cpp $@

For Mac: build.sh

#!/bin/bash
custom/bin/buildsuper_x64-mac.sh custom_layer.cpp $@

Now you can just launch the build script from the root directory to compile your custom code and have the library at the correct place. You can also pass "release" as an argument to the script to build the custom layer with optimization on.

Entry point

The "entry point" for the custom layer is the custom_layer_init function in your custom layer file.

Key bindings

The custom_layer_init function calls setup_default_mapping (from custom\4coder_default_map.cpp) or setup_mac_mapping which will define the default bindings of 4coder. You can either leave the call to that function and overwrite the bindings you want to change, or not call that function and set all the bindings yourself. You only need to set the bindings you use, there is no need to bind all available commands.

Before continuing further, make sure that the config.4coder file as the value of the mapping field set to an empty string. This will ensure that 4coder uses your bindings and doesn't "revert" to using the default bindings for your platform.

mapping = "";

Key maps overview

4coder groups key binding into key maps. A key maps allows you to have different sets of keys that will be active or inactive depending on the context. For instance by default different key bindings will be active if you are editing a code file or a plain text file. 4coder comes with 3 default key maps:

  • keys_global: bindings for operation not related to editing the content of a file. For example opening the file browser, closing a file, change the active panel;
  • keys_file: bindings to edit the content of a file. For example typing characters, deleting characters, searching, moving around;
  • keys_code: bindings that only apply to code editing. For example indentation, auto-completion, listing types;

Note that those bindings, by default, are attached to different part of the application. mapid_global is attached to the view context while mapid_file and mapid_code are attached to buffers. This is not important at the moment, but might be later when you try to do a more complex custom layer.

Key codes

Key codes are used to reference a key. They are integer number that you reference using a label starting with KeyCode_ followed by the name of a key. You can find a list of all names in custom\generated\4coder_event_codes.h. By default, 4coder uses key codes that mostly follow your keyboard layout. For example the letter next to tab is q in QWERTY layouts but a in AZERTY layouts. You need to reference that key using KeyCode_Q if you're using a QWERTY layout and KeyCode_A if you're using a AZERTY layout.

The key code names use the primary symbol of the key in QWERTY layout, so some key names will not match your layout key names. To find the key code name corresponding to a key you can use the display_key_codes command as explain below in the Non QWERTY US keyboard layouts section.

Physical key

Starting with version 4.1.5, 4coder can uses keyboard layout independent key codes, which means that when you use a key code in that mode, you reference the physical position of that key on a QWERTY US keyboard layout.

For example the key at the right of tab is always KeyCode_Q even if in your layout it's a different character. This is only for referencing a key when handling bindings or input. Text input will still use your keyboard layout.

This is optional and can be activated in config.4coder by changing the value of bind_by_physical_key to true.

This option was added in 4.1.5 and couldn't be turned off. It was made optional and off by default in 4.1.6.

Non QWERTY US keyboard layouts

If you use a keyboard layout that is not QWERTY US you'll probably need help figuring out the key codes corresponding to some keys. There is a command that does just that. Press Alt + x to open the command lister, search for display_key_codes and execute it. Now any time you press a key on your keyboard the panel will show you the corresponding key code. While writing your bindings, you can keep that open in one panel and write the bindings in the other panel, you'll need to switch panel using the mouse though. You can close that panel by pressing Escape.

Defining mappings

Using configuration file

Since version 4.1.7, you can modify bindings.4coder (or mac-bindings.4coder), to change bindings without needing to recompile the custom layer. If you don't need a special input system (like a modal input system), just modifying the file should be enough to setup all your bindings, and you can skip the next few sections.

In several places in the next few sections, I'll suggest to #if 0 the code that reads the configuration file, but you can use the bindings file to setup your keymaps, but it's likely that you'll need to rewrite some functions from 4coder to make it work. And it only applies to version 4.1.7.

Using code

If you just want to overwrite some bindings in code, you need to select a key map using the SelectMap function and passing it the key map id you wish to use, and then bind a key combination using Bind and passing it the command you want to bind and the keys to use to call the command separated by commas ( , ). Make sure to do that after calling setup_default_mapping.

You'll also need to remove the code that reads the bindings.4coder file otherwise it will overwrite your changes. That code is in 4coder_default_framework.cpp in the default_4coder_initialize function. You need to #if 0 or comment the following code.

#if 0
    String_Const_u8 bindings_file_name = string_u8_litexpr("bindings.4coder");
    String_Const_u8 mapping = def_get_config_string(scratch, vars_save_string_lit("mapping"));
    
    if (string_match(mapping, string_u8_litexpr("mac-default"))){
        bindings_file_name = string_u8_litexpr("mac-bindings.4coder");
    }
    else if (OS_MAC && string_match(mapping, string_u8_litexpr("choose"))){
        bindings_file_name = string_u8_litexpr("mac-bindings.4coder");
    }
    
    // TODO(allen): cleanup
    String_ID global_map_id = vars_save_string_lit("keys_global");
    String_ID file_map_id = vars_save_string_lit("keys_file");
    String_ID code_map_id = vars_save_string_lit("keys_code");
    
    if (dynamic_binding_load_from_file(app, &framework_mapping, bindings_file_name)){
        setup_essential_mapping(&framework_mapping, global_map_id, file_map_id, code_map_id);
    }
    else{
        setup_built_in_mapping(app, mapping, &framework_mapping, global_map_id, file_map_id, code_map_id);
    }
#endif

In the following example, CTRL + ALT + o is bound to change the active panel and CTRL + k to kill the current buffer.

void
custom_layer_init(Application_Links *app){
    Thread_Context *tctx = get_thread_context(app);
    
    // NOTE(allen): setup for default framework
    default_framework_init(app);
    
    // NOTE(allen): default hooks and command maps
    set_all_default_hooks(app);
    mapping_init(tctx, &framework_mapping);
    String_ID global_map_id = vars_save_string_lit("keys_global");
    String_ID file_map_id = vars_save_string_lit("keys_file");
    String_ID code_map_id = vars_save_string_lit("keys_code");
#if OS_MAC
    setup_mac_mapping(&framework_mapping, global_map_id, file_map_id, code_map_id);
#else
    setup_default_mapping(&framework_mapping, global_map_id, file_map_id, code_map_id);
#endif
    setup_essential_mapping(&framework_mapping, global_map_id, file_map_id, code_map_id);

    /* Custom mapping start */
    MappingScope();
    SelectMapping( &framework_mapping );

    SelectMap( global_map_id );
    Bind( kill_buffer, KeyCode_K, KeyCode_Control );
    Bind( change_active_panel, KeyCode_O, KeyCode_Control, KeyCode_Alt );
    /* Custom mapping end */
}

You can take a look into the setup_default_mapping function to see how the default mappings are setup.

To know which commands are available you can use 4coder command lister. The default binding to open it is Alt + x. It will list you all available commands and the associated binding. You can use any names from that list. Note that selecting an item in that list executes the corresponding command, so it's a pretty useful binding to keep around.

Remarks

  • If setup_default_mapping sets a binding and you set another binding with the same keys only the last binding will be valid.
  • Key maps can be inherited. If an inherited map defines a binding its parent had defined, only the child binding will be valid.
  • code_map_id inherits from file_map_id, file_map_id inherits from global_map_id.
  • That means that all global_map_id bindings are available in file_map_id; all file_map_id bindings (including those inherited from global_map_id) are available in code_map_id.
  • It also means that if setup_default_mapping defines a binding in code_map_id and you want to use the same key combination in file_map_id or global_map_id it wouldn't work. You would need to comment the code_map_id binding in setup_default_mapping.
  • You can't set the same key binding to two different functions. To do something like that you'll need to create a custom command (explained latter).
  • You can set two different bindings to the same function.
  • The available key codes are defined in custom\generated\4coder_event_codes.h
  • There are special bindings:
  • BindTextInput: to set which function should be called when text is inserted (more precisely when you press a key that produces text and doesn't have a command bound);
  • BindMouse, BindMouseRelease: to bind commands to mouse buttons;
  • BindMouseWheel: to bind commands to the mouse wheel rotation;
  • BindMouseMove: to bind commands to mouse movements;
  • BindCore: to bind non key events. For example to set a function to call on application startup or exit.

Bindings from scratch

If you want to define all the bindings yourself, you can do the same as in the previous section, with the difference that you need to remove the calls to setup_default_mapping and setup_essential_mapping. Note that if you do that you'll need to setup some bindings that are necessary for 4coder to work correctly. As in the previous section, you need to remove the code that reads the bindings file.

The following bindings need to be set in a key map that is attached to buffers. With the default key maps it means that they should be in keys_global (mapid_global in previous versions) or keys_file (mapid_file in previous versions), assuming keys_file inherit keys_global bindings. If you aren't using the default key maps, make sure those function are in a key map you attach to buffers.

// This is needed for 4coder to properly start.
BindCore( default_startup, CoreCode_Startup );

// Without this you will not be able to close 4coder.
BindCore( default_try_exit, CoreCode_TryExit );

Example

void
custom_layer_init(Application_Links *app){
    Thread_Context *tctx = get_thread_context(app);
    
    // NOTE(allen): setup for default framework
    default_framework_init(app);
    
    // NOTE(allen): default hooks and command maps
    set_all_default_hooks(app);
    mapping_init(tctx, &framework_mapping);
    String_ID global_map_id = vars_save_string_lit("keys_global");
    String_ID file_map_id = vars_save_string_lit("keys_file");
    String_ID code_map_id = vars_save_string_lit("keys_code");
    
    /* Custom mapping start */
    MappingScope();
    SelectMapping( &framework_mapping );

    SelectMap( global_map_id );
    BindCore( default_startup, CoreCode_Startup );
    BindCore( default_try_exit, CoreCode_TryExit );
    Bind( kill_buffer, KeyCode_K, KeyCode_Control );
    Bind( change_active_panel, KeyCode_O, KeyCode_Control, KeyCode_Alt );

    SelectMap( file_map_id );
    ParentMap( global_map_id );
    BindTextInput(write_text_input);

    SelectMap( code_map_id );
    ParentMap( file_map_id );
    Bind( ... );
    /* Custom mapping end */
}

Since this is just C++ code, there is no need for the code to be in the custom_layer_init function body, you can put it in another function or file, as long as you call the function from custom_layer_init after the SelectMapping call.

Defining new key maps

To define your own set of key maps, you only need to define a new id for each one of them using vars_save_string_lit. This function can be called after the inclusion of 4coder_default_include.cpp.

For example

String_ID map_id = vars_save_string_lit( "name_for_a_map_id" );

Inheriting a key map

As mentioned before, a selected key maps can inherit an other key map. This is simply done by calling the ParentMap.

SelectMap( file_map_id );
...
ParentMap( global_map_id );

This will make all bindings from the global key map available in the file key map. Note that any binding from the child key map that uses the same key combination will overwrite it's parent: only the child command will be called if you press the key combination.

It's also important to know that you need to call SelectMap on the parent map before being able to inherit it, even if you don't bind anything in the parent because SelectMap is what actually creates the map.

Using key maps

For a key map to be active, you need to attached it to a buffer. When the view_input_handler hook receives an input, it will retrieve the current key map associated to the current buffer, and search it for a command corresponding to the key pressed.

You can change the current key map attached to a buffer with the following function:

void set_current_mapid( Application_Links* app, Command_Map_ID mapid ) {
    
    View_ID view = get_active_view( app, 0 );
    Buffer_ID buffer = view_get_buffer( app, view, 0 );
    Managed_Scope scope = buffer_get_managed_scope( app, buffer );
    Command_Map_ID* map_id_ptr = scope_attachment( app, scope, buffer_map_id, Command_Map_ID );
    *map_id_ptr = mapid;
}

Once again, key maps are associated to buffers, not at the application or view level. But this can be changed if you rewrite the view_input_handler hook.

Lister bindings

The list view that shows up when you want to open a file, change buffer... are called listers. Unfortunately there is no easy way to change the bindings in those views if you need to. You'll need to rewrite the run_lister function from custom\4coder_lister_base.cpp to change the bindings. To keep it somewhat simple when you update to a new version of 4coder, I suggest you to create a copy of the function in a new file with a new name, custom_run_lister for example, and call it from the actual run_lister function.

/* In custom/4coder_lister_base. */

#if 0
function Lister_Result
run_lister(Application_Links *app, Lister *lister){
/* Original function body. */
}
#else

/* Prototype for custom_run_lister. */
function Lister_Result custom_run_lister(Application_Links *app, Lister *lister);

/* Replacing the original function so that anyone who calls it actually call the custom version. */
function Lister_Result run_lister(Application_Links *app, Lister *lister) {
    return custom_run_lister( app, lister );
}
#endif

/* In custom_layer.cpp */
function Lister_Result custom_run_lister(Application_Links *app, Lister *lister) {
    /* The modified body of run_lister that implements your desired bindings. */
}

Remark

By default, when a lister is on the screen the active binding is global_map_id.

Custom commands

To create a function that can be called with a key bind you need to create a command using the CUSTOM_COMMAND_SIG(name) macro defined in custom\4coder_types.h. The actual function signature is void name(struct Application_Links* app).

CUSTOM_COMMAND_SIG( hello ) {
    print_message( app, string_u8_litexpr( "Hello Dave.\n" ) );
}

All command use the same signature, which means you can call any other command from a single command.

CUSTOM_COMMAND_SIG( select_line ) {
    seek_beginning_of_line( app );
    set_mark( app );
    seek_end_of_line( app );
}

Hooks

4coder allows you to specify functions (hooks) that will be called when some events happen. custom_layer_init calls set_all_default_hooks, defined in custom\4coder_default_hooks.cpp, to setup the default behavior. You can replace those hooks with your functions. The function signatures for those hooks can be found in custom\4coder_types.h. You can leave the set_all_default_hooks call and just overwrite some hooks to replace the default behavior.

void
custom_layer_init(Application_Links *app){

    Thread_Context *tctx = get_thread_context(app);

    // NOTE(allen): setup for default framework
    default_framework_init(app);

    // NOTE(allen): default hooks and command maps
    set_all_default_hooks(app);

    set_custom_hook( app, HookID_RenderCaller, custom_render_caller );
    set_custom_hook( app, HookID_BeginBuffer, custom_begin_buffer );

    mapping_init(tctx, &framework_mapping);

    String_ID global_map_id = vars_save_string_lit("keys_global");
    String_ID file_map_id = vars_save_string_lit("keys_file");
    String_ID code_map_id = vars_save_string_lit("keys_code");
#if OS_MAC
    setup_mac_mapping(&framework_mapping, global_map_id, file_map_id, code_map_id);
#else
    setup_default_mapping(&framework_mapping, global_map_id, file_map_id, code_map_id);
#endif
    setup_essential_mapping(&framework_mapping, global_map_id, file_map_id, code_map_id);
}

A few notes:

  • default_view_input_handler: handles inputs and call commands. If for some reason you want to do something before a command is called it's in this function that you'll need to do it. Note that this function sets the map id associated to the view (global_map_id by default, it will be used for lister) and than goes in an infinite loop, waiting for events to happens. In the infinite loop it will try to get the key map associated to a buffer, and if none was set it will set it to file_map_id. If you wish to use a different default key map you may need to modify this hook. I said "may" because the default_begin_buffer hook will generally set the key map to use when a buffer is open.
  • default_begin_buffer: is called when a buffer is created. By default it will set the key map of the buffer depending on the file name, setting it to code_map_id if the file extensions id "c", "cpp", "cc", "h", "hpp" and setting it to file_map_id otherwise. It will also set line wrapping settings and line ending settings.
  • default_render_caller: is called to render a view. In turns it call default_render_buffer which is used to render a buffer.

Adding key words to the syntax highlighting

To add extra key words to the syntax highlighting, you'll need to modify the default_render_buffer function (or create a copy and do the wiring).

Below are two helper functions.

  • draw_keyword_highlights relies on the tokens array provided by 4coder for C++ file. It will set any C++ identifier corresponding the Highlight_Pair.needle to the corresponding color.
  • draw_string_highlights will do simple text search and color any text match with the corresponding color. If you search for key and there is the word keyword in the text, it will highlight key in keyword. Note that this will search the text that is in view for each entry in the pairs array. So there might be performance issues if there is a lot of entries.
typedef struct Highlight_Pair {
    String_Const_u8 needle;
    ARGB_Color color;
} Highlight_Pair;

/* NOTE: based on draw_comment_highlights. */
function void draw_keyword_highlights( Application_Links *app, Buffer_ID buffer, Text_Layout_ID text_layout_id, Token_Array *array, Highlight_Pair *pairs, i32 pair_count ) {
    
    Scratch_Block scratch( app );
    Range_i64 visible_range = text_layout_get_visible_range( app, text_layout_id );
    i64 first_index = token_index_from_pos( array, visible_range.first );
    Token_Iterator_Array it = token_iterator_index( buffer, array, first_index );
    
    for ( ; ; ) {
        
        Temp_Memory_Block temp( scratch );
        Token *token = token_it_read( &it );
        if ( token->pos >= visible_range.one_past_last ){
            break;
        }
        
        String_Const_u8 tail = { 0 };
        
        if ( token_it_check_and_get_lexeme( app, scratch, &it, TokenBaseKind_Identifier, &tail ) ){
            
            Highlight_Pair *pair = pairs;
            
            for ( i32 i = 0; i < pair_count; i += 1, pair += 1 ) {
                
                if ( string_match( tail, pair->needle ) ) {
                    Range_i64 range = Ii64_size( token->pos, token->size );
                    paint_text_color( app, text_layout_id, range, pair->color );
                    break;
                }
            }
        }
        
        if ( !token_it_inc_non_whitespace( &it ) ){
            break;
        }
    }
}

function void draw_string_highlights( Application_Links *app, Buffer_ID buffer, Text_Layout_ID text_layout_id, Highlight_Pair *pairs, i32 pair_count ) {
    
    Range_i64 visible_range = text_layout_get_visible_range( app, text_layout_id );
    
    Highlight_Pair* pair = pairs;
    
    for ( i32 i = 0; i < pair_count; i += 1, pair += 1 ) {
        
        if ( pair->needle.size <= 0 ) {
            continue;
        }
        
        i64 position = visible_range.min;
        seek_string_insensitive_forward( app, buffer, position - 1, visible_range.max, pair->needle, &position );
        
        while ( position < visible_range.max ) {
            
            Range_i64 range = Ii64_size( position, pair->needle.size );
            paint_text_color( app, text_layout_id, range, pair->color );
            seek_string_insensitive_forward( app, buffer, position, visible_range.max, pair->needle, &position );
        }
    }
}

To use the keyword highlight, in default_render_buffer, just below the part that highlight TODOs and NOTEs:

draw_cpp_token_colors(app, text_layout_id, &token_array);
        
// NOTE(allen): Scan for TODOs and NOTEs
if (global_config.use_comment_keyword){
    Comment_Highlight_Pair pairs[] = {
        {string_u8_litexpr("NOTE"), finalize_color(defcolor_comment_pop, 0)},
        {string_u8_litexpr("TODO"), finalize_color(defcolor_comment_pop, 1)},
    };
    draw_comment_highlights(app, buffer, text_layout_id, &token_array, pairs, ArrayCount(pairs));
}

Highlight_Pair pairs[ ] = {
    string_u8_litexpr( "u8" ), finalize_color( defcolor_keyword, 0 ), /* Use theme color "defcolor_keyword" first color. */
    string_u8_litexpr( "u16" ), finalize_color( defcolor_keyword, 0 ),
    string_u8_litexpr( "u32" ), finalize_color( defcolor_keyword, 0 ),
    string_u8_litexpr( "u64" ), finalize_color( defcolor_keyword, 0 ),
    string_u8_litexpr( "u64" ), finalize_color( defcolor_keyword, 0 ),
    string_u8_litexpr( "__debugbreak" ), 0xffa46391, /* Hardcoded colors work too. */
    ...
};
        
draw_keyword_highlights( app, buffer, text_layout_id, &token_array, pairs, ArrayCount( pairs ) );

To use the simple text highlight, in default_render_buffer (for example before the line highlight part)

Highlight_Pair pairs[ ] = {
    string_u8_litexpr( "some string" ), finalize_color( defcolor_keyword, 0 ), /* Use theme color "defcolor_keyword" first color. */
    string_u8_litexpr( "other string" ), 0xffa46391, /* Hardcoded colors work too. */
    ...
};
        
draw_string_highlights( app, buffer, text_layout_id, pairs, ArrayCount( pairs ) );

Modal customization layer

You can use the key map system to make a modal version of 4coder. Once again key map changes are on the buffer, not at the application level or view level (by default). Here is a small example.

/*
4coder_default_bidings.cpp - Supplies the default bindings used for default 4coder behavior.
*/

// TOP

#if !defined(FCODER_DEFAULT_BINDINGS_CPP)
#define FCODER_DEFAULT_BINDINGS_CPP

#include "4coder_default_include.cpp"

// NOTE(allen): Users can declare their own managed IDs here.

#include "generated/managed_id_metadata.cpp"

String_ID mapid_shared;
String_ID mapid_normal;
String_ID mapid_insert;
String_ID mapid_delete;

void set_current_mapid( Application_Links* app, Command_Map_ID mapid ) {

    View_ID view = get_active_view( app, 0 );
    Buffer_ID buffer = view_get_buffer( app, view, 0 );
    Managed_Scope scope = buffer_get_managed_scope( app, buffer );
    Command_Map_ID* map_id_ptr = scope_attachment( app, scope, buffer_map_id, Command_Map_ID );
    *map_id_ptr = mapid;
}

CUSTOM_COMMAND_SIG( go_to_normal_mode ) {

    set_current_mapid( app, mapid_normal );

    active_color_table.arrays[ defcolor_cursor ].vals[ 0 ] = 0xffff5533;
    active_color_table.arrays[ defcolor_at_cursor ].vals[ 0 ] = 0xff00aacc;
    active_color_table.arrays[ defcolor_margin_active ].vals[ 0 ] = 0xffff5533;
}

CUSTOM_COMMAND_SIG( go_to_insert_mode ) {

    set_current_mapid( app, mapid_insert );

    active_color_table.arrays[ defcolor_cursor ].vals[ 0 ] = 0xff80ff80;
    active_color_table.arrays[ defcolor_at_cursor ].vals[ 0 ] = 0xff293134;
    active_color_table.arrays[ defcolor_margin_active ].vals[ 0 ] = 0xff80ff80;
}

CUSTOM_COMMAND_SIG( go_to_delete_mode ) {

    set_current_mapid( app, mapid_delete );

    active_color_table.arrays[ defcolor_cursor ].vals[ 0 ] = 0xffffff00;
    active_color_table.arrays[ defcolor_at_cursor ].vals[ 0 ] = 0xff0000ff;
}

CUSTOM_COMMAND_SIG( modal_delete_word_right ) {
    delete_alpha_numeric_boundary( app );
    go_to_normal_mode( app );
}

CUSTOM_COMMAND_SIG( modal_delete_word_left ) {
    backspace_alpha_numeric_boundary( app );
    go_to_normal_mode( app );
}

void
custom_layer_init(Application_Links *app){
    Thread_Context *tctx = get_thread_context(app);

    // NOTE(allen): setup for default framework
    default_framework_init(app);

    // NOTE(allen): default hooks and command maps
    set_all_default_hooks(app);
    mapping_init(tctx, &framework_mapping);

    String_ID global_map_id = vars_save_string_lit("keys_global");
    String_ID file_map_id = vars_save_string_lit("keys_file");
    String_ID code_map_id = vars_save_string_lit("keys_code");

    mapid_shared = vars_save_string_lit( "mapid_shared" );
    mapid_normal = vars_save_string_lit( "mapid_normal" );
    mapid_insert = vars_save_string_lit( "mapid_insert" );
    mapid_delete = vars_save_string_lit( "mapid_delete" );

    MappingScope( );
    SelectMapping( &framework_mapping );

    SelectMap( global_map_id );

    SelectMap( mapid_shared );
    BindCore( default_startup, CoreCode_Startup );
    BindCore( default_try_exit, CoreCode_TryExit );
    Bind( go_to_normal_mode, KeyCode_Escape );
    Bind( move_left, KeyCode_Left );
    Bind( move_right, KeyCode_Right );
    Bind( move_up, KeyCode_Up );
    Bind( move_down, KeyCode_Down );

    SelectMap( mapid_normal );
    ParentMap( mapid_shared );
    Bind( go_to_insert_mode, KeyCode_Tab );
    Bind( go_to_delete_mode, KeyCode_D );

    SelectMap( mapid_insert );
    ParentMap( mapid_shared );
    BindTextInput( write_text_and_auto_indent );

    SelectMap( mapid_delete );
    Bind( go_to_normal_mode, KeyCode_Escape );
    Bind( modal_delete_word_left, KeyCode_Left );
    Bind( modal_delete_word_right, KeyCode_Right );

    /* This is to make sure that the default bindings on the buffers will be mapid_normal. */
    SelectMap( file_map_id );
    ParentMap( mapid_normal );

    SelectMap( code_map_id );
    ParentMap( mapid_normal );
}

#endif //FCODER_DEFAULT_BINDINGS

// BOTTOM

How do I do something ?

The best way to find out how to do something is to look in the code how similar things are done. Most of the functions you use are accessible in the source files (but the code for the core of 4coder isn't available). And remember that you can attach the debugger to 4coder to debug everything in the custom layer. The 4coder website contains the documentation for the API.

  • You can use the command lister (Alt + x by default) to quickly search for command that might do what you want;
  • The command list_all_functions_current_buffer and list_all_functions_all_buffers are a quick way to find if a function you want doesn't already exists;
  • There are several other list_all commands to search in all open files;
  • If you want to open all 4coder source files you can use the default project.4coder at the root of the 4coder directory. Just start 4coder, open project.4coder file (to set the hot directory to the 4coder folder if it's not the case), and then use the command load_project;
  • The file custom\4coder_helper.cpp contains a lot of useful functions. I recommend reading all the function signature once to get a idea of what is available;

If you can't figure it out, you can ask for help in 4coder's handmade.network forum.


Edited by Simon Anciaux on Reason: Remove paid mention