2261 lines
90 KiB
Plaintext
2261 lines
90 KiB
Plaintext
package main
|
|
|
|
import "core:c"
|
|
import "core:os"
|
|
import "core:path/filepath"
|
|
import "core:math"
|
|
import "core:strings"
|
|
import "base:runtime"
|
|
import "core:fmt"
|
|
import "core:log"
|
|
import "core:mem"
|
|
import "core:slice"
|
|
import "vendor:sdl2"
|
|
import "vendor:sdl2/ttf"
|
|
import lua "vendor:lua/5.4"
|
|
|
|
import "core"
|
|
import "theme"
|
|
import "ui"
|
|
import "plugin"
|
|
|
|
HardcodedFontPath :: "bin/BerkeleyMono-Regular.ttf";
|
|
|
|
State :: core.State;
|
|
FileBuffer :: core.FileBuffer;
|
|
|
|
// TODO: should probably go into state
|
|
scratch: mem.Scratch;
|
|
scratch_alloc: runtime.Allocator;
|
|
state := core.State {};
|
|
|
|
StateWithUi :: struct {
|
|
state: ^State,
|
|
ui_context: ^ui.Context,
|
|
}
|
|
|
|
// TODO: why do I have this here again?
|
|
// TODO: use buffer list in state
|
|
do_normal_mode :: proc(state: ^State, buffer: ^FileBuffer) {
|
|
if state.current_input_map != nil {
|
|
// if raylib.IsKeyPressed(.ESCAPE) {
|
|
// core.request_window_close(state);
|
|
// } else if raylib.IsKeyDown(.LEFT_CONTROL) {
|
|
// for key, action in &state.current_input_map.ctrl_key_actions {
|
|
// if raylib.IsKeyPressed(key) {
|
|
// switch value in action.action {
|
|
// case core.PluginEditorAction:
|
|
// value(state.plugin_vtable);
|
|
// case core.EditorAction:
|
|
// value(state);
|
|
// case core.InputMap:
|
|
// state.current_input_map = &(&state.current_input_map.ctrl_key_actions[key]).action.(core.InputMap)
|
|
// }
|
|
// }
|
|
// }
|
|
// } else {
|
|
// for key, action in state.current_input_map.key_actions {
|
|
// if raylib.IsKeyPressed(key) {
|
|
// switch value in action.action {
|
|
// case core.PluginEditorAction:
|
|
// value(state.plugin_vtable);
|
|
// case core.EditorAction:
|
|
// value(state);
|
|
// case core.InputMap:
|
|
// state.current_input_map = &(&state.current_input_map.key_actions[key]).action.(core.InputMap)
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
}
|
|
}
|
|
|
|
// TODO: use buffer list in state
|
|
do_insert_mode :: proc(state: ^State, buffer: ^FileBuffer) {
|
|
key := 0;
|
|
|
|
for key > 0 {
|
|
if key >= 32 && key <= 125 && len(buffer.input_buffer) < 1024-1 {
|
|
append(&buffer.input_buffer, u8(key));
|
|
|
|
for hook_proc in state.hooks[plugin.Hook.BufferInput] {
|
|
hook_proc(state.plugin_vtable, buffer);
|
|
}
|
|
}
|
|
|
|
key = 0;
|
|
}
|
|
}
|
|
|
|
do_visual_mode :: proc(state: ^State, buffer: ^FileBuffer) {
|
|
}
|
|
|
|
register_default_leader_actions :: proc(input_map: ^core.InputActions) {
|
|
core.register_key_action(input_map, .Q, proc(state: ^State) {
|
|
state.current_input_map = &state.input_map.mode[state.mode];
|
|
}, "close this help");
|
|
}
|
|
|
|
register_default_go_actions :: proc(input_map: ^core.InputActions) {
|
|
core.register_key_action(input_map, .H, proc(state: ^State) {
|
|
core.move_cursor_start_of_line(core.current_buffer(state));
|
|
state.current_input_map = &state.input_map.mode[state.mode];
|
|
}, "move to beginning of line");
|
|
core.register_key_action(input_map, .L, proc(state: ^State) {
|
|
core.move_cursor_end_of_line(core.current_buffer(state));
|
|
state.current_input_map = &state.input_map.mode[state.mode];
|
|
}, "move to end of line");
|
|
}
|
|
|
|
register_default_input_actions :: proc(input_map: ^core.InputActions) {
|
|
// Cursor Movement
|
|
{
|
|
core.register_key_action(input_map, .W, proc(state: ^State) {
|
|
core.move_cursor_forward_start_of_word(core.current_buffer(state));
|
|
}, "move forward one word");
|
|
core.register_key_action(input_map, .E, proc(state: ^State) {
|
|
core.move_cursor_forward_end_of_word(core.current_buffer(state));
|
|
}, "move forward to end of word");
|
|
|
|
core.register_key_action(input_map, .B, proc(state: ^State) {
|
|
core.move_cursor_backward_start_of_word(core.current_buffer(state));
|
|
}, "move backward one word");
|
|
|
|
core.register_key_action(input_map, .K, proc(state: ^State) {
|
|
core.move_cursor_up(core.current_buffer(state));
|
|
}, "move up one line");
|
|
core.register_key_action(input_map, .J, proc(state: ^State) {
|
|
core.move_cursor_down(core.current_buffer(state));
|
|
}, "move down one line");
|
|
core.register_key_action(input_map, .H, proc(state: ^State) {
|
|
core.move_cursor_left(core.current_buffer(state));
|
|
}, "move left one char");
|
|
core.register_key_action(input_map, .L, proc(state: ^State) {
|
|
core.move_cursor_right(core.current_buffer(state));
|
|
}, "move right one char");
|
|
|
|
core.register_ctrl_key_action(input_map, .U, proc(state: ^State) {
|
|
core.scroll_file_buffer(core.current_buffer(state), .Up);
|
|
}, "scroll buffer up");
|
|
core.register_ctrl_key_action(input_map, .D, proc(state: ^State) {
|
|
core.scroll_file_buffer(core.current_buffer(state), .Down);
|
|
}, "scroll buffer up");
|
|
}
|
|
|
|
// Scale font size
|
|
{
|
|
core.register_ctrl_key_action(input_map, .MINUS, proc(state: ^State) {
|
|
if state.source_font_height > 16 {
|
|
state.source_font_height -= 2;
|
|
state.source_font_width = state.source_font_height / 2;
|
|
|
|
state.font_atlas = core.gen_font_atlas(state, HardcodedFontPath);
|
|
}
|
|
log.debug(state.source_font_height);
|
|
}, "increase font size");
|
|
core.register_ctrl_key_action(input_map, .EQUAL, proc(state: ^State) {
|
|
state.source_font_height += 2;
|
|
state.source_font_width = state.source_font_height / 2;
|
|
|
|
state.font_atlas = core.gen_font_atlas(state, HardcodedFontPath);
|
|
}, "decrease font size");
|
|
}
|
|
|
|
core.register_key_action(input_map, .SPACE, core.new_input_actions(), "leader commands");
|
|
register_default_leader_actions(&(&input_map.key_actions[.SPACE]).action.(core.InputActions));
|
|
|
|
core.register_key_action(input_map, .G, core.new_input_actions(), "Go commands");
|
|
register_default_go_actions(&(&input_map.key_actions[.G]).action.(core.InputActions));
|
|
|
|
core.register_key_action(&state.input_map.mode[.Normal], .V, proc(state: ^State) {
|
|
state.mode = .Visual;
|
|
state.current_input_map = &state.input_map.mode[.Visual];
|
|
|
|
core.current_buffer(state).selection = core.new_selection(core.current_buffer(state).cursor);
|
|
}, "enter visual mode");
|
|
|
|
}
|
|
|
|
register_default_visual_actions :: proc(input_map: ^core.InputActions) {
|
|
core.register_key_action(input_map, .ESCAPE, proc(state: ^State) {
|
|
state.mode = .Normal;
|
|
state.current_input_map = &state.input_map.mode[.Normal];
|
|
|
|
core.current_buffer(state).selection = nil;
|
|
}, "exit visual mode");
|
|
|
|
// Cursor Movement
|
|
{
|
|
core.register_key_action(input_map, .W, proc(state: ^State) {
|
|
sel_cur := core.current_buffer(state).selection.?;
|
|
|
|
core.move_cursor_forward_start_of_word(core.current_buffer(state), cursor = &sel_cur.end);
|
|
}, "move forward one word");
|
|
core.register_key_action(input_map, .E, proc(state: ^State) {
|
|
sel_cur := &(core.current_buffer(state).selection.?);
|
|
|
|
core.move_cursor_forward_end_of_word(core.current_buffer(state), cursor = &sel_cur.end);
|
|
}, "move forward to end of word");
|
|
|
|
core.register_key_action(input_map, .B, proc(state: ^State) {
|
|
sel_cur := &(core.current_buffer(state).selection.?);
|
|
|
|
core.move_cursor_backward_start_of_word(core.current_buffer(state), cursor = &sel_cur.end);
|
|
}, "move backward one word");
|
|
|
|
core.register_key_action(input_map, .K, proc(state: ^State) {
|
|
sel_cur := &(core.current_buffer(state).selection.?);
|
|
|
|
core.move_cursor_up(core.current_buffer(state), cursor = &sel_cur.end);
|
|
}, "move up one line");
|
|
core.register_key_action(input_map, .J, proc(state: ^State) {
|
|
sel_cur := &(core.current_buffer(state).selection.?);
|
|
|
|
core.move_cursor_down(core.current_buffer(state), cursor = &sel_cur.end);
|
|
}, "move down one line");
|
|
core.register_key_action(input_map, .H, proc(state: ^State) {
|
|
sel_cur := &(core.current_buffer(state).selection.?);
|
|
|
|
core.move_cursor_left(core.current_buffer(state), cursor = &sel_cur.end);
|
|
}, "move left one char");
|
|
core.register_key_action(input_map, .L, proc(state: ^State) {
|
|
sel_cur := &(core.current_buffer(state).selection.?);
|
|
|
|
core.move_cursor_right(core.current_buffer(state), cursor = &sel_cur.end);
|
|
}, "move right one char");
|
|
|
|
core.register_ctrl_key_action(input_map, .U, proc(state: ^State) {
|
|
sel_cur := &(core.current_buffer(state).selection.?);
|
|
|
|
core.scroll_file_buffer(core.current_buffer(state), .Up, cursor = &sel_cur.end);
|
|
}, "scroll buffer up");
|
|
core.register_ctrl_key_action(input_map, .D, proc(state: ^State) {
|
|
sel_cur := &(core.current_buffer(state).selection.?);
|
|
|
|
core.scroll_file_buffer(core.current_buffer(state), .Down, cursor = &sel_cur.end);
|
|
}, "scroll buffer up");
|
|
}
|
|
}
|
|
|
|
register_default_text_input_actions :: proc(input_map: ^core.InputActions) {
|
|
core.register_key_action(input_map, .I, proc(state: ^State) {
|
|
state.mode = .Insert;
|
|
sdl2.StartTextInput();
|
|
}, "enter insert mode");
|
|
core.register_key_action(input_map, .A, proc(state: ^State) {
|
|
core.move_cursor_right(core.current_buffer(state), false);
|
|
state.mode = .Insert;
|
|
sdl2.StartTextInput();
|
|
}, "enter insert mode after character (append)");
|
|
|
|
// TODO: add shift+o to insert newline above current one
|
|
|
|
core.register_key_action(input_map, .O, proc(state: ^State) {
|
|
core.move_cursor_end_of_line(core.current_buffer(state), false);
|
|
core.insert_content(core.current_buffer(state), []u8{'\n'});
|
|
core.move_cursor_down(core.current_buffer(state));
|
|
state.mode = .Insert;
|
|
|
|
sdl2.StartTextInput();
|
|
}, "insert mode on newline");
|
|
}
|
|
|
|
load_plugin :: proc(info: os.File_Info, in_err: os.Errno, state: rawptr) -> (err: os.Errno, skip_dir: bool) {
|
|
state := cast(^State)state;
|
|
|
|
relative_file_path, rel_error := filepath.rel(state.directory, info.fullpath);
|
|
extension := filepath.ext(info.fullpath);
|
|
|
|
if extension == ".dylib" || extension == ".dll" || extension == ".so" {
|
|
if loaded_plugin, succ := plugin.try_load_plugin(info.fullpath); succ {
|
|
append(&state.plugins, loaded_plugin);
|
|
|
|
if rel_error == .None {
|
|
log.info("Loaded", relative_file_path);
|
|
} else {
|
|
log.info("Loaded", info.fullpath);
|
|
}
|
|
}
|
|
}
|
|
|
|
return in_err, skip_dir;
|
|
}
|
|
|
|
ui_font_width :: proc() -> i32 {
|
|
return i32(state.source_font_width);
|
|
}
|
|
ui_font_height :: proc() -> i32 {
|
|
return i32(state.source_font_height);
|
|
}
|
|
|
|
draw :: proc(state_with_ui: ^StateWithUi) {
|
|
if buffer := core.current_buffer(state_with_ui.state); buffer != nil {
|
|
buffer.glyph_buffer_height = math.min(256, int((state_with_ui.state.screen_height - state_with_ui.state.source_font_height*2) / state_with_ui.state.source_font_height)) + 1;
|
|
buffer.glyph_buffer_width = math.min(256, int((state_with_ui.state.screen_width - state_with_ui.state.source_font_width) / state_with_ui.state.source_font_width));
|
|
}
|
|
|
|
render_color := theme.get_palette_color(.Background);
|
|
sdl2.SetRenderDrawColor(state_with_ui.state.sdl_renderer, render_color.r, render_color.g, render_color.b, render_color.a);
|
|
sdl2.RenderClear(state_with_ui.state.sdl_renderer);
|
|
|
|
// if state_with_ui.state.window != nil && state_with_ui.state.window.draw != nil {
|
|
// state_with_ui.state.window.draw(state_with_ui.state.plugin_vtable, state_with_ui.state.window.user_data);
|
|
// }
|
|
|
|
ui.compute_layout(state_with_ui.ui_context, { state_with_ui.state.screen_width, state_with_ui.state.screen_height }, state_with_ui.state.source_font_width, state_with_ui.state.source_font_height, state_with_ui.ui_context.root);
|
|
ui.draw(state_with_ui.ui_context, state_with_ui.state, state_with_ui.state.source_font_width, state_with_ui.state.source_font_height, state_with_ui.ui_context.root);
|
|
|
|
if state_with_ui.state.mode != .Insert && state_with_ui.state.current_input_map != &state_with_ui.state.input_map.mode[state_with_ui.state.mode] {
|
|
longest_description := 0;
|
|
for key, action in state_with_ui.state.current_input_map.key_actions {
|
|
if len(action.description) > longest_description {
|
|
longest_description = len(action.description);
|
|
}
|
|
}
|
|
for key, action in state_with_ui.state.current_input_map.ctrl_key_actions {
|
|
if len(action.description) > longest_description {
|
|
longest_description = len(action.description);
|
|
}
|
|
}
|
|
longest_description += 8;
|
|
|
|
helper_height := state_with_ui.state.source_font_height * (len(state_with_ui.state.current_input_map.key_actions) + len(state_with_ui.state.current_input_map.ctrl_key_actions));
|
|
offset_from_bottom := state_with_ui.state.source_font_height * 2;
|
|
|
|
core.draw_rect(
|
|
state_with_ui.state,
|
|
state_with_ui.state.screen_width - longest_description * state_with_ui.state.source_font_width,
|
|
state_with_ui.state.screen_height - helper_height - offset_from_bottom,
|
|
longest_description*state_with_ui.state.source_font_width,
|
|
helper_height,
|
|
.Background2
|
|
);
|
|
|
|
index := 0;
|
|
for key, action in state_with_ui.state.current_input_map.key_actions {
|
|
core.draw_text(
|
|
state_with_ui.state,
|
|
fmt.tprintf("%s - %s", key, action.description),
|
|
state_with_ui.state.screen_width - longest_description * state_with_ui.state.source_font_width,
|
|
state_with_ui.state.screen_height - helper_height + index * state_with_ui.state.source_font_height - offset_from_bottom
|
|
);
|
|
|
|
index += 1;
|
|
}
|
|
for key, action in state_with_ui.state.current_input_map.ctrl_key_actions {
|
|
core.draw_text(
|
|
state_with_ui.state,
|
|
fmt.tprintf("<C>-%s - %s", key, action.description),
|
|
state_with_ui.state.screen_width - longest_description * state_with_ui.state.source_font_width,
|
|
state_with_ui.state.screen_height - helper_height + index * state_with_ui.state.source_font_height - offset_from_bottom
|
|
);
|
|
|
|
index += 1;
|
|
}
|
|
}
|
|
|
|
sdl2.RenderPresent(state_with_ui.state.sdl_renderer);
|
|
}
|
|
|
|
expose_event_watcher :: proc "c" (state: rawptr, event: ^sdl2.Event) -> i32 {
|
|
if event.type == .WINDOWEVENT {
|
|
state := transmute(^StateWithUi)state;
|
|
context = state.state.ctx;
|
|
|
|
if event.window.event == .EXPOSED {
|
|
//draw(state);
|
|
} else if event.window.event == .SIZE_CHANGED {
|
|
w,h: i32;
|
|
|
|
sdl2.GetRendererOutputSize(state.state.sdl_renderer, &w, &h);
|
|
|
|
state.state.screen_width = int(w);
|
|
state.state.screen_height = int(h);
|
|
state.state.width_dpi_ratio = f32(w) / f32(event.window.data1);
|
|
state.state.height_dpi_ratio = f32(h) / f32(event.window.data2);
|
|
|
|
draw(state);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
ui_file_buffer :: proc(ctx: ^ui.Context, buffer: ^FileBuffer) -> ui.Interaction {
|
|
draw_func := proc(state: ^State, box: ^ui.Box, user_data: rawptr) {
|
|
buffer := transmute(^FileBuffer)user_data;
|
|
buffer.glyph_buffer_width = box.computed_size.x / state.source_font_width;
|
|
buffer.glyph_buffer_height = box.computed_size.y / state.source_font_height + 1;
|
|
|
|
core.draw_file_buffer(state, buffer, box.computed_pos.x, box.computed_pos.y);
|
|
};
|
|
|
|
relative_file_path, _ := filepath.rel(state.directory, buffer.file_path, context.temp_allocator)
|
|
|
|
buffer_container, _ := ui.push_box(ctx, relative_file_path, {}, .Vertical, semantic_size = {ui.make_semantic_size(.Fill), ui.make_semantic_size(.Fill)});
|
|
ui.push_parent(ctx, buffer_container);
|
|
defer ui.pop_parent(ctx);
|
|
|
|
interaction := ui.custom(ctx, "buffer1", draw_func, transmute(rawptr)buffer);
|
|
|
|
{
|
|
info_box, _ := ui.push_box(ctx, "buffer info", {}, semantic_size = {ui.make_semantic_size(.Fill), ui.make_semantic_size(.Exact, state.source_font_height)});
|
|
ui.push_parent(ctx, info_box);
|
|
defer ui.pop_parent(ctx);
|
|
|
|
ui.label(ctx, fmt.tprintf("%s", state.mode))
|
|
if selection, exists := buffer.selection.?; exists {
|
|
ui.label(ctx, fmt.tprintf("sel: %d:%d", selection.end.line, selection.end.col));
|
|
}
|
|
ui.spacer(ctx, "spacer");
|
|
ui.label(ctx, relative_file_path);
|
|
}
|
|
|
|
return interaction;
|
|
}
|
|
|
|
init_plugin_vtable :: proc(ui_context: ^ui.Context) -> plugin.Plugin {
|
|
return plugin.Plugin {
|
|
state = cast(rawptr)&state,
|
|
register_hook = proc "c" (hook: plugin.Hook, on_hook: plugin.OnHookProc) {
|
|
context = state.ctx;
|
|
|
|
core.add_hook(&state, hook, on_hook);
|
|
},
|
|
register_highlighter = proc "c" (extension: cstring, on_color_buffer: plugin.OnColorBufferProc) {
|
|
context = state.ctx;
|
|
|
|
extension := strings.clone(string(extension));
|
|
|
|
if _, exists := state.highlighters[extension]; exists {
|
|
log.error("Highlighter already registered for", extension, "files");
|
|
} else {
|
|
state.highlighters[extension] = on_color_buffer;
|
|
}
|
|
},
|
|
register_input_group = proc "c" (input_map: rawptr, key: plugin.Key, register_group: plugin.InputGroupProc) {
|
|
context = state.ctx;
|
|
|
|
to_be_edited_map: ^core.InputActions = nil;
|
|
|
|
if input_map != nil {
|
|
to_be_edited_map = transmute(^core.InputActions)input_map;
|
|
} else {
|
|
to_be_edited_map = state.current_input_map;
|
|
}
|
|
|
|
// TODO: change this to use the given mode
|
|
if action, exists := to_be_edited_map.key_actions[key]; exists {
|
|
switch value in action.action {
|
|
case core.LuaEditorAction:
|
|
log.warn("Plugin attempted to register input group on existing key action (added from Lua)");
|
|
case core.PluginEditorAction:
|
|
log.warn("Plugin attempted to register input group on existing key action (added from Plugin)");
|
|
case core.EditorAction:
|
|
log.warn("Plugin attempted to register input group on existing key action");
|
|
case core.InputActions:
|
|
input_map := &(&to_be_edited_map.key_actions[key]).action.(core.InputActions);
|
|
register_group(state.plugin_vtable, transmute(rawptr)input_map);
|
|
}
|
|
} else {
|
|
core.register_key_action(to_be_edited_map, key, core.new_input_actions(), "PLUGIN INPUT GROUP");
|
|
register_group(state.plugin_vtable, &(&to_be_edited_map.key_actions[key]).action.(core.InputActions));
|
|
}
|
|
},
|
|
register_input = proc "c" (input_map: rawptr, key: plugin.Key, input_action: plugin.InputActionProc, description: cstring) {
|
|
context = state.ctx;
|
|
|
|
to_be_edited_map: ^core.InputActions = nil;
|
|
description := strings.clone(string(description));
|
|
|
|
if input_map != nil {
|
|
to_be_edited_map = transmute(^core.InputActions)input_map;
|
|
} else {
|
|
to_be_edited_map = state.current_input_map;
|
|
}
|
|
|
|
if action, exists := to_be_edited_map.key_actions[key]; exists {
|
|
switch value in action.action {
|
|
case core.LuaEditorAction:
|
|
log.warn("Plugin attempted to register key action on existing key action (added from Lua)");
|
|
case core.PluginEditorAction:
|
|
log.warn("Plugin attempted to register key action on existing key action (added from Plugin)");
|
|
case core.EditorAction:
|
|
log.warn("Plugin attempted to register input key action on existing key action");
|
|
case core.InputActions:
|
|
log.warn("Plugin attempted to register input key action on existing input group");
|
|
}
|
|
} else {
|
|
core.register_key_action(to_be_edited_map, key, input_action, description);
|
|
}
|
|
},
|
|
create_window = proc "c" (user_data: rawptr, register_group: plugin.InputGroupProc, draw_proc: plugin.WindowDrawProc, free_window_proc: plugin.WindowFreeProc, get_buffer_proc: plugin.WindowGetBufferProc) -> rawptr {
|
|
context = state.ctx;
|
|
window := new(core.Window);
|
|
window^ = core.Window {
|
|
input_map = core.new_input_actions(),
|
|
draw = draw_proc,
|
|
get_buffer = get_buffer_proc,
|
|
free_user_data = free_window_proc,
|
|
|
|
user_data = user_data,
|
|
};
|
|
|
|
register_group(state.plugin_vtable, transmute(rawptr)&window.input_map);
|
|
|
|
state.window = window;
|
|
state.current_input_map = &window.input_map;
|
|
|
|
return window;
|
|
},
|
|
get_window = proc "c" () -> rawptr {
|
|
if state.window != nil {
|
|
return state.window.user_data;
|
|
}
|
|
|
|
return nil;
|
|
},
|
|
request_window_close = proc "c" () {
|
|
context = state.ctx;
|
|
|
|
core.request_window_close(&state);
|
|
},
|
|
get_screen_width = proc "c" () -> int {
|
|
return state.screen_width;
|
|
},
|
|
get_screen_height = proc "c" () -> int {
|
|
return state.screen_height;
|
|
},
|
|
get_font_width = proc "c" () -> int {
|
|
return state.source_font_width;
|
|
},
|
|
get_font_height = proc "c" () -> int {
|
|
return state.source_font_height;
|
|
},
|
|
get_current_directory = proc "c" () -> cstring {
|
|
context = state.ctx;
|
|
|
|
return strings.clone_to_cstring(state.directory, context.temp_allocator);
|
|
},
|
|
enter_insert_mode = proc "c" () {
|
|
state.mode = .Insert;
|
|
sdl2.StartTextInput();
|
|
},
|
|
draw_rect = proc "c" (x: i32, y: i32, width: i32, height: i32, color: theme.PaletteColor) {
|
|
context = state.ctx;
|
|
|
|
core.draw_rect(&state, int(x), int(y), int(width), int(height), color);
|
|
},
|
|
draw_text = proc "c" (text: cstring, x: f32, y: f32, color: theme.PaletteColor) {
|
|
context = state.ctx;
|
|
|
|
core.draw_text(&state, string(text), int(x), int(y), color);
|
|
},
|
|
draw_buffer_from_index = proc "c" (buffer_index: int, x: int, y: int, glyph_buffer_width: int, glyph_buffer_height: int, show_line_numbers: bool) {
|
|
context = state.ctx;
|
|
core.buffer_from_index(&state, buffer_index).glyph_buffer_width = glyph_buffer_width;
|
|
core.buffer_from_index(&state, buffer_index).glyph_buffer_height = glyph_buffer_height;
|
|
|
|
core.draw_file_buffer(
|
|
&state,
|
|
core.buffer_from_index(&state, buffer_index),
|
|
x,
|
|
y,
|
|
show_line_numbers);
|
|
},
|
|
draw_buffer = proc "c" (buffer: rawptr, x: int, y: int, glyph_buffer_width: int, glyph_buffer_height: int, show_line_numbers: bool) {
|
|
context = state.ctx;
|
|
|
|
buffer := transmute(^core.FileBuffer)buffer;
|
|
buffer.glyph_buffer_width = glyph_buffer_width;
|
|
buffer.glyph_buffer_height = glyph_buffer_height;
|
|
|
|
core.draw_file_buffer(
|
|
&state,
|
|
buffer,
|
|
x,
|
|
y,
|
|
show_line_numbers);
|
|
},
|
|
iter = plugin.Iterator {
|
|
get_current_buffer_iterator = proc "c" () -> plugin.BufferIter {
|
|
context = state.ctx;
|
|
|
|
it := core.new_file_buffer_iter(core.current_buffer(&state));
|
|
|
|
// TODO: make this into a function
|
|
return plugin.BufferIter {
|
|
cursor = plugin.Cursor {
|
|
col = it.cursor.col,
|
|
line = it.cursor.line,
|
|
index = plugin.BufferIndex {
|
|
slice_index = it.cursor.index.slice_index,
|
|
content_index = it.cursor.index.content_index,
|
|
}
|
|
},
|
|
buffer = cast(rawptr)it.buffer,
|
|
hit_end = it.hit_end,
|
|
}
|
|
},
|
|
get_buffer_iterator = proc "c" (buffer: rawptr) -> plugin.BufferIter {
|
|
buffer := cast(^core.FileBuffer)buffer;
|
|
context = state.ctx;
|
|
|
|
it := core.new_file_buffer_iter(buffer);
|
|
|
|
// TODO: make this into a function
|
|
return plugin.BufferIter {
|
|
cursor = plugin.Cursor {
|
|
col = it.cursor.col,
|
|
line = it.cursor.line,
|
|
index = plugin.BufferIndex {
|
|
slice_index = it.cursor.index.slice_index,
|
|
content_index = it.cursor.index.content_index,
|
|
}
|
|
},
|
|
buffer = cast(rawptr)it.buffer,
|
|
hit_end = it.hit_end,
|
|
}
|
|
},
|
|
get_char_at_iter = proc "c" (it: ^plugin.BufferIter) -> u8 {
|
|
context = state.ctx;
|
|
|
|
internal_it := core.FileBufferIter {
|
|
cursor = core.Cursor {
|
|
col = it.cursor.col,
|
|
line = it.cursor.line,
|
|
index = core.FileBufferIndex {
|
|
slice_index = it.cursor.index.slice_index,
|
|
content_index = it.cursor.index.content_index,
|
|
}
|
|
},
|
|
buffer = cast(^core.FileBuffer)it.buffer,
|
|
hit_end = it.hit_end,
|
|
}
|
|
|
|
return core.get_character_at_iter(internal_it);
|
|
},
|
|
get_buffer_list_iter = proc "c" (prev_buffer: ^int) -> int {
|
|
context = state.ctx;
|
|
|
|
return core.next_buffer(&state, prev_buffer);
|
|
},
|
|
iterate_buffer = proc "c" (it: ^plugin.BufferIter) -> plugin.IterateResult {
|
|
context = state.ctx;
|
|
|
|
// TODO: make this into a function
|
|
internal_it := core.FileBufferIter {
|
|
cursor = core.Cursor {
|
|
col = it.cursor.col,
|
|
line = it.cursor.line,
|
|
index = core.FileBufferIndex {
|
|
slice_index = it.cursor.index.slice_index,
|
|
content_index = it.cursor.index.content_index,
|
|
}
|
|
},
|
|
buffer = cast(^core.FileBuffer)it.buffer,
|
|
hit_end = it.hit_end,
|
|
}
|
|
|
|
char, _, cond := core.iterate_file_buffer(&internal_it);
|
|
|
|
it^ = plugin.BufferIter {
|
|
cursor = plugin.Cursor {
|
|
col = internal_it.cursor.col,
|
|
line = internal_it.cursor.line,
|
|
index = plugin.BufferIndex {
|
|
slice_index = internal_it.cursor.index.slice_index,
|
|
content_index = internal_it.cursor.index.content_index,
|
|
}
|
|
},
|
|
buffer = cast(rawptr)internal_it.buffer,
|
|
hit_end = internal_it.hit_end,
|
|
};
|
|
|
|
return plugin.IterateResult {
|
|
char = char,
|
|
should_continue = cond,
|
|
};
|
|
},
|
|
iterate_buffer_reverse = proc "c" (it: ^plugin.BufferIter) -> plugin.IterateResult {
|
|
context = state.ctx;
|
|
|
|
// TODO: make this into a function
|
|
internal_it := core.FileBufferIter {
|
|
cursor = core.Cursor {
|
|
col = it.cursor.col,
|
|
line = it.cursor.line,
|
|
index = core.FileBufferIndex {
|
|
slice_index = it.cursor.index.slice_index,
|
|
content_index = it.cursor.index.content_index,
|
|
}
|
|
},
|
|
buffer = cast(^core.FileBuffer)it.buffer,
|
|
hit_end = it.hit_end,
|
|
}
|
|
|
|
char, _, cond := core.iterate_file_buffer_reverse(&internal_it);
|
|
|
|
it^ = plugin.BufferIter {
|
|
cursor = plugin.Cursor {
|
|
col = internal_it.cursor.col,
|
|
line = internal_it.cursor.line,
|
|
index = plugin.BufferIndex {
|
|
slice_index = internal_it.cursor.index.slice_index,
|
|
content_index = internal_it.cursor.index.content_index,
|
|
}
|
|
},
|
|
buffer = cast(rawptr)internal_it.buffer,
|
|
hit_end = internal_it.hit_end,
|
|
};
|
|
|
|
return plugin.IterateResult {
|
|
char = char,
|
|
should_continue = cond,
|
|
};
|
|
},
|
|
iterate_buffer_until = proc "c" (it: ^plugin.BufferIter, until_proc: rawptr) {
|
|
context = state.ctx;
|
|
|
|
// TODO: make this into a function
|
|
internal_it := core.FileBufferIter {
|
|
cursor = core.Cursor {
|
|
col = it.cursor.col,
|
|
line = it.cursor.line,
|
|
index = core.FileBufferIndex {
|
|
slice_index = it.cursor.index.slice_index,
|
|
content_index = it.cursor.index.content_index,
|
|
}
|
|
},
|
|
buffer = cast(^core.FileBuffer)it.buffer,
|
|
hit_end = it.hit_end,
|
|
}
|
|
|
|
core.iterate_file_buffer_until(&internal_it, transmute(core.UntilProc)until_proc);
|
|
|
|
it^ = plugin.BufferIter {
|
|
cursor = plugin.Cursor {
|
|
col = internal_it.cursor.col,
|
|
line = internal_it.cursor.line,
|
|
index = plugin.BufferIndex {
|
|
slice_index = internal_it.cursor.index.slice_index,
|
|
content_index = internal_it.cursor.index.content_index,
|
|
}
|
|
},
|
|
buffer = cast(rawptr)internal_it.buffer,
|
|
hit_end = internal_it.hit_end,
|
|
};
|
|
},
|
|
iterate_buffer_peek = proc "c" (it: ^plugin.BufferIter) -> plugin.IterateResult {
|
|
context = state.ctx;
|
|
|
|
// TODO: make this into a function
|
|
internal_it := core.FileBufferIter {
|
|
cursor = core.Cursor {
|
|
col = it.cursor.col,
|
|
line = it.cursor.line,
|
|
index = core.FileBufferIndex {
|
|
slice_index = it.cursor.index.slice_index,
|
|
content_index = it.cursor.index.content_index,
|
|
}
|
|
},
|
|
buffer = cast(^core.FileBuffer)it.buffer,
|
|
hit_end = it.hit_end,
|
|
}
|
|
|
|
char, _, cond := core.iterate_peek(&internal_it, core.iterate_file_buffer);
|
|
|
|
it^ = plugin.BufferIter {
|
|
cursor = plugin.Cursor {
|
|
col = internal_it.cursor.col,
|
|
line = internal_it.cursor.line,
|
|
index = plugin.BufferIndex {
|
|
slice_index = internal_it.cursor.index.slice_index,
|
|
content_index = internal_it.cursor.index.content_index,
|
|
}
|
|
},
|
|
buffer = cast(rawptr)internal_it.buffer,
|
|
hit_end = internal_it.hit_end,
|
|
};
|
|
|
|
return plugin.IterateResult {
|
|
char = char,
|
|
should_continue = cond,
|
|
};
|
|
},
|
|
until_line_break = transmute(rawptr)core.until_line_break,
|
|
until_single_quote = transmute(rawptr)core.until_single_quote,
|
|
until_double_quote = transmute(rawptr)core.until_double_quote,
|
|
until_end_of_word = transmute(rawptr)core.until_end_of_word,
|
|
},
|
|
buffer = plugin.Buffer {
|
|
get_num_buffers = proc "c" () -> int {
|
|
context = state.ctx;
|
|
|
|
return len(state.buffers);
|
|
},
|
|
get_buffer_info = proc "c" (buffer: rawptr) -> plugin.BufferInfo {
|
|
context = state.ctx;
|
|
buffer := cast(^core.FileBuffer)buffer;
|
|
|
|
return core.into_buffer_info(&state, buffer);
|
|
},
|
|
get_buffer_info_from_index = proc "c" (buffer_index: int) -> plugin.BufferInfo {
|
|
context = state.ctx;
|
|
buffer := core.buffer_from_index(&state, buffer_index);
|
|
|
|
return core.into_buffer_info(&state, buffer);
|
|
},
|
|
color_char_at = proc "c" (buffer: rawptr, start_cursor: plugin.Cursor, end_cursor: plugin.Cursor, palette_index: i32) {
|
|
buffer := cast(^core.FileBuffer)buffer;
|
|
context = state.ctx;
|
|
|
|
start_cursor := core.Cursor {
|
|
col = start_cursor.col,
|
|
line = start_cursor.line,
|
|
index = core.FileBufferIndex {
|
|
slice_index = start_cursor.index.slice_index,
|
|
content_index = start_cursor.index.content_index,
|
|
}
|
|
};
|
|
end_cursor := core.Cursor {
|
|
col = end_cursor.col,
|
|
line = end_cursor.line,
|
|
index = core.FileBufferIndex {
|
|
slice_index = end_cursor.index.slice_index,
|
|
content_index = end_cursor.index.content_index,
|
|
}
|
|
};
|
|
|
|
core.color_character(buffer, start_cursor, end_cursor, cast(theme.PaletteColor)palette_index);
|
|
},
|
|
set_current_buffer = proc "c" (buffer_index: int) {
|
|
state.current_buffer = buffer_index;
|
|
},
|
|
open_buffer = proc "c" (path: cstring, line: int, col: int) {
|
|
context = state.ctx;
|
|
|
|
fmt.eprintln("opening file from dll", path)
|
|
|
|
path := string(path);
|
|
should_create_buffer := true;
|
|
for buffer, index in state.buffers {
|
|
if strings.compare(buffer.file_path, path) == 0 {
|
|
state.current_buffer = index;
|
|
should_create_buffer = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
buffer: ^core.FileBuffer = nil;
|
|
err := core.no_error();
|
|
|
|
if should_create_buffer {
|
|
new_buffer, err := core.new_file_buffer(context.allocator, strings.clone(path));
|
|
if err.type != .None {
|
|
log.error("Failed to open/create file buffer:", err);
|
|
} else {
|
|
runtime.append(&state.buffers, new_buffer);
|
|
state.current_buffer = len(state.buffers)-1;
|
|
buffer = core.current_buffer(&state);
|
|
}
|
|
} else {
|
|
buffer = core.current_buffer(&state);
|
|
}
|
|
|
|
if buffer != nil {
|
|
buffer.cursor.line = line;
|
|
buffer.cursor.col = col;
|
|
buffer.glyph_buffer_height = math.min(256, int((state.screen_height - state.source_font_height*2) / state.source_font_height)) + 1;
|
|
buffer.glyph_buffer_width = math.min(256, int((state.screen_width - state.source_font_width) / state.source_font_width));
|
|
core.update_file_buffer_index_from_cursor(buffer);
|
|
}
|
|
},
|
|
open_virtual_buffer = proc "c" () -> rawptr {
|
|
context = state.ctx;
|
|
|
|
buffer := new(FileBuffer);
|
|
buffer^ = core.new_virtual_file_buffer(context.allocator);
|
|
|
|
return buffer;
|
|
},
|
|
free_virtual_buffer = proc "c" (buffer: rawptr) {
|
|
context = state.ctx;
|
|
|
|
if buffer != nil {
|
|
buffer := cast(^core.FileBuffer)buffer;
|
|
|
|
core.free_file_buffer(buffer);
|
|
free(buffer);
|
|
}
|
|
},
|
|
},
|
|
ui = plugin.Ui {
|
|
ui_context = ui_context,
|
|
|
|
push_parent = proc "c" (ui_context: rawptr, box: plugin.UiBox) {
|
|
context = state.ctx;
|
|
ui_context := transmute(^ui.Context)ui_context;
|
|
box := transmute(^ui.Box)box;
|
|
|
|
ui.push_parent(ui_context, box);
|
|
},
|
|
|
|
pop_parent = proc "c" (ui_context: rawptr) {
|
|
context = state.ctx;
|
|
ui_context := transmute(^ui.Context)ui_context;
|
|
|
|
ui.pop_parent(ui_context);
|
|
},
|
|
|
|
spacer = proc "c" (ui_context: rawptr, label: cstring) -> plugin.UiInteraction {
|
|
context = state.ctx;
|
|
ui_context := transmute(^ui.Context)ui_context;
|
|
label := strings.clone(string(label), context.temp_allocator);
|
|
|
|
interaction := ui.spacer(ui_context, label);
|
|
|
|
return plugin.UiInteraction {
|
|
hovering = interaction.hovering,
|
|
clicked = interaction.clicked,
|
|
};
|
|
},
|
|
// TODO: allow this to have more flags sent to it
|
|
floating = proc "c" (ui_context: rawptr, label: cstring, pos: [2]int) -> plugin.UiBox {
|
|
context = state.ctx;
|
|
ui_context := transmute(^ui.Context)ui_context;
|
|
label := strings.clone(string(label), context.temp_allocator);
|
|
|
|
floating, _ := ui.push_floating(ui_context, label, pos);
|
|
return floating;
|
|
},
|
|
rect = proc "c" (ui_context: rawptr, label: cstring, background: bool, border: bool, axis: plugin.UiAxis, size: [2]plugin.UiSemanticSize) -> plugin.UiBox {
|
|
context = state.ctx;
|
|
ui_context := transmute(^ui.Context)ui_context;
|
|
label := strings.clone(string(label), context.temp_allocator);
|
|
|
|
size := [2]ui.SemanticSize {
|
|
ui.SemanticSize {
|
|
kind = ui.SemanticSizeKind(size.x.kind),
|
|
value = size.x.value,
|
|
},
|
|
ui.SemanticSize {
|
|
kind = ui.SemanticSizeKind(size.y.kind),
|
|
value = size.y.value,
|
|
},
|
|
};
|
|
|
|
rect, _ := ui.push_rect(ui_context, label, background, border, ui.Axis(axis), size);
|
|
return rect;
|
|
},
|
|
|
|
label = proc "c" (ui_context: rawptr, label: cstring) -> plugin.UiInteraction {
|
|
context = state.ctx;
|
|
ui_context := transmute(^ui.Context)ui_context;
|
|
label := strings.clone(string(label), context.temp_allocator);
|
|
|
|
interaction := ui.label(ui_context, label);
|
|
|
|
return plugin.UiInteraction {
|
|
hovering = interaction.hovering,
|
|
clicked = interaction.clicked,
|
|
};
|
|
},
|
|
button = proc "c" (ui_context: rawptr, label: cstring) -> plugin.UiInteraction {
|
|
context = state.ctx;
|
|
ui_context := transmute(^ui.Context)ui_context;
|
|
label := strings.clone(string(label), context.temp_allocator);
|
|
|
|
interaction := ui.button(ui_context, label);
|
|
|
|
return plugin.UiInteraction {
|
|
hovering = interaction.hovering,
|
|
clicked = interaction.clicked,
|
|
};
|
|
},
|
|
buffer = proc "c" (ui_context: rawptr, buffer: rawptr, show_line_numbers: bool) {
|
|
context = state.ctx;
|
|
ui_context := transmute(^ui.Context)ui_context;
|
|
buffer := transmute(^FileBuffer)buffer;
|
|
|
|
ui_file_buffer(ui_context, buffer);
|
|
},
|
|
|
|
buffer_from_index = proc "c" (ui_context: rawptr, buffer_index: int, show_line_numbers: bool) {
|
|
context = state.ctx;
|
|
ui_context := transmute(^ui.Context)ui_context;
|
|
|
|
buffer := core.buffer_from_index(&state, buffer_index);
|
|
|
|
ui_file_buffer(ui_context, buffer);
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
lua_ui_flags :: proc(L: ^lua.State, index: i32) -> (bit_set[ui.Flag], bool) {
|
|
lua.L_checktype(L, index, i32(lua.TTABLE));
|
|
lua.len(L, index);
|
|
array_len := lua.tointeger(L, -1);
|
|
lua.pop(L, 1);
|
|
|
|
flags: bit_set[ui.Flag]
|
|
|
|
for i in 1..=array_len {
|
|
lua.rawgeti(L, index, i);
|
|
defer lua.pop(L, 1);
|
|
|
|
flag := lua.tostring(L, -1);
|
|
switch flag {
|
|
case "Clickable": flags |= {.Clickable}
|
|
case "Hoverable": flags |= {.Hoverable}
|
|
case "Scrollable": flags |= {.Scrollable}
|
|
case "DrawText": flags |= {.DrawText}
|
|
case "DrawBorder": flags |= {.DrawBorder}
|
|
case "DrawBackground": flags |= {.DrawBackground}
|
|
case "RoundedBorder": flags |= {.RoundedBorder}
|
|
case "Floating": flags |= {.Floating}
|
|
case "CustomDrawFunc": flags |= {.CustomDrawFunc}
|
|
}
|
|
}
|
|
|
|
return flags, true
|
|
}
|
|
|
|
main :: proc() {
|
|
state = State {
|
|
ctx = context,
|
|
screen_width = 640,
|
|
screen_height = 480,
|
|
source_font_width = 8,
|
|
source_font_height = 16,
|
|
input_map = core.new_input_map(),
|
|
commands = make(core.EditorCommandList),
|
|
|
|
window = nil,
|
|
directory = os.get_current_directory(),
|
|
plugins = make([dynamic]plugin.Interface),
|
|
highlighters = make(map[string]plugin.OnColorBufferProc),
|
|
hooks = make(map[plugin.Hook][dynamic]plugin.OnHookProc),
|
|
lua_hooks = make(map[plugin.Hook][dynamic]core.LuaHookRef),
|
|
|
|
log_buffer = core.new_virtual_file_buffer(context.allocator),
|
|
};
|
|
|
|
context.logger = core.new_logger(&state.log_buffer);
|
|
state.ctx = context;
|
|
|
|
mem.scratch_allocator_init(&scratch, 1024*1024);
|
|
scratch_alloc = mem.scratch_allocator(&scratch);
|
|
|
|
state.current_input_map = &state.input_map.mode[.Normal];
|
|
register_default_input_actions(&state.input_map.mode[.Normal]);
|
|
register_default_visual_actions(&state.input_map.mode[.Visual]);
|
|
|
|
register_default_text_input_actions(&state.input_map.mode[.Normal]);
|
|
|
|
core.register_editor_command(
|
|
&state.commands,
|
|
"nl.spacegirl.editor.core",
|
|
"New Scratch Buffer",
|
|
"Opens a new scratch buffer",
|
|
proc(state: ^State) {
|
|
buffer := core.new_virtual_file_buffer(context.allocator);
|
|
runtime.append(&state.buffers, buffer);
|
|
}
|
|
)
|
|
core.register_editor_command(
|
|
&state.commands,
|
|
"nl.spacegirl.editor.core",
|
|
"Quit",
|
|
"Quits the application",
|
|
proc(state: ^State) {
|
|
state.should_close = true
|
|
}
|
|
)
|
|
|
|
{
|
|
cmds := core.query_editor_commands_by_group(&state.commands, "nl.spacegirl.editor.core", scratch_alloc);
|
|
log.info("List of commands:");
|
|
for cmd in cmds {
|
|
log.info(cmd.name, ":", cmd.description);
|
|
}
|
|
}
|
|
|
|
if len(os.args) > 1 {
|
|
for arg in os.args[1:] {
|
|
buffer, err := core.new_file_buffer(context.allocator, arg, state.directory);
|
|
if err.type != .None {
|
|
log.error("Failed to create file buffer:", err);
|
|
continue;
|
|
}
|
|
|
|
runtime.append(&state.buffers, buffer);
|
|
}
|
|
} else {
|
|
buffer := core.new_virtual_file_buffer(context.allocator);
|
|
runtime.append(&state.buffers, buffer);
|
|
}
|
|
|
|
if sdl2.Init({.VIDEO}) < 0 {
|
|
log.error("SDL failed to initialize:", sdl2.GetError());
|
|
return;
|
|
}
|
|
|
|
if ttf.Init() < 0 {
|
|
log.error("SDL_TTF failed to initialize:", ttf.GetError());
|
|
return;
|
|
}
|
|
defer ttf.Quit();
|
|
|
|
sdl_window := sdl2.CreateWindow(
|
|
"odin_editor - [now with more ui]",
|
|
sdl2.WINDOWPOS_UNDEFINED,
|
|
sdl2.WINDOWPOS_UNDEFINED,
|
|
640,
|
|
480,
|
|
{.SHOWN, .RESIZABLE, .ALLOW_HIGHDPI}
|
|
);
|
|
defer if sdl_window != nil {
|
|
sdl2.DestroyWindow(sdl_window);
|
|
}
|
|
|
|
if sdl_window == nil {
|
|
log.error("Failed to create window:", sdl2.GetError());
|
|
return;
|
|
}
|
|
|
|
state.sdl_renderer = sdl2.CreateRenderer(sdl_window, -1, {.ACCELERATED, .PRESENTVSYNC});
|
|
defer if state.sdl_renderer != nil {
|
|
sdl2.DestroyRenderer(state.sdl_renderer);
|
|
}
|
|
|
|
if state.sdl_renderer == nil {
|
|
log.error("Failed to create renderer:", sdl2.GetError());
|
|
return;
|
|
}
|
|
state.font_atlas = core.gen_font_atlas(&state, HardcodedFontPath);
|
|
defer {
|
|
if state.font_atlas.font != nil {
|
|
ttf.CloseFont(state.font_atlas.font);
|
|
}
|
|
if state.font_atlas.texture != nil {
|
|
sdl2.DestroyTexture(state.font_atlas.texture);
|
|
}
|
|
}
|
|
|
|
{
|
|
w,h: i32;
|
|
sdl2.GetRendererOutputSize(state.sdl_renderer, &w, &h);
|
|
|
|
state.width_dpi_ratio = f32(w) / f32(state.screen_width);
|
|
state.height_dpi_ratio = f32(h) / f32(state.screen_height);
|
|
state.screen_width = int(w);
|
|
state.screen_height = int(h);
|
|
}
|
|
|
|
sdl2.SetRenderDrawBlendMode(state.sdl_renderer, .BLEND);
|
|
|
|
// Done to clear the buffer
|
|
sdl2.StartTextInput();
|
|
sdl2.StopTextInput();
|
|
|
|
ui_context := ui.init(state.sdl_renderer);
|
|
sdl2.AddEventWatch(expose_event_watcher, &StateWithUi { &state, &ui_context });
|
|
state.plugin_vtable = init_plugin_vtable(&ui_context);
|
|
|
|
// Load plugins
|
|
// TODO(pcleavelin): Get directory of binary instead of shells current working directory
|
|
filepath.walk(filepath.join({ os.get_current_directory(), "bin" }), load_plugin, transmute(rawptr)&state);
|
|
|
|
for plugin in state.plugins {
|
|
if plugin.on_initialize != nil {
|
|
plugin.on_initialize(state.plugin_vtable);
|
|
}
|
|
}
|
|
|
|
// **********************************************************************
|
|
lua_allocator :: proc "c" (ud: rawptr, ptr: rawptr, osize, nsize: c.size_t) -> (buf: rawptr) {
|
|
old_size := int(osize)
|
|
new_size := int(nsize)
|
|
context = (^runtime.Context)(ud)^
|
|
|
|
if ptr == nil {
|
|
data, err := runtime.mem_alloc(new_size)
|
|
return raw_data(data) if err == .None else nil
|
|
} else {
|
|
if nsize > 0 {
|
|
data, err := runtime.mem_resize(ptr, old_size, new_size)
|
|
return raw_data(data) if err == .None else nil
|
|
} else {
|
|
runtime.mem_free(ptr)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
_context := context;
|
|
// L := lua.newstate(lua_allocator, &_context);
|
|
// L := lua.L_newstate();
|
|
state.L = lua.L_newstate();
|
|
lua.L_openlibs(state.L);
|
|
|
|
bbb: [^]lua.L_Reg;
|
|
editor_lib := [?]lua.L_Reg {
|
|
lua.L_Reg {
|
|
"quit",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
|
|
state.should_close = true;
|
|
return i32(lua.OK);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"log",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
text := string(lua.L_checkstring(L, 1));
|
|
log.info("[LUA]:", text);
|
|
|
|
return i32(lua.OK);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"register_hook",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
hook := lua.L_checkinteger(L, 1);
|
|
|
|
lua.L_checktype(L, 2, i32(lua.TFUNCTION));
|
|
lua.pushvalue(L, 2);
|
|
fn_ref := lua.L_ref(L, i32(lua.REGISTRYINDEX));
|
|
|
|
core.add_lua_hook(&state, plugin.Hook(hook), fn_ref);
|
|
|
|
return i32(lua.OK);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"register_key_group",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
lua.L_checktype(L, 1, i32(lua.TTABLE));
|
|
|
|
table_to_action :: proc(L: ^lua.State, index: i32, input_map: ^core.InputActions) {
|
|
lua.len(L, index);
|
|
key_group_len := lua.tointeger(L, -1);
|
|
lua.pop(L, 1);
|
|
|
|
for i in 1..=key_group_len {
|
|
lua.rawgeti(L, index, i);
|
|
defer lua.pop(L, 1);
|
|
|
|
lua.rawgeti(L, -1, 1);
|
|
key:= plugin.Key(lua.tointeger(L, -1));
|
|
lua.pop(L, 1);
|
|
|
|
lua.rawgeti(L, -1, 2);
|
|
desc := strings.clone(string(lua.tostring(L, -1)));
|
|
lua.pop(L, 1);
|
|
|
|
switch lua.rawgeti(L, -1, 3) {
|
|
case i32(lua.TTABLE):
|
|
if action, exists := input_map.key_actions[key]; exists {
|
|
switch value in action.action {
|
|
case core.LuaEditorAction:
|
|
log.warn("Plugin attempted to register input group on existing key action (added from Lua)");
|
|
case core.PluginEditorAction:
|
|
log.warn("Plugin attempted to register input group on existing key action (added from Plugin)");
|
|
case core.EditorAction:
|
|
log.warn("Plugin attempted to register input group on existing key action");
|
|
case core.InputActions:
|
|
input_map := &(&input_map.key_actions[key]).action.(core.InputActions);
|
|
table_to_action(L, lua.gettop(L), input_map);
|
|
}
|
|
} else {
|
|
core.register_key_action(input_map, key, core.new_input_actions(), desc);
|
|
table_to_action(L, lua.gettop(L), &((&input_map.key_actions[key]).action.(core.InputActions)));
|
|
}
|
|
lua.pop(L, 1);
|
|
|
|
case i32(lua.TFUNCTION):
|
|
fn_ref := lua.L_ref(L, i32(lua.REGISTRYINDEX));
|
|
|
|
if lua.rawgeti(L, -1, 4) == i32(lua.TTABLE) {
|
|
maybe_input_map := core.new_input_actions();
|
|
table_to_action(L, lua.gettop(L), &maybe_input_map);
|
|
|
|
core.register_key_action_group(input_map, key, core.LuaEditorAction { fn_ref, maybe_input_map }, desc);
|
|
} else {
|
|
core.register_key_action_group(input_map, key, core.LuaEditorAction { fn_ref, core.InputActions {} }, desc);
|
|
}
|
|
|
|
case:
|
|
lua.pop(L, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
table_to_action(L, 1, state.current_input_map);
|
|
|
|
return i32(lua.OK);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"request_window_close",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
core.request_window_close(&state);
|
|
|
|
return i32(lua.OK);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"get_current_buffer_index",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
lua.pushinteger(L, lua.Integer(state.current_buffer));
|
|
|
|
return 1;
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"set_current_buffer_from_index",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
buffer_index := int(lua.L_checkinteger(L, 1));
|
|
if buffer_index != -2 && (buffer_index < 0 || buffer_index >= len(state.buffers)) {
|
|
return i32(lua.ERRRUN);
|
|
} else {
|
|
state.current_buffer = buffer_index;
|
|
}
|
|
|
|
return i32(lua.OK);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"buffer_info_from_index",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
buffer_index := int(lua.L_checkinteger(L, 1));
|
|
if buffer_index < 0 || buffer_index >= len(state.buffers) {
|
|
lua.pushnil(L);
|
|
} else {
|
|
push_lua_buffer_info :: proc(L: ^lua.State, buffer: ^FileBuffer) {
|
|
lua.newtable(L);
|
|
{
|
|
lua.pushlightuserdata(L, buffer);
|
|
lua.setfield(L, -2, "buffer");
|
|
|
|
lua.newtable(L);
|
|
{
|
|
lua.pushinteger(L, lua.Integer(buffer.cursor.col));
|
|
lua.setfield(L, -2, "col");
|
|
|
|
lua.pushinteger(L, lua.Integer(buffer.cursor.line));
|
|
lua.setfield(L, -2, "line");
|
|
}
|
|
lua.setfield(L, -2, "cursor");
|
|
|
|
lua.pushstring(L, strings.clone_to_cstring(buffer.file_path, context.temp_allocator));
|
|
lua.setfield(L, -2, "full_file_path");
|
|
|
|
relative_file_path, _ := filepath.rel(state.directory, buffer.file_path, context.temp_allocator)
|
|
lua.pushstring(L, strings.clone_to_cstring(relative_file_path, context.temp_allocator));
|
|
lua.setfield(L, -2, "file_path");
|
|
}
|
|
}
|
|
|
|
push_lua_buffer_info(L, core.buffer_from_index(&state, buffer_index));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"query_command_group",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
group := lua.L_checkstring(L, 1);
|
|
cmds := core.query_editor_commands_by_group(&state.commands, string(group), scratch_alloc);
|
|
|
|
lua.newtable(L);
|
|
{
|
|
for cmd, i in cmds {
|
|
lua.newtable(L);
|
|
{
|
|
lua.pushstring(L, strings.clone_to_cstring(cmd.name, scratch_alloc));
|
|
lua.setfield(L, -2, "name");
|
|
|
|
lua.pushstring(L, strings.clone_to_cstring(cmd.description, scratch_alloc));
|
|
lua.setfield(L, -2, "description");
|
|
}
|
|
lua.rawseti(L, -2, lua.Integer(i+1));
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"run_command",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
group := lua.L_checkstring(L, 1);
|
|
name := lua.L_checkstring(L, 2);
|
|
core.run_command(&state, string(group), string(name));
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
};
|
|
bbb = raw_data(editor_lib[:]);
|
|
|
|
get_lua_semantic_size :: proc(L: ^lua.State, index: i32) -> ui.SemanticSize {
|
|
if lua.istable(L, index) {
|
|
lua.rawgeti(L, index, 1);
|
|
semantic_kind := ui.SemanticSizeKind(lua.tointeger(L, -1));
|
|
lua.pop(L, 1);
|
|
|
|
lua.rawgeti(L, index, 2);
|
|
semantic_value := int(lua.tointeger(L, -1));
|
|
lua.pop(L, 1);
|
|
|
|
return {semantic_kind, semantic_value};
|
|
} else {
|
|
semantic_kind := ui.SemanticSizeKind(lua.L_checkinteger(L, index));
|
|
return {semantic_kind, 0};
|
|
}
|
|
}
|
|
|
|
push_lua_semantic_size_table :: proc(L: ^lua.State, size: ui.SemanticSize) {
|
|
lua.newtable(L);
|
|
{
|
|
lua.pushinteger(L, lua.Integer(i32(size.kind)));
|
|
lua.rawseti(L, -2, 1);
|
|
|
|
lua.pushinteger(L, lua.Integer(size.value));
|
|
lua.rawseti(L, -2, 2);
|
|
}
|
|
}
|
|
|
|
push_lua_box_interaction :: proc(L: ^lua.State, interaction: ui.Interaction) {
|
|
lua.newtable(L);
|
|
{
|
|
lua.pushboolean(L, b32(interaction.clicked));
|
|
lua.setfield(L, -2, "clicked");
|
|
|
|
lua.pushboolean(L, b32(interaction.hovering));
|
|
lua.setfield(L, -2, "hovering");
|
|
|
|
lua.pushboolean(L, b32(interaction.dragging));
|
|
lua.setfield(L, -2, "dragging");
|
|
|
|
lua.newtable(L);
|
|
{
|
|
lua.pushinteger(L, lua.Integer(interaction.box_pos.x));
|
|
lua.setfield(L, -2, "x");
|
|
|
|
lua.pushinteger(L, lua.Integer(interaction.box_pos.y));
|
|
lua.setfield(L, -2, "y");
|
|
}
|
|
lua.setfield(L, -2, "box_pos");
|
|
|
|
lua.newtable(L);
|
|
{
|
|
lua.pushinteger(L, lua.Integer(interaction.box_size.x));
|
|
lua.setfield(L, -2, "x");
|
|
|
|
lua.pushinteger(L, lua.Integer(interaction.box_size.y));
|
|
lua.setfield(L, -2, "y");
|
|
}
|
|
lua.setfield(L, -2, "box_size");
|
|
}
|
|
}
|
|
|
|
ui_lib := [?]lua.L_Reg {
|
|
lua.L_Reg {
|
|
"get_mouse_pos",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA));
|
|
lua.pushvalue(L, 1);
|
|
ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1);
|
|
if ui_ctx == nil { return i32(lua.ERRRUN); }
|
|
|
|
lua.pushinteger(L, lua.Integer(ui_ctx.mouse_x));
|
|
lua.pushinteger(L, lua.Integer(ui_ctx.mouse_y));
|
|
|
|
return 2;
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"Exact",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
value := lua.L_checknumber(L, 1);
|
|
push_lua_semantic_size_table(L, { ui.SemanticSizeKind.Exact, int(value) });
|
|
|
|
return 1;
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"PercentOfParent",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
value := lua.L_checknumber(L, 1);
|
|
push_lua_semantic_size_table(L, { ui.SemanticSizeKind.PercentOfParent, int(value) });
|
|
|
|
return 1;
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"push_parent",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA));
|
|
lua.pushvalue(L, 1);
|
|
ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1);
|
|
if ui_ctx == nil { return i32(lua.ERRRUN); }
|
|
|
|
lua.L_checktype(L, 2, i32(lua.TLIGHTUSERDATA));
|
|
lua.pushvalue(L, 2);
|
|
box := transmute(^ui.Box)lua.touserdata(L, -1);
|
|
if box == nil { return i32(lua.ERRRUN); }
|
|
|
|
ui.push_parent(ui_ctx, box);
|
|
return i32(lua.OK);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"pop_parent",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA));
|
|
lua.pushvalue(L, 1);
|
|
ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1);
|
|
if ui_ctx == nil { return i32(lua.ERRRUN); }
|
|
|
|
ui.pop_parent(ui_ctx);
|
|
return i32(lua.OK);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"push_floating",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA));
|
|
lua.pushvalue(L, 1);
|
|
ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1);
|
|
if ui_ctx != nil {
|
|
label := lua.L_checkstring(L, 2);
|
|
x := int(lua.L_checkinteger(L, 3));
|
|
y := int(lua.L_checkinteger(L, 4));
|
|
|
|
box, interaction := ui.push_floating(ui_ctx, strings.clone(string(label), context.temp_allocator), {x,y});
|
|
lua.pushlightuserdata(L, box);
|
|
push_lua_box_interaction(L, interaction);
|
|
return 2;
|
|
}
|
|
|
|
return i32(lua.ERRRUN);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"push_box",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA));
|
|
lua.pushvalue(L, 1);
|
|
ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1);
|
|
if ui_ctx != nil {
|
|
label := lua.L_checkstring(L, 2);
|
|
flags, err := lua_ui_flags(L, 3);
|
|
axis := ui.Axis(lua.L_checkinteger(L, 4));
|
|
|
|
semantic_width := get_lua_semantic_size(L, 5);
|
|
semantic_height := get_lua_semantic_size(L, 6);
|
|
|
|
box, interaction := ui.push_box(ui_ctx, strings.clone(string(label), context.temp_allocator), flags, axis, { semantic_width, semantic_height });
|
|
|
|
lua.pushlightuserdata(L, box);
|
|
push_lua_box_interaction(L, interaction)
|
|
return 2;
|
|
}
|
|
|
|
return i32(lua.ERRRUN);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"_box_interaction",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA));
|
|
lua.pushvalue(L, 1);
|
|
ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1);
|
|
if ui_ctx == nil { return i32(lua.ERRRUN); }
|
|
|
|
lua.L_checktype(L, 2, i32(lua.TLIGHTUSERDATA));
|
|
lua.pushvalue(L, 2);
|
|
box := transmute(^ui.Box)lua.touserdata(L, -1);
|
|
if box == nil { return i32(lua.ERRRUN); }
|
|
|
|
interaction := ui.test_box(ui_ctx, box);
|
|
push_lua_box_interaction(L, interaction)
|
|
return 1;
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"push_box",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA));
|
|
lua.pushvalue(L, 1);
|
|
ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1);
|
|
if ui_ctx != nil {
|
|
label := lua.L_checkstring(L, 2);
|
|
flags, err := lua_ui_flags(L, 3);
|
|
axis := ui.Axis(lua.L_checkinteger(L, 4));
|
|
|
|
semantic_width := get_lua_semantic_size(L, 5);
|
|
semantic_height := get_lua_semantic_size(L, 6);
|
|
|
|
box, interaction := ui.push_box(ui_ctx, strings.clone(string(label), context.temp_allocator), flags, axis, { semantic_width, semantic_height });
|
|
lua.pushlightuserdata(L, box);
|
|
push_lua_box_interaction(L, interaction)
|
|
return 2;
|
|
}
|
|
|
|
return i32(lua.ERRRUN);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"push_rect",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA));
|
|
lua.pushvalue(L, 1);
|
|
ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1);
|
|
if ui_ctx != nil {
|
|
label := lua.L_checkstring(L, 2);
|
|
background := bool(lua.toboolean(L, 3));
|
|
border := bool(lua.toboolean(L, 4));
|
|
axis := ui.Axis(lua.L_checkinteger(L, 5));
|
|
|
|
semantic_width := get_lua_semantic_size(L, 6);
|
|
semantic_height := get_lua_semantic_size(L, 7);
|
|
|
|
box, interaction := ui.push_rect(ui_ctx, strings.clone(string(label), context.temp_allocator), background, border, axis, { semantic_width, semantic_height });
|
|
lua.pushlightuserdata(L, box);
|
|
push_lua_box_interaction(L, interaction)
|
|
return 2;
|
|
}
|
|
|
|
return i32(lua.ERRRUN);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"spacer",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA));
|
|
lua.pushvalue(L, 1);
|
|
ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1);
|
|
if ui_ctx != nil {
|
|
label := lua.L_checkstring(L, 2);
|
|
|
|
interaction := ui.spacer(ui_ctx, strings.clone(string(label), context.temp_allocator), semantic_size = {{.Fill, 0}, {.Fill, 0}});
|
|
|
|
push_lua_box_interaction(L, interaction)
|
|
|
|
return 1;
|
|
}
|
|
|
|
return i32(lua.ERRRUN);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"label",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA));
|
|
lua.pushvalue(L, 1);
|
|
ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1);
|
|
if ui_ctx != nil {
|
|
label := lua.L_checkstring(L, 2);
|
|
|
|
interaction := ui.label(ui_ctx, strings.clone(string(label), context.temp_allocator));
|
|
push_lua_box_interaction(L, interaction)
|
|
|
|
return 1;
|
|
}
|
|
|
|
return i32(lua.ERRRUN);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"button",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA));
|
|
lua.pushvalue(L, 1);
|
|
ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1);
|
|
if ui_ctx != nil {
|
|
label := lua.L_checkstring(L, 2);
|
|
|
|
interaction := ui.button(ui_ctx, strings.clone(string(label), context.temp_allocator));
|
|
push_lua_box_interaction(L, interaction)
|
|
|
|
return 1;
|
|
}
|
|
|
|
return i32(lua.ERRRUN);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"advanced_button",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA));
|
|
lua.pushvalue(L, 1);
|
|
ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1);
|
|
|
|
if ui_ctx != nil {
|
|
label := lua.L_checkstring(L, 2);
|
|
flags, err := lua_ui_flags(L, 3);
|
|
|
|
semantic_width := get_lua_semantic_size(L, 4);
|
|
semantic_height := get_lua_semantic_size(L, 5);
|
|
|
|
interaction := ui.advanced_button(ui_ctx, strings.clone(string(label), context.temp_allocator), flags, { semantic_width, semantic_height });
|
|
push_lua_box_interaction(L, interaction)
|
|
|
|
return 1;
|
|
}
|
|
|
|
return i32(lua.ERRRUN);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"buffer",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA));
|
|
lua.pushvalue(L, 1);
|
|
ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1);
|
|
|
|
if ui_ctx != nil {
|
|
buffer_index := int(lua.L_checkinteger(L, 2));
|
|
|
|
if buffer_index != -2 && (buffer_index < 0 || buffer_index >= len(state.buffers)) {
|
|
return i32(lua.ERRRUN);
|
|
}
|
|
|
|
ui_file_buffer(ui_ctx, core.buffer_from_index(&state, buffer_index));
|
|
|
|
return i32(lua.OK);
|
|
}
|
|
|
|
return i32(lua.ERRRUN);
|
|
}
|
|
},
|
|
lua.L_Reg {
|
|
"log_buffer",
|
|
proc "c" (L: ^lua.State) -> i32 {
|
|
context = state.ctx;
|
|
|
|
lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA));
|
|
lua.pushvalue(L, 1);
|
|
ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1);
|
|
|
|
if ui_ctx != nil {
|
|
ui_file_buffer(ui_ctx, &state.log_buffer);
|
|
|
|
return i32(lua.OK);
|
|
}
|
|
|
|
return i32(lua.ERRRUN);
|
|
}
|
|
}
|
|
};
|
|
|
|
// TODO: generate this from the plugin.Key enum
|
|
lua.newtable(state.L);
|
|
{
|
|
lua.newtable(state.L);
|
|
lua.pushinteger(state.L, lua.Integer(plugin.Key.B));
|
|
lua.setfield(state.L, -2, "B");
|
|
|
|
lua.pushinteger(state.L, lua.Integer(plugin.Key.T));
|
|
lua.setfield(state.L, -2, "T");
|
|
|
|
lua.pushinteger(state.L, lua.Integer(plugin.Key.Y));
|
|
lua.setfield(state.L, -2, "Y");
|
|
|
|
lua.pushinteger(state.L, lua.Integer(plugin.Key.P));
|
|
lua.setfield(state.L, -2, "P");
|
|
|
|
lua.pushinteger(state.L, lua.Integer(plugin.Key.M));
|
|
lua.setfield(state.L, -2, "M");
|
|
|
|
lua.pushinteger(state.L, lua.Integer(plugin.Key.K));
|
|
lua.setfield(state.L, -2, "K");
|
|
|
|
lua.pushinteger(state.L, lua.Integer(plugin.Key.J));
|
|
lua.setfield(state.L, -2, "J");
|
|
|
|
lua.pushinteger(state.L, lua.Integer(plugin.Key.Q));
|
|
lua.setfield(state.L, -2, "Q");
|
|
|
|
lua.pushinteger(state.L, lua.Integer(plugin.Key.BACKQUOTE));
|
|
lua.setfield(state.L, -2, "Backtick");
|
|
|
|
lua.pushinteger(state.L, lua.Integer(plugin.Key.ESCAPE));
|
|
lua.setfield(state.L, -2, "Escape");
|
|
|
|
lua.pushinteger(state.L, lua.Integer(plugin.Key.ENTER));
|
|
lua.setfield(state.L, -2, "Enter");
|
|
|
|
lua.pushinteger(state.L, lua.Integer(plugin.Key.SPACE));
|
|
lua.setfield(state.L, -2, "Space");
|
|
}
|
|
lua.setfield(state.L, -2, "Key");
|
|
|
|
{
|
|
lua.newtable(state.L);
|
|
lua.pushinteger(state.L, lua.Integer(plugin.Hook.BufferInput));
|
|
lua.setfield(state.L, -2, "OnBufferInput");
|
|
lua.pushinteger(state.L, lua.Integer(plugin.Hook.Draw));
|
|
lua.setfield(state.L, -2, "OnDraw");
|
|
}
|
|
lua.setfield(state.L, -2, "Hook");
|
|
|
|
lua.L_setfuncs(state.L, bbb, 0);
|
|
lua.setglobal(state.L, "Editor");
|
|
|
|
lua.newtable(state.L);
|
|
{
|
|
lua.pushinteger(state.L, lua.Integer(ui.Axis.Horizontal));
|
|
lua.setfield(state.L, -2, "Horizontal");
|
|
lua.pushinteger(state.L, lua.Integer(ui.Axis.Vertical));
|
|
lua.setfield(state.L, -2, "Vertical");
|
|
push_lua_semantic_size_table(state.L, { ui.SemanticSizeKind.Fill, 0 });
|
|
lua.setfield(state.L, -2, "Fill");
|
|
push_lua_semantic_size_table(state.L, { ui.SemanticSizeKind.ChildrenSum, 0 });
|
|
lua.setfield(state.L, -2, "ChildrenSum");
|
|
push_lua_semantic_size_table(state.L, { ui.SemanticSizeKind.FitText, 0 });
|
|
lua.setfield(state.L, -2, "FitText");
|
|
|
|
lua.L_setfuncs(state.L, raw_data(&ui_lib), 0);
|
|
lua.setglobal(state.L, "UI");
|
|
}
|
|
|
|
if lua.L_dofile(state.L, "plugins/lua/view.lua") == i32(lua.OK) {
|
|
lua.pop(state.L, lua.gettop(state.L));
|
|
} else {
|
|
err := lua.tostring(state.L, lua.gettop(state.L));
|
|
lua.pop(state.L, lua.gettop(state.L));
|
|
|
|
log.error(err);
|
|
}
|
|
|
|
// Initialize Lua Plugins
|
|
{
|
|
lua.getglobal(state.L, "OnInit");
|
|
if lua.pcall(state.L, 0, 0, 0) == i32(lua.OK) {
|
|
lua.pop(state.L, lua.gettop(state.L));
|
|
} else {
|
|
err := lua.tostring(state.L, lua.gettop(state.L));
|
|
lua.pop(state.L, lua.gettop(state.L));
|
|
|
|
log.error("failed to initialize plugin (OnInit):", err);
|
|
}
|
|
}
|
|
// **********************************************************************
|
|
|
|
control_key_pressed: bool;
|
|
for !state.should_close {
|
|
// if false {
|
|
// buffer := core.current_buffer(&state);
|
|
|
|
// ui.push_parent(&ui_context, ui.push_box(&ui_context, "main", {}, .Vertical, semantic_size = {ui.make_semantic_size(.Fill, 100), ui.make_semantic_size(.Fill, 100)}));
|
|
// defer ui.pop_parent(&ui_context);
|
|
|
|
// {
|
|
// ui.push_parent(&ui_context, ui.push_box(&ui_context, "top_nav", {.DrawBackground}, semantic_size = {ui.make_semantic_size(.PercentOfParent, 100), ui.make_semantic_size(.Exact, state.source_font_height)}));
|
|
// defer ui.pop_parent(&ui_context);
|
|
|
|
// if ui.label(&ui_context, "Editor").clicked {
|
|
// fmt.println("you clicked the button");
|
|
// }
|
|
|
|
// ui.push_box(
|
|
// &ui_context,
|
|
// "nav spacer",
|
|
// {.DrawBackground},
|
|
// semantic_size = {
|
|
// ui.make_semantic_size(.Exact, 16),
|
|
// ui.make_semantic_size(.Exact, state.source_font_height)
|
|
// }
|
|
// );
|
|
|
|
// if ui.label(&ui_context, "Buffers").clicked {
|
|
// fmt.println("you clicked the button");
|
|
// }
|
|
// }
|
|
// {
|
|
// ui.push_parent(&ui_context, ui.push_box(&ui_context, "deezbuffer", {}, .Horizontal, semantic_size = {ui.make_semantic_size(.PercentOfParent, 100), ui.make_semantic_size(.Fill, 0)}));
|
|
// defer ui.pop_parent(&ui_context);
|
|
|
|
// {
|
|
// ui.push_parent(&ui_context, ui.push_box(&ui_context, "left side", {}, .Vertical, semantic_size = {ui.make_semantic_size(.Fill), ui.make_semantic_size(.Fill, 0)}));
|
|
// defer ui.pop_parent(&ui_context);
|
|
|
|
// {
|
|
// if ui_file_buffer(&ui_context, &state.buffers[0]).clicked {
|
|
// state.current_buffer = 0;
|
|
// }
|
|
// }
|
|
// {
|
|
// if ui_file_buffer(&ui_context, &state.buffers[0+1]).clicked {
|
|
// state.current_buffer = 1;
|
|
// }
|
|
// }
|
|
// {
|
|
// if ui_file_buffer(&ui_context, &state.buffers[0+2]).clicked {
|
|
// state.current_buffer = 2;
|
|
// }
|
|
// }
|
|
// }
|
|
// {
|
|
// ui.push_parent(&ui_context, ui.push_box(&ui_context, "right side", {}, .Vertical, semantic_size = {ui.make_semantic_size(.Fill), ui.make_semantic_size(.Fill, 0)}));
|
|
// defer ui.pop_parent(&ui_context);
|
|
|
|
// {
|
|
// if ui_file_buffer(&ui_context, core.current_buffer(&state)).clicked {
|
|
// state.current_buffer = 3;
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// {
|
|
// ui.push_parent(&ui_context, ui.push_box(&ui_context, "bottom stats", {.DrawBackground}, semantic_size = {ui.make_semantic_size(.PercentOfParent, 100), ui.make_semantic_size(.Exact, state.source_font_height)}));
|
|
// defer ui.pop_parent(&ui_context);
|
|
|
|
// label := "";
|
|
// if state.mode == .Insert {
|
|
// label = "INSERT";
|
|
// } else if state.mode == .Normal {
|
|
// label = "NORMAL";
|
|
// }
|
|
|
|
// if ui.label(&ui_context, label).clicked {
|
|
// fmt.println("you clicked the button");
|
|
// }
|
|
// ui.spacer(&ui_context, "mode spacer", semantic_size = {ui.make_semantic_size(.Exact, 16), ui.make_semantic_size(.Fill)});
|
|
|
|
// relative_file_path, _ := filepath.rel(state.directory, buffer.file_path, context.temp_allocator)
|
|
// ui.label(&ui_context, relative_file_path);
|
|
|
|
// ui.spacer(&ui_context, "stats inbetween");
|
|
|
|
// {
|
|
// ui.push_parent(&ui_context, ui.push_box(&ui_context, "center info", {}, semantic_size = ui.ChildrenSum));
|
|
// defer ui.pop_parent(&ui_context);
|
|
|
|
// line_info_text := fmt.tprintf(
|
|
// //"Line: %d, Col: %d, Len: %d --- Slice Index: %d, Content Index: %d",
|
|
// "Line: %d, Col: %d",
|
|
// buffer.cursor.line + 1,
|
|
// buffer.cursor.col + 1,
|
|
// //core.file_buffer_line_length(buffer, buffer.cursor.index),
|
|
// // buffer.cursor.index.slice_index,
|
|
// // buffer.cursor.index.content_index,
|
|
// );
|
|
// ui.label(&ui_context, line_info_text);
|
|
|
|
// mouse_pos_str := fmt.tprintf("x,y: [%d,%d]", ui_context.mouse_x, ui_context.mouse_y);
|
|
// ui.label(&ui_context, mouse_pos_str);
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// TODO: move this to view.lua
|
|
// log_window, _ := ui.push_floating(&ui_context, "log", {0,0}, flags = {.Floating, .DrawBackground}, semantic_size = {ui.make_semantic_size(.PercentOfParent, 75), ui.make_semantic_size(.PercentOfParent, 75)});
|
|
// ui.push_parent(&ui_context, log_window);
|
|
// {
|
|
// defer ui.pop_parent(&ui_context);
|
|
// ui_file_buffer(&ui_context, &state.log_buffer);
|
|
// }
|
|
|
|
|
|
if draw_hooks, ok := state.lua_hooks[plugin.Hook.Draw]; ok {
|
|
for hook_ref in draw_hooks {
|
|
lua.rawgeti(state.L, lua.REGISTRYINDEX, lua.Integer(hook_ref));
|
|
lua.pushlightuserdata(state.L, &ui_context);
|
|
if lua.pcall(state.L, 1, 0, 0) != i32(lua.OK) {
|
|
err := lua.tostring(state.L, lua.gettop(state.L));
|
|
lua.pop(state.L, lua.gettop(state.L));
|
|
|
|
log.error(err);
|
|
} else {
|
|
lua.pop(state.L, lua.gettop(state.L));
|
|
}
|
|
}
|
|
}
|
|
|
|
if state.window != nil && state.window.draw != nil {
|
|
state.window.draw(state.plugin_vtable, state.window.user_data);
|
|
}
|
|
|
|
{
|
|
ui_context.last_mouse_left_down = ui_context.mouse_left_down;
|
|
ui_context.last_mouse_right_down = ui_context.mouse_right_down;
|
|
|
|
ui_context.last_mouse_x = ui_context.mouse_x;
|
|
ui_context.last_mouse_y = ui_context.mouse_y;
|
|
|
|
sdl_event: sdl2.Event;
|
|
for(sdl2.PollEvent(&sdl_event)) {
|
|
if sdl_event.type == .QUIT {
|
|
state.should_close = true;
|
|
}
|
|
|
|
if sdl_event.type == .MOUSEMOTION {
|
|
ui_context.mouse_x = int(f32(sdl_event.motion.x) * state.width_dpi_ratio);
|
|
ui_context.mouse_y = int(f32(sdl_event.motion.y) * state.height_dpi_ratio);
|
|
}
|
|
|
|
if sdl_event.type == .MOUSEBUTTONDOWN || sdl_event.type == .MOUSEBUTTONUP {
|
|
event := sdl_event.button;
|
|
|
|
if event.button == sdl2.BUTTON_LEFT {
|
|
ui_context.mouse_left_down = sdl_event.type == .MOUSEBUTTONDOWN;
|
|
}
|
|
if event.button == sdl2.BUTTON_RIGHT {
|
|
ui_context.mouse_left_down = sdl_event.type == .MOUSEBUTTONDOWN;
|
|
}
|
|
}
|
|
|
|
switch state.mode {
|
|
case .Visual: fallthrough
|
|
case .Normal: {
|
|
if sdl_event.type == .KEYDOWN {
|
|
key := plugin.Key(sdl_event.key.keysym.sym);
|
|
if key == .ESCAPE {
|
|
core.request_window_close(&state);
|
|
}
|
|
|
|
if key == .LCTRL {
|
|
control_key_pressed = true;
|
|
} else if state.current_input_map != nil {
|
|
if control_key_pressed {
|
|
if action, exists := state.current_input_map.ctrl_key_actions[key]; exists {
|
|
switch value in action.action {
|
|
case core.LuaEditorAction:
|
|
lua.rawgeti(state.L, lua.REGISTRYINDEX, lua.Integer(value.fn_ref));
|
|
if lua.pcall(state.L, 0, 0, 0) != i32(lua.OK) {
|
|
err := lua.tostring(state.L, lua.gettop(state.L));
|
|
lua.pop(state.L, lua.gettop(state.L));
|
|
|
|
log.error(err);
|
|
} else {
|
|
lua.pop(state.L, lua.gettop(state.L));
|
|
}
|
|
|
|
if value.maybe_input_map.ctrl_key_actions != nil {
|
|
|
|
}
|
|
case core.PluginEditorAction:
|
|
value(state.plugin_vtable);
|
|
case core.EditorAction:
|
|
value(&state);
|
|
case core.InputActions:
|
|
state.current_input_map = &(&state.current_input_map.ctrl_key_actions[key]).action.(core.InputActions)
|
|
}
|
|
}
|
|
} else {
|
|
if action, exists := state.current_input_map.key_actions[key]; exists {
|
|
switch value in action.action {
|
|
case core.LuaEditorAction:
|
|
lua.rawgeti(state.L, lua.REGISTRYINDEX, lua.Integer(value.fn_ref));
|
|
if lua.pcall(state.L, 0, 0, 0) != i32(lua.OK) {
|
|
err := lua.tostring(state.L, lua.gettop(state.L));
|
|
lua.pop(state.L, lua.gettop(state.L));
|
|
|
|
log.error(err);
|
|
} else {
|
|
lua.pop(state.L, lua.gettop(state.L));
|
|
}
|
|
|
|
if value.maybe_input_map.key_actions != nil {
|
|
ptr_action := &(&state.current_input_map.key_actions[key]).action.(core.LuaEditorAction)
|
|
state.current_input_map = (&ptr_action.maybe_input_map)
|
|
}
|
|
case core.PluginEditorAction:
|
|
value(state.plugin_vtable);
|
|
case core.EditorAction:
|
|
value(&state);
|
|
case core.InputActions:
|
|
state.current_input_map = &(&state.current_input_map.key_actions[key]).action.(core.InputActions)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if sdl_event.type == .KEYUP {
|
|
key := plugin.Key(sdl_event.key.keysym.sym);
|
|
if key == .LCTRL {
|
|
control_key_pressed = false;
|
|
}
|
|
}
|
|
}
|
|
case .Insert: {
|
|
buffer: ^FileBuffer;
|
|
|
|
if state.window != nil && state.window.get_buffer != nil {
|
|
buffer = transmute(^core.FileBuffer)(state.window.get_buffer(state.plugin_vtable, state.window.user_data));
|
|
} else {
|
|
buffer = core.current_buffer(&state);
|
|
}
|
|
|
|
if sdl_event.type == .KEYDOWN {
|
|
key := plugin.Key(sdl_event.key.keysym.sym);
|
|
|
|
#partial switch key {
|
|
case .ESCAPE: {
|
|
state.mode = .Normal;
|
|
|
|
core.insert_content(buffer, buffer.input_buffer[:]);
|
|
runtime.clear(&buffer.input_buffer);
|
|
|
|
sdl2.StopTextInput();
|
|
}
|
|
case .BACKSPACE: {
|
|
core.delete_content(buffer, 1);
|
|
|
|
for hook_proc in state.hooks[plugin.Hook.BufferInput] {
|
|
hook_proc(state.plugin_vtable, buffer);
|
|
}
|
|
for hook_ref in state.lua_hooks[plugin.Hook.BufferInput] {
|
|
lua.rawgeti(state.L, lua.REGISTRYINDEX, lua.Integer(hook_ref));
|
|
if lua.pcall(state.L, 0, 0, 0) != i32(lua.OK) {
|
|
err := lua.tostring(state.L, lua.gettop(state.L));
|
|
lua.pop(state.L, lua.gettop(state.L));
|
|
|
|
log.error(err);
|
|
} else {
|
|
lua.pop(state.L, lua.gettop(state.L));
|
|
}
|
|
}
|
|
}
|
|
case .ENTER: {
|
|
append(&buffer.input_buffer, '\n');
|
|
}
|
|
}
|
|
}
|
|
|
|
if sdl_event.type == .TEXTINPUT {
|
|
for char in sdl_event.text.text {
|
|
if char < 1 {
|
|
break;
|
|
}
|
|
|
|
if char >= 32 && char <= 125 && len(buffer.input_buffer) < 1024-1 {
|
|
append(&buffer.input_buffer, u8(char));
|
|
|
|
for hook_proc in state.hooks[plugin.Hook.BufferInput] {
|
|
hook_proc(state.plugin_vtable, buffer);
|
|
}
|
|
for hook_ref in state.lua_hooks[plugin.Hook.BufferInput] {
|
|
lua.rawgeti(state.L, lua.REGISTRYINDEX, lua.Integer(hook_ref));
|
|
if lua.pcall(state.L, 0, 0, 0) != i32(lua.OK) {
|
|
err := lua.tostring(state.L, lua.gettop(state.L));
|
|
lua.pop(state.L, lua.gettop(state.L));
|
|
|
|
log.error(err);
|
|
} else {
|
|
lua.pop(state.L, lua.gettop(state.L));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
draw(&StateWithUi { &state, &ui_context });
|
|
|
|
ui.prune(&ui_context);
|
|
|
|
switch state.mode {
|
|
case .Normal:
|
|
if state.window != nil && state.window.get_buffer != nil {
|
|
buffer := transmute(^core.FileBuffer)(state.window.get_buffer(state.plugin_vtable, state.window.user_data));
|
|
do_normal_mode(&state, buffer);
|
|
} else {
|
|
buffer := core.current_buffer(&state);
|
|
do_normal_mode(&state, buffer);
|
|
}
|
|
case .Insert:
|
|
if state.window != nil && state.window.get_buffer != nil {
|
|
buffer := transmute(^core.FileBuffer)(state.window.get_buffer(state.plugin_vtable, state.window.user_data));
|
|
do_insert_mode(&state, buffer);
|
|
} else {
|
|
buffer := core.current_buffer(&state);
|
|
do_insert_mode(&state, buffer);
|
|
}
|
|
case .Visual:
|
|
if state.window != nil && state.window.get_buffer != nil {
|
|
// TODO
|
|
} else {
|
|
buffer := core.current_buffer(&state);
|
|
do_visual_mode(&state, buffer);
|
|
}
|
|
}
|
|
|
|
if state.should_close_window {
|
|
state.should_close_window = false;
|
|
core.close_window_and_free(&state);
|
|
}
|
|
|
|
runtime.free_all(context.temp_allocator);
|
|
}
|
|
|
|
for plugin in state.plugins {
|
|
if plugin.on_exit != nil {
|
|
plugin.on_exit();
|
|
}
|
|
}
|
|
lua.close(state.L);
|
|
|
|
sdl2.Quit();
|
|
}
|