fix some memory isses

memory-refactor
Patrick Cleaveliln 2025-07-19 23:56:13 +00:00
parent 0a0681c704
commit cdadbbef18
10 changed files with 169 additions and 57 deletions

View File

@ -136,6 +136,8 @@ current_buffer :: proc(state: ^State) -> ^FileBuffer {
}
yank_whole_line :: proc(state: ^State, buffer: ^FileBuffer) {
context.allocator = buffer.allocator
if state.yank_register.data != nil {
delete(state.yank_register.data)
state.yank_register.data = nil

View File

@ -825,8 +825,8 @@ color_character :: proc(buffer: ^FileBuffer, start: Cursor, end: Cursor, palette
}
draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x, y, w, h: int, show_line_numbers: bool = true, show_cursor: bool = true) {
glyph_width := math.min(256, int((w - state.source_font_width) / state.source_font_width));
glyph_height := math.min(256, int((h - state.source_font_height*2) / state.source_font_height)) + 1;
glyph_width := math.min(256, int(w / state.source_font_width));
glyph_height := math.min(256, int(h / state.source_font_height)) + 1;
update_glyph_buffer(buffer, glyph_width, glyph_height);
@ -845,7 +845,7 @@ draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x, y, w, h: int, sh
if show_cursor {
if state.mode == .Normal {
draw_rect(state, cursor_x, cursor_y, state.source_font_width, state.source_font_height, .Background4);
} else if state.mode == .Visual {
} else if state.mode == .Visual && buffer.selection != nil {
start_sel_x := x + padding + buffer.selection.?.start.col * state.source_font_width;
start_sel_y := y + buffer.selection.?.start.line * state.source_font_height;
@ -937,7 +937,9 @@ update_file_buffer_scroll :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) =
cursor = &buffer.history.cursor;
}
if cursor.?.line > (buffer.top_line + buffer.glyphs.height - 5) {
if buffer.glyphs.height < 5 {
buffer.top_line = cursor.?.line
} else if cursor.?.line > (buffer.top_line + buffer.glyphs.height - 5) {
buffer.top_line = math.max(cursor.?.line - buffer.glyphs.height + 5, 0);
} else if cursor.?.line < (buffer.top_line + 5) {
buffer.top_line = math.max(cursor.?.line - 5, 0);

View File

@ -1,8 +1,11 @@
package core
import "core:log"
import "core:mem"
FileHistory :: struct {
allocator: mem.Allocator,
piece_table: PieceTable,
cursor: Cursor,
@ -20,6 +23,7 @@ make_history_with_data :: proc(initial_data: []u8, starting_capacity: int = 1024
context.allocator = allocator
return FileHistory {
allocator = allocator,
piece_table = make_piece_table(initial_data, starting_capacity = starting_capacity),
snapshots = make([]Snapshot, starting_capacity),
next = 0,
@ -31,6 +35,7 @@ make_history_empty :: proc(starting_capacity: int = 1024, allocator := context.a
context.allocator = allocator
return FileHistory {
allocator = allocator,
piece_table = make_piece_table(starting_capacity = starting_capacity),
snapshots = make([]Snapshot, starting_capacity),
next = 0,
@ -54,6 +59,8 @@ free_history :: proc(history: ^FileHistory) {
}
push_new_snapshot :: proc(history: ^FileHistory) {
context.allocator = history.allocator
if history.snapshots[history.next].chunks != nil {
delete(history.snapshots[history.next].chunks)
}
@ -65,6 +72,8 @@ push_new_snapshot :: proc(history: ^FileHistory) {
}
pop_snapshot :: proc(history: ^FileHistory, make_new_snapshot: bool = false) {
context.allocator = history.allocator
new_next, _ := next_indexes(history, backward = true)
if new_next == history.next do return
@ -81,6 +90,8 @@ pop_snapshot :: proc(history: ^FileHistory, make_new_snapshot: bool = false) {
}
recover_snapshot :: proc(history: ^FileHistory) {
context.allocator = history.allocator
new_next, _ := next_indexes(history)
if history.snapshots[new_next].chunks == nil do return
history.next = new_next

View File

@ -84,9 +84,6 @@ render_file_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileB
draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data;
if buffer != nil {
buffer.glyphs.width = e.layout.size.x / state.source_font_width;
buffer.glyphs.height = e.layout.size.y / state.source_font_height + 1;
core.draw_file_buffer(state, buffer, e.layout.pos.x, e.layout.pos.y, e.layout.size.x, e.layout.size.y);
}
};

View File

@ -58,7 +58,12 @@ make_grep_panel :: proc() -> core.Panel {
panel_state := &panel.type.(core.GrepPanel)
mem.arena_init(&panel_state.query_arena, make([]u8, 1024*1024*2))
arena_bytes, err := make([]u8, 1024*1024*2)
if err != nil {
log.errorf("failed to allocate arena for grep panel: '%v'", err)
return
}
mem.arena_init(&panel_state.query_arena, arena_bytes)
panel.input_map = core.new_input_map()
panel_state.glyphs = core.make_glyph_buffer(256,256)
@ -215,7 +220,7 @@ make_grep_panel :: proc() -> core.Panel {
{
defer ui.close_element(s)
// render_raw_buffer(state, s, &state.buffers[panel_state.buffer])
render_raw_buffer(state, s, &panel_state.buffer)
}
}
ui.close_element(s)
@ -275,9 +280,6 @@ render_raw_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileBu
draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data;
if buffer != nil {
buffer.glyphs.width = e.layout.size.x / state.source_font_width;
buffer.glyphs.height = e.layout.size.y / state.source_font_height + 1;
core.draw_file_buffer(state, buffer, e.layout.pos.x, e.layout.pos.y, e.layout.size.x, e.layout.size.y, false);
}
};

View File

@ -66,7 +66,14 @@ open :: proc(state: ^core.State, panel: core.Panel, make_active: bool = true) ->
panel.id = panel_id
state.current_panel = panel_id
mem.arena_init(&panel.arena, make([]u8, 1024*1024*4))
arena_bytes, err := make([]u8, 1024*1024*8)
if err != nil {
log.errorf("failed to allocate memory for panel: '%v'", err)
util.delete(&state.panels, panel_id)
return
}
mem.arena_init(&panel.arena, arena_bytes)
panel.allocator = mem.arena_allocator(&panel.arena)
panel->create(state)

View File

@ -26,6 +26,10 @@ new_test_editor :: proc() -> core.State {
return state
}
delete_editor :: proc(e: ^core.State) {
util.delete(&e.panels)
}
buffer_to_string :: proc(buffer: ^core.FileBuffer) -> string {
if buffer == nil {
log.error("nil buffer")
@ -84,9 +88,7 @@ input_text :: proc(text: string) -> ArtificialTextInput {
}
setup_empty_buffer :: proc(state: ^core.State) {
buffer := core.new_virtual_file_buffer(context.allocator);
panels.open(state, panels.make_file_buffer_panel(len(state.buffers)))
runtime.append(&state.buffers, buffer);
panels.open(state, panels.make_file_buffer_panel(""))
core.reset_input_map(state)
}
@ -135,8 +137,12 @@ expect_cursor_index :: proc(t: ^testing.T, cursor: core.Cursor, chunk_index, cha
insert_from_empty_no_newlines :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
inputted_text := "Hello, world!"
expected_text := fmt.aprintf("%v\n", inputted_text)
@ -146,6 +152,8 @@ insert_from_empty_no_newlines :: proc(t: ^testing.T) {
expect_cursor_index(t, buffer.history.cursor, 0, 12)
contents := buffer_to_string(core.current_buffer(&e))
defer delete(contents)
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
}
@ -153,8 +161,12 @@ insert_from_empty_no_newlines :: proc(t: ^testing.T) {
insert_from_empty_with_newline :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
inputted_text := "Hello, world!\nThis is a new line"
expected_text := fmt.aprintf("%v\n", inputted_text)
@ -164,6 +176,8 @@ insert_from_empty_with_newline :: proc(t: ^testing.T) {
expect_cursor_index(t, buffer.history.cursor, 0, 31)
contents := buffer_to_string(core.current_buffer(&e))
defer delete(contents)
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
}
@ -171,8 +185,12 @@ insert_from_empty_with_newline :: proc(t: ^testing.T) {
insert_in_between_text :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
inputted_text := "Hello, world!"
expected_text := "Hello, beautiful world!\n"
@ -188,6 +206,8 @@ insert_in_between_text :: proc(t: ^testing.T) {
expect_cursor_index(t, buffer.history.cursor, 1, 9)
contents := buffer_to_string(core.current_buffer(&e))
defer delete(contents)
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
}
@ -195,8 +215,12 @@ insert_in_between_text :: proc(t: ^testing.T) {
insert_before_slice_at_beginning_of_file :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
inputted_text := "Hello, world!"
expected_text := "Well, Hello, beautiful world!\n"
@ -215,6 +239,8 @@ insert_before_slice_at_beginning_of_file :: proc(t: ^testing.T) {
expect_cursor_index(t, buffer.history.cursor, 0, 5)
contents := buffer_to_string(core.current_buffer(&e))
defer delete(contents)
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
}
@ -222,8 +248,12 @@ insert_before_slice_at_beginning_of_file :: proc(t: ^testing.T) {
insert_before_slice :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
inputted_text := "Hello, world!"
expected_text := "Hello, beautiful rich world!\n"
@ -243,6 +273,8 @@ insert_before_slice :: proc(t: ^testing.T) {
expect_cursor_index(t, buffer.history.cursor, 2, 4)
contents := buffer_to_string(core.current_buffer(&e))
defer delete(contents)
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
}
@ -250,8 +282,12 @@ insert_before_slice :: proc(t: ^testing.T) {
delete_last_content_slice_beginning_of_file :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
run_text_insertion(&e, "Hello, world!")
@ -283,8 +319,12 @@ delete_last_content_slice_beginning_of_file :: proc(t: ^testing.T) {
delete_in_slice :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
inputted_text := "Hello, world!"
expected_text := "Hello, beautiful h world!\n"
@ -310,6 +350,8 @@ delete_in_slice :: proc(t: ^testing.T) {
expect_cursor_index(t, buffer.history.cursor, 3, 0)
contents := buffer_to_string(core.current_buffer(&e))
defer delete(contents)
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
}
@ -317,8 +359,12 @@ delete_in_slice :: proc(t: ^testing.T) {
delete_across_slices :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
inputted_text := "Hello, world!"
expected_text := "Hello, beautiful world!\n"
@ -352,6 +398,8 @@ delete_across_slices :: proc(t: ^testing.T) {
expect_cursor_index(t, buffer.history.cursor, 2, 0)
contents := buffer_to_string(core.current_buffer(&e))
defer delete(contents)
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
}
@ -359,10 +407,14 @@ delete_across_slices :: proc(t: ^testing.T) {
move_down_next_line_has_shorter_length :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
is_ctrl_pressed := false
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
run_text_insertion(&e, "012345678\n0")
@ -383,10 +435,14 @@ move_down_next_line_has_shorter_length :: proc(t: ^testing.T) {
move_down_on_last_line :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
is_ctrl_pressed := false
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
run_text_insertion(&e, "012345678")
@ -402,8 +458,12 @@ move_down_on_last_line :: proc(t: ^testing.T) {
move_left_at_beginning_of_file :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
run_text_insertion(&e, "01234")
// Move cursor from --------^
@ -425,10 +485,14 @@ move_left_at_beginning_of_file :: proc(t: ^testing.T) {
move_right_at_end_of_file :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
is_ctrl_pressed := false
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
run_text_insertion(&e, "01234")
@ -447,10 +511,14 @@ move_right_at_end_of_file :: proc(t: ^testing.T) {
move_to_end_of_line_from_end :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
is_ctrl_pressed := false
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
run_text_insertion(&e, "01234\n01234")
@ -468,10 +536,14 @@ move_to_end_of_line_from_end :: proc(t: ^testing.T) {
move_to_end_of_line_from_middle :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
is_ctrl_pressed := false
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
run_text_insertion(&e, "01234\n01234")
@ -492,10 +564,14 @@ move_to_end_of_line_from_middle :: proc(t: ^testing.T) {
move_to_beginning_of_line_from_middle :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
is_ctrl_pressed := false
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
run_text_insertion(&e, "01234\n01234")
@ -516,10 +592,14 @@ move_to_beginning_of_line_from_middle :: proc(t: ^testing.T) {
move_to_beginning_of_line_from_start :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
is_ctrl_pressed := false
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
run_text_insertion(&e, "01234\n01234")
@ -540,8 +620,12 @@ move_to_beginning_of_line_from_start :: proc(t: ^testing.T) {
append_end_of_line :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
run_text_insertion(&e, "hello")
@ -562,8 +646,12 @@ append_end_of_line :: proc(t: ^testing.T) {
insert_line_under_current :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
initial_text := "Hello, world!\nThis is a new line"
run_text_insertion(&e, initial_text)
@ -590,6 +678,8 @@ insert_line_under_current :: proc(t: ^testing.T) {
expect_cursor_index(t, buffer.history.cursor, 1, 23)
contents := buffer_to_string(core.current_buffer(&e))
defer delete(contents)
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
}
@ -597,8 +687,12 @@ insert_line_under_current :: proc(t: ^testing.T) {
yank_and_paste_whole_line :: proc(t: ^testing.T) {
e := new_test_editor()
setup_empty_buffer(&e)
defer {
panels.close(&e, 0)
delete_editor(&e)
}
buffer := &e.buffers[0]
buffer := core.current_buffer(&e)
initial_text := "Hello, world!\nThis is a new line"
run_text_insertion(&e, initial_text)
@ -617,22 +711,22 @@ yank_and_paste_whole_line :: proc(t: ^testing.T) {
expect_line_col(t, buffer.history.cursor, 1, 0)
contents := buffer_to_string(core.current_buffer(&e))
defer delete(contents)
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
}
run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pressed: ^bool) {
log.infof("running input: %v", input)
{
run_key_action := proc(state: ^core.State, control_key_pressed: bool, key: core.Key) -> bool {
log.info("key_action")
if current_panel, ok := state.current_panel.?; ok {
panel := util.get(&state.panels, current_panel).?
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.EditorAction:
value(state);
value(state, panel);
return true;
case core.InputActions:
state.current_input_map = &(&state.current_input_map.ctrl_key_actions[key]).action.(core.InputActions)
@ -643,7 +737,7 @@ run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pre
if action, exists := state.current_input_map.key_actions[key]; exists {
switch value in action.action {
case core.EditorAction:
value(state);
value(state, panel);
return true;
case core.InputActions:
state.current_input_map = &(&state.current_input_map.key_actions[key]).action.(core.InputActions)
@ -651,8 +745,6 @@ run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pre
}
}
}
} else {
log.info("current_input_map is null")
}
return false
@ -661,8 +753,6 @@ run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pre
switch state.mode {
case .Visual: fallthrough
case .Normal: {
log.info("it's normal/visual mode")
if key, ok := input.(ArtificialKey); ok {
if key.is_down {
if key.key == .LCTRL {
@ -678,8 +768,6 @@ run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pre
}
}
case .Insert: {
log.info("it's insert mode")
buffer := core.current_buffer(state);
if key, ok := input.(ArtificialKey); ok {
@ -710,24 +798,20 @@ run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pre
}
}
log.info("before text input")
if text_input, ok := input.(ArtificialTextInput); ok {
log.infof("attempting to append '%v' to buffer", text_input)
for char in text_input.text {
if char < 1 {
break;
}
if char == '\n' || (char >= 32 && char <= 125 && len(buffer.input_buffer) < 1024-1) {
log.infof("appening '%v' to buffer", char)
append(&buffer.input_buffer, u8(char));
}
}
if current_panel, ok := state.current_panel.?; ok {
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input_proc != nil {
panel.on_buffer_input_proc(state, &panel.panel_state)
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil {
panel->on_buffer_input(state)
}
}
}

View File

@ -230,6 +230,10 @@ delete_state :: proc(state: ^State) {
}
parse_buffer :: proc(state: ^State, input: Input) {
if state.parser == nil {
return
}
old_tree := state.tree
if old_tree != nil {
defer tree_delete(old_tree)

View File

@ -11,7 +11,7 @@ StaticListSlot :: struct($T: typeid) {
data: T,
}
append_static_list :: proc(list: ^StaticList($T), value: T) -> (id: int, panel: ^T, ok: bool) {
append_static_list :: proc(list: ^StaticList($T), value: T) -> (id: int, item: ^T, ok: bool) {
for i in 0..<len(list.data) {
if !list.data[i].active {
list.data[i].active = true
@ -89,4 +89,8 @@ delete_static_list_elem :: proc(list: ^StaticList($T), index: int) {
}
}
delete :: proc{delete_static_list_elem}
delete_static_list :: proc(list: ^StaticList($T)) {
runtime.delete(list.data)
}
delete :: proc{delete_static_list_elem, delete_static_list}

View File

@ -1,5 +1,4 @@
# Bugs
- Memory Leak
- Fix jumping forward a word jumping past consecutive brackets
- Odd scrolling behavior on small screen heights
- Scrolling past end/beginning of results panics
@ -9,7 +8,7 @@
# Planned Features
- [ ] Jump List
- Use grouped lifetimes exclusively for memory allocation/freeing
- [x] Use grouped lifetimes exclusively for memory allocation/freeing
- [ ] Highlight which panel is currently active
- [ ] Persist end of line cursor position
- Testing Harness