editor commands + text deletion
parent
f0eae25439
commit
402205a2e3
|
@ -25,8 +25,8 @@ local MovingTabInBetween = false
|
||||||
local LastMouseX = 0
|
local LastMouseX = 0
|
||||||
local LastMouseY = 0
|
local LastMouseY = 0
|
||||||
|
|
||||||
function buffer_list_iter()
|
function buffer_list_iter(start)
|
||||||
local idx = 0
|
local idx = start
|
||||||
return function ()
|
return function ()
|
||||||
buffer_info = Editor.buffer_info_from_index(idx)
|
buffer_info = Editor.buffer_info_from_index(idx)
|
||||||
idx = idx + 1
|
idx = idx + 1
|
||||||
|
@ -49,6 +49,30 @@ function centered(ctx, label, axis, width, height, body)
|
||||||
UI.pop_parent(ctx)
|
UI.pop_parent(ctx)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function list_iter(start, list)
|
||||||
|
local idx = start
|
||||||
|
|
||||||
|
return function()
|
||||||
|
local value = list[idx]
|
||||||
|
idx = idx + 1
|
||||||
|
return value, idx-1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function list(ctx, label, selection_index, list, render_func)
|
||||||
|
list_with_iter(ctx, label, selection_index, list_iter(selection_index, list), render_func)
|
||||||
|
end
|
||||||
|
|
||||||
|
function list_with_iter(ctx, label, selection_index, list_iter, render_func)
|
||||||
|
local num_items = 10
|
||||||
|
|
||||||
|
UI.push_parent(ctx, UI.push_rect(ctx, label, true, true, UI.Vertical, UI.Fill, UI.Fill))
|
||||||
|
for data, i in list_iter do
|
||||||
|
render_func(ctx, data, i == selection_index)
|
||||||
|
end
|
||||||
|
UI.pop_parent(ctx)
|
||||||
|
end
|
||||||
|
|
||||||
function lerp(from, to, rate)
|
function lerp(from, to, rate)
|
||||||
return (1 - rate) * from + rate*to
|
return (1 - rate) * from + rate*to
|
||||||
end
|
end
|
||||||
|
@ -97,7 +121,7 @@ function ui_sidemenu(ctx)
|
||||||
UI.pop_parent(ctx)
|
UI.pop_parent(ctx)
|
||||||
UI.push_rect(ctx, "padded bottom open files", false, false, UI.Horizontal, UI.Fill, UI.Exact(8))
|
UI.push_rect(ctx, "padded bottom open files", false, false, UI.Horizontal, UI.Fill, UI.Exact(8))
|
||||||
|
|
||||||
for buffer_info, i in buffer_list_iter() do
|
for buffer_info, i in buffer_list_iter(0) do
|
||||||
button_container = UI.push_rect(ctx, "button container"..i, false, false, UI.Horizontal, UI.Fill, UI.ChildrenSum)
|
button_container = UI.push_rect(ctx, "button container"..i, false, false, UI.Horizontal, UI.Fill, UI.ChildrenSum)
|
||||||
UI.push_parent(ctx, button_container)
|
UI.push_parent(ctx, button_container)
|
||||||
flags = {"Clickable", "Hoverable", "DrawText"}
|
flags = {"Clickable", "Hoverable", "DrawText"}
|
||||||
|
@ -309,19 +333,32 @@ function render_buffer_search(ctx)
|
||||||
UI.push_parent(ctx, UI.push_floating(ctx, "buffer search canvas", 0, 0))
|
UI.push_parent(ctx, UI.push_floating(ctx, "buffer search canvas", 0, 0))
|
||||||
centered(ctx, "buffer search window", UI.Horizontal, UI.PercentOfParent(window_percent), UI.PercentOfParent(window_percent), (
|
centered(ctx, "buffer search window", UI.Horizontal, UI.PercentOfParent(window_percent), UI.PercentOfParent(window_percent), (
|
||||||
function ()
|
function ()
|
||||||
UI.push_parent(ctx, UI.push_rect(ctx, "window", true, true, UI.Horizontal, UI.Fill, UI.Fill))
|
list_with_iter(ctx, "buffer list", BufferSearchIndex, buffer_list_iter(BufferSearchIndex),
|
||||||
UI.push_parent(ctx, UI.push_rect(ctx, "buffer list", false, false, UI.Vertical, UI.Fill, UI.Fill))
|
function(ctx, buffer_info, is_selected)
|
||||||
for buffer_info, i in buffer_list_iter() do
|
flags = {"DrawText"}
|
||||||
flags = {"DrawText"}
|
|
||||||
|
|
||||||
if i == BufferSearchIndex then
|
if is_selected then
|
||||||
table.insert(flags, 1, "DrawBorder")
|
table.insert(flags, 1, "DrawBorder")
|
||||||
end
|
|
||||||
interaction = UI.advanced_button(ctx, " "..buffer_info.file_path.." ", flags, UI.Fill, UI.FitText)
|
|
||||||
end
|
end
|
||||||
UI.pop_parent(ctx)
|
|
||||||
UI.buffer(ctx, BufferSearchIndex)
|
interaction = UI.advanced_button(ctx, " "..buffer_info.file_path.." ", flags, UI.Fill, UI.FitText)
|
||||||
UI.pop_parent(ctx)
|
end
|
||||||
|
)
|
||||||
|
UI.buffer(ctx, BufferSearchIndex)
|
||||||
|
|
||||||
|
-- UI.push_parent(ctx, UI.push_rect(ctx, "window", true, true, UI.Horizontal, UI.Fill, UI.Fill))
|
||||||
|
-- UI.push_parent(ctx, UI.push_rect(ctx, "buffer list", false, false, UI.Vertical, UI.Fill, UI.Fill))
|
||||||
|
-- for buffer_info, i in buffer_list_iter() do
|
||||||
|
-- flags = {"DrawText"}
|
||||||
|
--
|
||||||
|
-- if i == BufferSearchIndex then
|
||||||
|
-- table.insert(flags, 1, "DrawBorder")
|
||||||
|
-- end
|
||||||
|
-- interaction = UI.advanced_button(ctx, " "..buffer_info.file_path.." ", flags, UI.Fill, UI.FitText)
|
||||||
|
-- end
|
||||||
|
-- UI.pop_parent(ctx)
|
||||||
|
-- UI.buffer(ctx, BufferSearchIndex)
|
||||||
|
-- UI.pop_parent(ctx)
|
||||||
end
|
end
|
||||||
))
|
))
|
||||||
UI.pop_parent(ctx)
|
UI.pop_parent(ctx)
|
||||||
|
@ -346,23 +383,21 @@ function render_command_search(ctx)
|
||||||
end
|
end
|
||||||
|
|
||||||
UI.push_parent(ctx, UI.push_floating(ctx, "buffer search canvas", 0, 0))
|
UI.push_parent(ctx, UI.push_floating(ctx, "buffer search canvas", 0, 0))
|
||||||
centered(ctx, "command search window", UI.Horizontal, UI.PercentOfParent(window_percent_width), UI.PercentOfParent(window_percent_height), (
|
centered(ctx, "command search window", UI.Horizontal, UI.PercentOfParent(window_percent_width), UI.PercentOfParent(window_percent_height),
|
||||||
function ()
|
function ()
|
||||||
UI.push_parent(ctx, UI.push_rect(ctx, "window", true, true, UI.Horizontal, UI.Fill, UI.Fill))
|
list(ctx, "command list", CommandSearchIndex, CommandList,
|
||||||
UI.push_parent(ctx, UI.push_rect(ctx, "command list", false, false, UI.Vertical, UI.Fill, UI.Fill))
|
function(ctx, cmd, is_selected)
|
||||||
-- local commands = Editor.query_command_group("nl.spacegirl.editor.core")
|
flags = {"DrawText"}
|
||||||
for i, cmd in ipairs(CommandList) do
|
|
||||||
flags = {"DrawText"}
|
|
||||||
|
|
||||||
if i == CommandSearchIndex then
|
if is_selected then
|
||||||
table.insert(flags, 1, "DrawBorder")
|
table.insert(flags, 1, "DrawBorder")
|
||||||
end
|
|
||||||
interaction = UI.advanced_button(ctx, " "..cmd.name..": "..cmd.description.." ", flags, UI.Fill, UI.FitText)
|
|
||||||
end
|
end
|
||||||
UI.pop_parent(ctx)
|
|
||||||
UI.pop_parent(ctx)
|
interaction = UI.advanced_button(ctx, " "..cmd.name..": "..cmd.description.." ", flags, UI.Fill, UI.FitText)
|
||||||
|
end
|
||||||
|
)
|
||||||
end
|
end
|
||||||
))
|
)
|
||||||
UI.pop_parent(ctx)
|
UI.pop_parent(ctx)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -416,7 +451,7 @@ function OnInit()
|
||||||
end
|
end
|
||||||
end)},
|
end)},
|
||||||
{Editor.Key.Space, "", {
|
{Editor.Key.Space, "", {
|
||||||
{Editor.Key.Backtick, "Command Palette",
|
{Editor.Key.P, "Command Palette",
|
||||||
(function ()
|
(function ()
|
||||||
CommandSearchOpen = true
|
CommandSearchOpen = true
|
||||||
CommandSearchIndex = 1
|
CommandSearchIndex = 1
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import "base:runtime"
|
import "base:runtime"
|
||||||
|
import "base:intrinsics"
|
||||||
import "core:reflect"
|
import "core:reflect"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:log"
|
import "core:log"
|
||||||
|
@ -78,6 +79,8 @@ State :: struct {
|
||||||
current_input_map: ^InputActions,
|
current_input_map: ^InputActions,
|
||||||
|
|
||||||
commands: EditorCommandList,
|
commands: EditorCommandList,
|
||||||
|
command_arena: runtime.Allocator,
|
||||||
|
command_args: [dynamic]EditorCommandArgument,
|
||||||
|
|
||||||
plugins: [dynamic]plugin.Interface,
|
plugins: [dynamic]plugin.Interface,
|
||||||
plugin_vtable: plugin.Plugin,
|
plugin_vtable: plugin.Plugin,
|
||||||
|
@ -92,6 +95,16 @@ EditorCommand :: struct {
|
||||||
action: EditorAction,
|
action: EditorAction,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EditorCommandExec :: struct {
|
||||||
|
num_args: int,
|
||||||
|
args: [dynamic]EditorCommandArgument,
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorCommandArgument :: union {
|
||||||
|
string,
|
||||||
|
i32
|
||||||
|
}
|
||||||
|
|
||||||
current_buffer :: proc(state: ^State) -> ^FileBuffer {
|
current_buffer :: proc(state: ^State) -> ^FileBuffer {
|
||||||
if state.current_buffer == -2 {
|
if state.current_buffer == -2 {
|
||||||
return &state.log_buffer;
|
return &state.log_buffer;
|
||||||
|
@ -123,6 +136,7 @@ add_hook :: proc(state: ^State, hook: plugin.Hook, hook_proc: plugin.OnHookProc)
|
||||||
add_lua_hook :: proc(state: ^State, hook: plugin.Hook, hook_ref: LuaHookRef) {
|
add_lua_hook :: proc(state: ^State, hook: plugin.Hook, hook_ref: LuaHookRef) {
|
||||||
if _, exists := state.lua_hooks[hook]; !exists {
|
if _, exists := state.lua_hooks[hook]; !exists {
|
||||||
state.lua_hooks[hook] = make([dynamic]LuaHookRef);
|
state.lua_hooks[hook] = make([dynamic]LuaHookRef);
|
||||||
|
log.info("added lua hook", hook)
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime.append(&state.lua_hooks[hook], hook_ref);
|
runtime.append(&state.lua_hooks[hook], hook_ref);
|
||||||
|
@ -289,7 +303,26 @@ query_editor_commands_by_group :: proc(command_list: ^EditorCommandList, name: s
|
||||||
return commands[:];
|
return commands[:];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
push_command_arg :: proc(state: ^State, command_arg: EditorCommandArgument) {
|
||||||
|
context.allocator = state.command_arena;
|
||||||
|
|
||||||
|
if state.command_args == nil {
|
||||||
|
state.command_args = make([dynamic]EditorCommandArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
append(&state.command_args, command_arg)
|
||||||
|
}
|
||||||
|
|
||||||
run_command :: proc(state: ^State, group: string, name: string) {
|
run_command :: proc(state: ^State, group: string, name: string) {
|
||||||
|
if state.command_args == nil {
|
||||||
|
state.command_args = make([dynamic]EditorCommandArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
defer {
|
||||||
|
state.command_args = nil
|
||||||
|
runtime.free_all(state.command_arena)
|
||||||
|
}
|
||||||
|
|
||||||
if cmds, ok := state.commands[group]; ok {
|
if cmds, ok := state.commands[group]; ok {
|
||||||
for cmd in cmds {
|
for cmd in cmds {
|
||||||
if cmd.name == name {
|
if cmd.name == name {
|
||||||
|
@ -302,3 +335,55 @@ run_command :: proc(state: ^State, group: string, name: string) {
|
||||||
|
|
||||||
log.error("no command", group, name);
|
log.error("no command", group, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attempt_read_command_args :: proc($T: typeid, args: []EditorCommandArgument) -> (value: T, ok: bool)
|
||||||
|
where intrinsics.type_is_struct(T) {
|
||||||
|
ti := runtime.type_info_base(type_info_of(T));
|
||||||
|
|
||||||
|
#partial switch v in ti.variant {
|
||||||
|
case runtime.Type_Info_Struct:
|
||||||
|
{
|
||||||
|
if int(v.field_count) != len(args) {
|
||||||
|
ok = false
|
||||||
|
log.error("invalid number of arguments", len(args), ", expected", v.field_count);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg, i in args {
|
||||||
|
switch varg in arg {
|
||||||
|
case string:
|
||||||
|
{
|
||||||
|
if _, is_string := v.types[i].variant.(runtime.Type_Info_String); !is_string {
|
||||||
|
ok = false
|
||||||
|
log.error("invalid argument #", i, "given to command, found string, expected", v.types[i].variant)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
value_string: ^string = transmute(^string)(uintptr(&value) + v.offsets[i])
|
||||||
|
value_string^ = varg
|
||||||
|
}
|
||||||
|
case i32:
|
||||||
|
{
|
||||||
|
|
||||||
|
if _, is_integer := v.types[i].variant.(runtime.Type_Info_Integer); !is_integer {
|
||||||
|
ok = false
|
||||||
|
log.error("invalid argument #", i, "given to command, unexpected integer, expected", v.types[i].variant)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
value_i32: ^i32 = transmute(^i32)(uintptr(&value) + v.offsets[i])
|
||||||
|
value_i32^ = varg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case:
|
||||||
|
{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package core;
|
package core;
|
||||||
|
|
||||||
import "core:os"
|
import "core:os"
|
||||||
|
import "core:log"
|
||||||
import "core:path/filepath"
|
import "core:path/filepath"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
|
@ -644,6 +645,20 @@ new_selection_span :: proc(start: Cursor, end: Cursor) -> Selection {
|
||||||
|
|
||||||
new_selection :: proc{new_selection_zero_length, new_selection_span};
|
new_selection :: proc{new_selection_zero_length, new_selection_span};
|
||||||
|
|
||||||
|
swap_selections :: proc(selection: Selection) -> (swapped: Selection) {
|
||||||
|
swapped = selection
|
||||||
|
|
||||||
|
if selection.start.index.slice_index > selection.end.index.slice_index ||
|
||||||
|
(selection.start.index.slice_index == selection.end.index.slice_index
|
||||||
|
&& selection.start.index.content_index > selection.end.index.content_index)
|
||||||
|
{
|
||||||
|
swapped.start = selection.end
|
||||||
|
swapped.end = selection.start
|
||||||
|
}
|
||||||
|
|
||||||
|
return swapped
|
||||||
|
}
|
||||||
|
|
||||||
new_virtual_file_buffer :: proc(allocator: mem.Allocator) -> FileBuffer {
|
new_virtual_file_buffer :: proc(allocator: mem.Allocator) -> FileBuffer {
|
||||||
context.allocator = allocator;
|
context.allocator = allocator;
|
||||||
width := 256;
|
width := 256;
|
||||||
|
@ -937,16 +952,22 @@ draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, sho
|
||||||
// NOTE: this requires transparent background color because it renders after the text
|
// NOTE: this requires transparent background color because it renders after the text
|
||||||
// and its after the text because the line length needs to be calculated
|
// and its after the text because the line length needs to be calculated
|
||||||
if state.mode == .Visual && current_buffer(state) == buffer {
|
if state.mode == .Visual && current_buffer(state) == buffer {
|
||||||
|
selection := swap_selections(buffer.selection.?)
|
||||||
|
// selection := buffer.selection.?
|
||||||
|
|
||||||
sel_x := x + padding;
|
sel_x := x + padding;
|
||||||
width: int
|
width: int
|
||||||
|
|
||||||
if begin+j >= buffer.selection.?.start.line && begin+j <= buffer.selection.?.end.line {
|
if begin+j >= selection.start.line && begin+j <= selection.end.line {
|
||||||
if begin+j == buffer.selection.?.end.line {
|
if begin+j == selection.start.line && selection.start.line == selection.end.line {
|
||||||
width = buffer.selection.?.end.col * state.source_font_width;
|
width = (selection.end.col - selection.start.col) * state.source_font_width;
|
||||||
|
sel_x += selection.start.col * state.source_font_width;
|
||||||
|
} else if begin+j == selection.end.line {
|
||||||
|
width = selection.end.col * state.source_font_width;
|
||||||
} else {
|
} else {
|
||||||
if begin+j == buffer.selection.?.start.line {
|
if begin+j == selection.start.line {
|
||||||
width = (line_length - buffer.selection.?.start.col) * state.source_font_width;
|
width = (line_length - selection.start.col) * state.source_font_width;
|
||||||
sel_x += buffer.selection.?.start.col * state.source_font_width;
|
sel_x += selection.start.col * state.source_font_width;
|
||||||
} else {
|
} else {
|
||||||
width = line_length * state.source_font_width;
|
width = line_length * state.source_font_width;
|
||||||
}
|
}
|
||||||
|
@ -1027,22 +1048,44 @@ insert_content :: proc(buffer: ^FileBuffer, to_be_inserted: []u8, append_to_end:
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: potentially add FileBufferIndex as parameter
|
// TODO: potentially add FileBufferIndex as parameter
|
||||||
split_content_slice :: proc(buffer: ^FileBuffer) {
|
split_content_slice_from_cursor :: proc(buffer: ^FileBuffer, cursor: ^Cursor) -> (did_split: bool) {
|
||||||
if buffer.cursor.index.content_index == 0 {
|
if cursor.index.content_index == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
end_slice := buffer.content_slices[buffer.cursor.index.slice_index][buffer.cursor.index.content_index:];
|
end_slice := buffer.content_slices[cursor.index.slice_index][cursor.index.content_index:];
|
||||||
buffer.content_slices[buffer.cursor.index.slice_index] = buffer.content_slices[buffer.cursor.index.slice_index][:buffer.cursor.index.content_index];
|
buffer.content_slices[cursor.index.slice_index] = buffer.content_slices[cursor.index.slice_index][:cursor.index.content_index];
|
||||||
|
|
||||||
inject_at(&buffer.content_slices, buffer.cursor.index.slice_index+1, end_slice);
|
inject_at(&buffer.content_slices, cursor.index.slice_index+1, end_slice);
|
||||||
|
|
||||||
// TODO: maybe move this out of this function
|
// TODO: maybe move this out of this function
|
||||||
buffer.cursor.index.slice_index += 1;
|
cursor.index.slice_index += 1;
|
||||||
buffer.cursor.index.content_index = 0;
|
cursor.index.content_index = 0;
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_content :: proc(buffer: ^FileBuffer, amount: int) {
|
split_content_slice_from_selection :: proc(buffer: ^FileBuffer, selection: ^Selection) {
|
||||||
|
// TODO: swap selections
|
||||||
|
|
||||||
|
log.info("start:", selection.start, "- end:", selection.end);
|
||||||
|
|
||||||
|
// move the end cursor forward one (we want the splitting to be exclusive, not inclusive)
|
||||||
|
it := new_file_buffer_iter_with_cursor(buffer, selection.end);
|
||||||
|
iterate_file_buffer(&it);
|
||||||
|
selection.end = it.cursor;
|
||||||
|
|
||||||
|
split_content_slice_from_cursor(buffer, &selection.end);
|
||||||
|
if split_content_slice_from_cursor(buffer, &selection.start) {
|
||||||
|
selection.end.index.slice_index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("start:", selection.start, "- end:", selection.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
split_content_slice :: proc{split_content_slice_from_cursor, split_content_slice_from_selection};
|
||||||
|
|
||||||
|
delete_content_from_buffer_cursor :: proc(buffer: ^FileBuffer, amount: int) {
|
||||||
if amount <= len(buffer.input_buffer) {
|
if amount <= len(buffer.input_buffer) {
|
||||||
runtime.resize(&buffer.input_buffer, len(buffer.input_buffer)-amount);
|
runtime.resize(&buffer.input_buffer, len(buffer.input_buffer)-amount);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1053,7 +1096,7 @@ delete_content :: proc(buffer: ^FileBuffer, amount: int) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
split_content_slice(buffer);
|
split_content_slice(buffer, &buffer.cursor);
|
||||||
|
|
||||||
it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor);
|
it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor);
|
||||||
|
|
||||||
|
@ -1086,3 +1129,27 @@ delete_content :: proc(buffer: ^FileBuffer, amount: int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete_content_from_selection :: proc(buffer: ^FileBuffer, selection: ^Selection) {
|
||||||
|
assert(len(buffer.content_slices) >= 1);
|
||||||
|
|
||||||
|
selection^ = swap_selections(selection^)
|
||||||
|
|
||||||
|
split_content_slice(buffer, selection);
|
||||||
|
|
||||||
|
it := new_file_buffer_iter_with_cursor(buffer, selection.start);
|
||||||
|
|
||||||
|
// go back one (to be at the end of the content slice)
|
||||||
|
iterate_file_buffer_reverse(&it);
|
||||||
|
|
||||||
|
for _ in selection.start.index.slice_index..<selection.end.index.slice_index {
|
||||||
|
runtime.ordered_remove(&buffer.content_slices, selection.start.index.slice_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !it.hit_end {
|
||||||
|
iterate_file_buffer(&it);
|
||||||
|
}
|
||||||
|
buffer.cursor = it.cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_content :: proc{delete_content_from_buffer_cursor, delete_content_from_selection};
|
||||||
|
|
||||||
|
|
|
@ -182,12 +182,13 @@ register_default_visual_actions :: proc(input_map: ^core.InputActions) {
|
||||||
state.current_input_map = &state.input_map.mode[.Normal];
|
state.current_input_map = &state.input_map.mode[.Normal];
|
||||||
|
|
||||||
core.current_buffer(state).selection = nil;
|
core.current_buffer(state).selection = nil;
|
||||||
|
core.update_file_buffer_scroll(core.current_buffer(state))
|
||||||
}, "exit visual mode");
|
}, "exit visual mode");
|
||||||
|
|
||||||
// Cursor Movement
|
// Cursor Movement
|
||||||
{
|
{
|
||||||
core.register_key_action(input_map, .W, proc(state: ^State) {
|
core.register_key_action(input_map, .W, proc(state: ^State) {
|
||||||
sel_cur := core.current_buffer(state).selection.?;
|
sel_cur := &(core.current_buffer(state).selection.?);
|
||||||
|
|
||||||
core.move_cursor_forward_start_of_word(core.current_buffer(state), cursor = &sel_cur.end);
|
core.move_cursor_forward_start_of_word(core.current_buffer(state), cursor = &sel_cur.end);
|
||||||
}, "move forward one word");
|
}, "move forward one word");
|
||||||
|
@ -235,6 +236,20 @@ register_default_visual_actions :: proc(input_map: ^core.InputActions) {
|
||||||
core.scroll_file_buffer(core.current_buffer(state), .Down, cursor = &sel_cur.end);
|
core.scroll_file_buffer(core.current_buffer(state), .Down, cursor = &sel_cur.end);
|
||||||
}, "scroll buffer up");
|
}, "scroll buffer up");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Text Modification
|
||||||
|
{
|
||||||
|
core.register_key_action(input_map, .D, proc(state: ^State) {
|
||||||
|
sel_cur := &(core.current_buffer(state).selection.?);
|
||||||
|
|
||||||
|
core.delete_content(core.current_buffer(state), sel_cur);
|
||||||
|
|
||||||
|
state.mode = .Normal
|
||||||
|
state.current_input_map = &state.input_map.mode[.Normal];
|
||||||
|
core.current_buffer(state).selection = nil;
|
||||||
|
core.update_file_buffer_scroll(core.current_buffer(state))
|
||||||
|
}, "delete selection");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
register_default_text_input_actions :: proc(input_map: ^core.InputActions) {
|
register_default_text_input_actions :: proc(input_map: ^core.InputActions) {
|
||||||
|
@ -253,7 +268,6 @@ register_default_text_input_actions :: proc(input_map: ^core.InputActions) {
|
||||||
core.register_key_action(input_map, .O, proc(state: ^State) {
|
core.register_key_action(input_map, .O, proc(state: ^State) {
|
||||||
core.move_cursor_end_of_line(core.current_buffer(state), false);
|
core.move_cursor_end_of_line(core.current_buffer(state), false);
|
||||||
core.insert_content(core.current_buffer(state), []u8{'\n'});
|
core.insert_content(core.current_buffer(state), []u8{'\n'});
|
||||||
core.move_cursor_down(core.current_buffer(state));
|
|
||||||
state.mode = .Insert;
|
state.mode = .Insert;
|
||||||
|
|
||||||
sdl2.StartTextInput();
|
sdl2.StartTextInput();
|
||||||
|
@ -1023,6 +1037,9 @@ lua_ui_flags :: proc(L: ^lua.State, index: i32) -> (bit_set[ui.Flag], bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: proc() {
|
main :: proc() {
|
||||||
|
_command_arena: mem.Arena
|
||||||
|
mem.arena_init(&_command_arena, make([]u8, 1024*1024));
|
||||||
|
|
||||||
state = State {
|
state = State {
|
||||||
ctx = context,
|
ctx = context,
|
||||||
screen_width = 640,
|
screen_width = 640,
|
||||||
|
@ -1031,6 +1048,7 @@ main :: proc() {
|
||||||
source_font_height = 16,
|
source_font_height = 16,
|
||||||
input_map = core.new_input_map(),
|
input_map = core.new_input_map(),
|
||||||
commands = make(core.EditorCommandList),
|
commands = make(core.EditorCommandList),
|
||||||
|
command_arena = mem.arena_allocator(&_command_arena),
|
||||||
|
|
||||||
window = nil,
|
window = nil,
|
||||||
directory = os.get_current_directory(),
|
directory = os.get_current_directory(),
|
||||||
|
@ -1042,9 +1060,28 @@ main :: proc() {
|
||||||
log_buffer = core.new_virtual_file_buffer(context.allocator),
|
log_buffer = core.new_virtual_file_buffer(context.allocator),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: please move somewhere else
|
||||||
|
{
|
||||||
|
ti := runtime.type_info_base(type_info_of(plugin.Hook));
|
||||||
|
if v, ok := ti.variant.(runtime.Type_Info_Enum); ok {
|
||||||
|
for i in &v.values {
|
||||||
|
state.hooks[cast(plugin.Hook)i] = make([dynamic]plugin.OnHookProc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ti := runtime.type_info_base(type_info_of(plugin.Hook));
|
||||||
|
if v, ok := ti.variant.(runtime.Type_Info_Enum); ok {
|
||||||
|
for i in &v.values {
|
||||||
|
state.lua_hooks[cast(plugin.Hook)i] = make([dynamic]core.LuaHookRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
context.logger = core.new_logger(&state.log_buffer);
|
context.logger = core.new_logger(&state.log_buffer);
|
||||||
state.ctx = context;
|
state.ctx = context;
|
||||||
|
|
||||||
|
// TODO: don't use this
|
||||||
mem.scratch_allocator_init(&scratch, 1024*1024);
|
mem.scratch_allocator_init(&scratch, 1024*1024);
|
||||||
scratch_alloc = mem.scratch_allocator(&scratch);
|
scratch_alloc = mem.scratch_allocator(&scratch);
|
||||||
|
|
||||||
|
@ -1064,6 +1101,31 @@ main :: proc() {
|
||||||
runtime.append(&state.buffers, buffer);
|
runtime.append(&state.buffers, buffer);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
core.register_editor_command(
|
||||||
|
&state.commands,
|
||||||
|
"nl.spacegirl.editor.core",
|
||||||
|
"Open File",
|
||||||
|
"Opens a file in a new buffer",
|
||||||
|
proc(state: ^State) {
|
||||||
|
log.info("open file args:");
|
||||||
|
|
||||||
|
Args :: struct {
|
||||||
|
file_path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
if args, ok := core.attempt_read_command_args(Args, state.command_args[:]); ok {
|
||||||
|
log.info("attempting to open file", args.file_path)
|
||||||
|
|
||||||
|
buffer, err := core.new_file_buffer(context.allocator, args.file_path, state.directory);
|
||||||
|
if err.type != .None {
|
||||||
|
log.error("Failed to create file buffer:", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.append(&state.buffers, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
core.register_editor_command(
|
core.register_editor_command(
|
||||||
&state.commands,
|
&state.commands,
|
||||||
"nl.spacegirl.editor.core",
|
"nl.spacegirl.editor.core",
|
||||||
|
@ -1074,14 +1136,6 @@ main :: proc() {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
{
|
|
||||||
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 {
|
if len(os.args) > 1 {
|
||||||
for arg in os.args[1:] {
|
for arg in os.args[1:] {
|
||||||
buffer, err := core.new_file_buffer(context.allocator, arg, state.directory);
|
buffer, err := core.new_file_buffer(context.allocator, arg, state.directory);
|
||||||
|
@ -2156,6 +2210,27 @@ main :: proc() {
|
||||||
|
|
||||||
sdl2.StopTextInput();
|
sdl2.StopTextInput();
|
||||||
}
|
}
|
||||||
|
case .TAB: {
|
||||||
|
// TODO: change this to insert a tab character
|
||||||
|
for _ in 0..<4 {
|
||||||
|
append(&buffer.input_buffer, ' ');
|
||||||
|
|
||||||
|
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 .BACKSPACE: {
|
case .BACKSPACE: {
|
||||||
core.delete_content(buffer, 1);
|
core.delete_content(buffer, 1);
|
||||||
|
|
||||||
|
|
10
todo.md
10
todo.md
|
@ -1,20 +1,20 @@
|
||||||
- Stack Like Allocator (for cross-frame temp data)
|
|
||||||
|
|
||||||
- Undo/Redo
|
- Undo/Redo
|
||||||
- Edit History Tree
|
- Edit History Tree
|
||||||
- Finish selections
|
- Finish selections
|
||||||
- Guarantee that start and end are always ordered
|
- Guarantee that start and end are always ordered
|
||||||
- Add in text actions
|
- Add in text actions
|
||||||
- Yank
|
- Yank
|
||||||
- Delete
|
- [x] Delete
|
||||||
- Change
|
- Change
|
||||||
|
- Virtual Whitespace
|
||||||
|
- Allow any-sized tabs
|
||||||
- Modify input system to allow for keybinds that take input
|
- Modify input system to allow for keybinds that take input
|
||||||
- Vim's f and F movement commands
|
- Vim's f and F movement commands
|
||||||
- Vim's r command
|
- Vim's r command
|
||||||
- Command Search and Execution
|
- Command Search and Execution
|
||||||
- Palette based UI?
|
- Palette based UI?
|
||||||
- Registering Plugin Commands that can be run in palette and via other plugins
|
- [ ] Registering Plugin Commands that can be run in palette and via other plugins
|
||||||
- A way to query these commands by-plugin
|
- [x] A way to query these commands by-plugin
|
||||||
- Re-write the UI (again)
|
- Re-write the UI (again)
|
||||||
- Re-do plugin system
|
- Re-do plugin system
|
||||||
- Potentially have a C# plugins system? Use it instead of Lua? (probably not)
|
- Potentially have a C# plugins system? Use it instead of Lua? (probably not)
|
||||||
|
|
Loading…
Reference in New Issue