diff --git a/src/core/core.odin b/src/core/core.odin index e45f228..63be5b4 100644 --- a/src/core/core.odin +++ b/src/core/core.odin @@ -41,6 +41,9 @@ State :: struct { current_buffer: int, buffers: [dynamic]FileBuffer, + // TODO: make more than one register to plop stuff into + yank_register: Register, + log_buffer: FileBuffer, current_input_map: ^InputActions, @@ -53,6 +56,11 @@ State :: struct { panels: util.StaticList(Panel), } +Register :: struct { + whole_line: bool, + data: []u8, +} + EditorCommand :: struct { name: string, description: string, @@ -127,6 +135,63 @@ current_buffer :: proc(state: ^State) -> ^FileBuffer { return &state.buffers[state.current_buffer]; } +yank_whole_line :: proc(state: ^State) { + if state.yank_register.data != nil { + delete(state.yank_register.data) + state.yank_register.data = nil + } + + if buffer := current_buffer(state); buffer != nil { + selection := new_selection(buffer, buffer.cursor) + length := selection_length(buffer, selection) + + state.yank_register.whole_line = true + state.yank_register.data = make([]u8, length) + + it := new_file_buffer_iter_with_cursor(buffer, selection.start) + + index := 0 + for !it.hit_end && index < length { + state.yank_register.data[index] = get_character_at_iter(it) + + iterate_file_buffer(&it) + index += 1 + } + } +} + +yank_selection :: proc(state: ^State) { + if state.yank_register.data != nil { + delete(state.yank_register.data) + state.yank_register.data = nil + } + + if buffer := current_buffer(state); buffer != nil && buffer.selection != nil { + selection := swap_selections(buffer.selection.?) + length := selection_length(buffer, selection) + + state.yank_register.whole_line = false + state.yank_register.data = make([]u8, length) + + it := new_file_buffer_iter_with_cursor(buffer, selection.start) + + index := 0 + for !it.hit_end && index < length { + state.yank_register.data[index] = get_character_at_iter(it) + + iterate_file_buffer(&it) + index += 1 + } + } +} + +paste_register :: proc(state: ^State, register: Register) { + if buffer := current_buffer(state); buffer != nil && register.data != nil { + insert_content(buffer, register.data) + move_cursor_left(buffer) + } +} + reset_input_map_from_state_mode :: proc(state: ^State) { reset_input_map_from_mode(state, state.mode) } diff --git a/src/core/file_buffer.odin b/src/core/file_buffer.odin index aeedf89..8900a99 100644 --- a/src/core/file_buffer.odin +++ b/src/core/file_buffer.odin @@ -644,15 +644,25 @@ new_selection_span :: proc(start: Cursor, end: Cursor) -> Selection { }; } -new_selection :: proc{new_selection_zero_length, new_selection_span}; +new_selection_current_line :: proc(buffer: ^FileBuffer, cursor: Cursor) -> Selection { + start := cursor + end := cursor + + move_cursor_start_of_line(buffer, &start) + move_cursor_end_of_line(buffer, true, &end) + + return { + start = start, + end = end, + } +} + +new_selection :: proc{new_selection_zero_length, new_selection_span, new_selection_current_line}; 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) - { + if is_selection_inverted(selection) { swapped.start = selection.end swapped.end = selection.start } @@ -660,6 +670,28 @@ swap_selections :: proc(selection: Selection) -> (swapped: Selection) { return swapped } +is_selection_inverted :: proc(selection: Selection) -> bool { + return 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) +} + +selection_length :: proc(buffer: ^FileBuffer, selection: Selection) -> int { + selection := selection + it := new_file_buffer_iter_with_cursor(buffer, selection.start) + + length := 0 + + for !it.hit_end && !is_selection_inverted(selection) { + iterate_file_buffer(&it); + + selection.start = it.cursor + length += 1 + } + + return length +} + new_virtual_file_buffer :: proc(allocator: mem.Allocator) -> FileBuffer { context.allocator = allocator; width := 256; diff --git a/src/input/input.odin b/src/input/input.odin index 54eb369..29a38eb 100644 --- a/src/input/input.odin +++ b/src/input/input.odin @@ -179,6 +179,31 @@ register_default_visual_actions :: proc(input_map: ^core.InputActions) { sdl2.StartTextInput(); }, "change selection"); } + + // Copy-Paste + { + core.register_key_action(input_map, .Y, proc(state: ^State) { + core.yank_selection(state) + + state.mode = .Normal; + core.reset_input_map(state) + + core.current_buffer(state).selection = nil; + core.update_file_buffer_scroll(core.current_buffer(state)) + }, "Yank Line"); + + core.register_key_action(input_map, .P, proc(state: ^State) { + if state.yank_register.whole_line { + core.insert_content(core.current_buffer(state), []u8{'\n'}); + core.paste_register(state, state.yank_register) + core.insert_content(core.current_buffer(state), []u8{'\n'}); + } else { + core.paste_register(state, state.yank_register) + } + + core.reset_input_map(state) + }, "Paste"); + } } register_default_text_input_actions :: proc(input_map: ^core.InputActions) { @@ -201,4 +226,31 @@ register_default_text_input_actions :: proc(input_map: ^core.InputActions) { sdl2.StartTextInput(); }, "insert mode on newline"); + + // Copy-Paste + { + { + yank_actions := core.new_input_actions() + defer core.register_key_action(input_map, .Y, yank_actions) + + core.register_key_action(&yank_actions, .Y, proc(state: ^State) { + core.yank_whole_line(state) + + core.reset_input_map(state) + }, "Yank Line"); + } + + core.register_key_action(input_map, .P, proc(state: ^State) { + if state.yank_register.whole_line { + core.move_cursor_end_of_line(core.current_buffer(state), false); + core.insert_content(core.current_buffer(state), []u8{'\n'}); + } else { + core.move_cursor_right(core.current_buffer(state)) + } + core.paste_register(state, state.yank_register) + + core.reset_input_map(state) + }, "Paste"); + } + } diff --git a/src/panels/panels.odin b/src/panels/panels.odin index 5301dde..7d96582 100644 --- a/src/panels/panels.odin +++ b/src/panels/panels.odin @@ -181,14 +181,16 @@ render_file_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileB ui.open_element(s, nil, { kind = {ui.Grow{}, ui.Grow{}}}) ui.close_element(s) + it := core.new_file_buffer_iter_with_cursor(buffer, buffer.cursor) ui.open_element( s, fmt.tprintf( - "%v:%v - Slice %v:%v", + "%v:%v - Slice %v:%v - Char: %v", buffer.cursor.line + 1, buffer.cursor.col + 1, buffer.cursor.index.slice_index, - buffer.cursor.index.content_index + buffer.cursor.index.content_index, + core.get_character_at_iter(it) ), {} ) diff --git a/todo.md b/todo.md index 9126d26..7becd7e 100644 --- a/todo.md +++ b/todo.md @@ -37,9 +37,12 @@ - Finish selections - [x] Guarantee that start and end are always ordered - Add in text actions - - [ ] Yank + - [x] Yank - [x] Delete - - [x] Change + - [ ] Change + - [ ] Change + - [ ] Change word + - [ ] Change inside delimiter - Virtual Whitespace - Allow any-sized tabs - Modify input system to allow for keybinds that take input