diff --git a/src/core/file_buffer.odin b/src/core/file_buffer.odin index 283b2e1..b95f3c6 100644 --- a/src/core/file_buffer.odin +++ b/src/core/file_buffer.odin @@ -38,6 +38,11 @@ Cursor :: struct { index: FileBufferIndex, } +Selection :: struct { + start: Cursor, + end: Cursor, +} + Glyph :: struct #packed { codepoint: u8, color: theme.PaletteColor, @@ -52,6 +57,7 @@ FileBuffer :: struct { top_line: int, cursor: Cursor, + selection: Maybe(Selection), original_content: [dynamic]u8, added_content: [dynamic]u8, @@ -415,43 +421,61 @@ file_buffer_line_length :: proc(buffer: ^FileBuffer, index: FileBufferIndex) -> return line_length; } -move_cursor_start_of_line :: proc(buffer: ^FileBuffer) { - if buffer.cursor.col > 0 { - it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor); +move_cursor_start_of_line :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) = nil) { + cursor := cursor; + + if cursor == nil { + cursor = &buffer.cursor; + } + + if cursor.?.col > 0 { + it := new_file_buffer_iter_with_cursor(buffer, cursor.?^); for _ in iterate_file_buffer_reverse(&it) { if it.cursor.col <= 0 { break; } } - buffer.cursor = it.cursor; + cursor.?^ = it.cursor; } } -move_cursor_end_of_line :: proc(buffer: ^FileBuffer, stop_at_end: bool = true) { - it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor); +move_cursor_end_of_line :: proc(buffer: ^FileBuffer, stop_at_end: bool = true, cursor: Maybe(^Cursor) = nil) { + cursor := cursor; + + if cursor == nil { + cursor = &buffer.cursor; + } + + it := new_file_buffer_iter_with_cursor(buffer, cursor.?^); line_length := file_buffer_line_length(buffer, it.cursor.index); if stop_at_end { line_length -= 1; } - if buffer.cursor.col < line_length { + if cursor.?.col < line_length { for _ in iterate_file_buffer(&it) { if it.cursor.col >= line_length { break; } } - buffer.cursor = it.cursor; + cursor.?^ = it.cursor; } } -move_cursor_up :: proc(buffer: ^FileBuffer, amount: int = 1) { - if buffer.cursor.line > 0 { - current_line := buffer.cursor.line; - current_col := buffer.cursor.col; +move_cursor_up :: proc(buffer: ^FileBuffer, amount: int = 1, cursor: Maybe(^Cursor) = nil) { + cursor := cursor; - it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor); + if cursor == nil { + cursor = &buffer.cursor; + } + + if cursor.?.line > 0 { + current_line := cursor.?.line; + current_col := cursor.?.col; + + it := new_file_buffer_iter_with_cursor(buffer, cursor.?^); for _ in iterate_file_buffer_reverse(&it) { if it.cursor.line <= current_line-amount || it.cursor.line < 1 { break; @@ -468,17 +492,23 @@ move_cursor_up :: proc(buffer: ^FileBuffer, amount: int = 1) { } } - buffer.cursor = it.cursor; + cursor.?^ = it.cursor; } - update_file_buffer_scroll(buffer); + update_file_buffer_scroll(buffer, cursor); } -move_cursor_down :: proc(buffer: ^FileBuffer, amount: int = 1) { - current_line := buffer.cursor.line; - current_col := buffer.cursor.col; +move_cursor_down :: proc(buffer: ^FileBuffer, amount: int = 1, cursor: Maybe(^Cursor) = nil) { + cursor := cursor; - it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor); + if cursor == nil { + cursor = &buffer.cursor; + } + + current_line := cursor.?.line; + current_col := cursor.?.col; + + it := new_file_buffer_iter_with_cursor(buffer, cursor.?^); for _ in iterate_file_buffer(&it) { if it.cursor.line >= current_line+amount { break; @@ -494,61 +524,113 @@ move_cursor_down :: proc(buffer: ^FileBuffer, amount: int = 1) { } } - buffer.cursor = it.cursor; - update_file_buffer_scroll(buffer); + cursor.?^ = it.cursor; + update_file_buffer_scroll(buffer, cursor); } -move_cursor_left :: proc(buffer: ^FileBuffer) { - if buffer.cursor.col > 0 { - it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor); +move_cursor_left :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) = nil) { + cursor := cursor; + + if cursor == nil { + cursor = &buffer.cursor; + } + + if cursor.?.col > 0 { + it := new_file_buffer_iter_with_cursor(buffer, cursor.?^); iterate_file_buffer_reverse(&it); - buffer.cursor = it.cursor; + cursor.?^ = it.cursor; } } -move_cursor_right :: proc(buffer: ^FileBuffer, stop_at_end: bool = true) { - it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor); +move_cursor_right :: proc(buffer: ^FileBuffer, stop_at_end: bool = true, cursor: Maybe(^Cursor) = nil) { + cursor := cursor; + + if cursor == nil { + cursor = &buffer.cursor; + } + + it := new_file_buffer_iter_with_cursor(buffer, cursor.?^); line_length := file_buffer_line_length(buffer, it.cursor.index); - if !stop_at_end || buffer.cursor.col < line_length-1 { + if !stop_at_end || cursor.?.col < line_length-1 { iterate_file_buffer(&it); - buffer.cursor = it.cursor; + cursor.?^ = it.cursor; } } -move_cursor_forward_start_of_word :: proc(buffer: ^FileBuffer) { - it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor); +move_cursor_forward_start_of_word :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) = nil) { + cursor := cursor; + + if cursor == nil { + cursor = &buffer.cursor; + } + + it := new_file_buffer_iter_with_cursor(buffer, cursor.?^); iterate_file_buffer_until(&it, until_start_of_word); - buffer.cursor = it.cursor; + cursor.?^ = it.cursor; - update_file_buffer_scroll(buffer); + update_file_buffer_scroll(buffer, cursor); } -move_cursor_forward_end_of_word :: proc(buffer: ^FileBuffer) { - it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor); +move_cursor_forward_end_of_word :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) = nil) { + cursor := cursor; + + if cursor == nil { + cursor = &buffer.cursor; + } + + it := new_file_buffer_iter_with_cursor(buffer, cursor.?^); iterate_file_buffer_until(&it, until_end_of_word); - buffer.cursor = it.cursor; + cursor.?^ = it.cursor; - update_file_buffer_scroll(buffer); + update_file_buffer_scroll(buffer, cursor); } -move_cursor_backward_start_of_word :: proc(buffer: ^FileBuffer) { - it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor); +move_cursor_backward_start_of_word :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) = nil) { + cursor := cursor; + + if cursor == nil { + cursor = &buffer.cursor; + } + + it := new_file_buffer_iter_with_cursor(buffer, cursor.?^); iterate_file_buffer_until_reverse(&it, until_end_of_word); //iterate_file_buffer_until(&it, until_non_whitespace); - buffer.cursor = it.cursor; + cursor.?^ = it.cursor; - update_file_buffer_scroll(buffer); + update_file_buffer_scroll(buffer, cursor); } -move_cursor_backward_end_of_word :: proc(buffer: ^FileBuffer) { - it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor); +move_cursor_backward_end_of_word :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) = nil) { + cursor := cursor; + + if cursor == nil { + cursor = &buffer.cursor; + } + + it := new_file_buffer_iter_with_cursor(buffer, cursor.?^); iterate_file_buffer_until_reverse(&it, until_start_of_word); - buffer.cursor = it.cursor; + cursor.?^ = it.cursor; - update_file_buffer_scroll(buffer); + update_file_buffer_scroll(buffer, cursor); } +new_selection_zero_length :: proc(cursor: Cursor) -> Selection { + return { + start = cursor, + end = cursor, + }; +} + +new_selection_span :: proc(start: Cursor, end: Cursor) -> Selection { + return { + start = start, + end = end, + }; +} + +new_selection :: proc{new_selection_zero_length, new_selection_span}; + new_virtual_file_buffer :: proc(allocator: mem.Allocator) -> FileBuffer { context.allocator = allocator; width := 256; @@ -778,9 +860,22 @@ draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, sho cursor_y -= begin * state.source_font_height; // draw cursor - if state.mode == .Normal || state.mode == .Visual { + if state.mode == .Normal { draw_rect(state, cursor_x, cursor_y, state.source_font_width, state.source_font_height, .Background4); - } else if state.mode == .Insert { + } else if state.mode == .Visual { + 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; + + end_sel_x := x + padding + buffer.selection.?.end.col * state.source_font_width; + end_sel_y := y + buffer.selection.?.end.line * state.source_font_height; + + start_sel_y -= begin * state.source_font_height; + end_sel_y -= begin * state.source_font_height; + + draw_rect(state, start_sel_x, start_sel_y, state.source_font_width, state.source_font_height, .Green); + draw_rect(state, end_sel_x, end_sel_y, state.source_font_width, state.source_font_height, .Blue); + } + else if state.mode == .Insert { draw_rect(state, cursor_x, cursor_y, state.source_font_width, state.source_font_height, .Green); num_line_break := 0; @@ -811,35 +906,72 @@ draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, sho draw_text(state, fmt.tprintf("%d", begin + j + 1), x, text_y); } + line_length := 0; for i in 0..= buffer.selection.?.start.line && begin+j <= buffer.selection.?.end.line { + if begin+j == buffer.selection.?.end.line { + width = buffer.selection.?.end.col * state.source_font_width; + } else { + if begin+j == buffer.selection.?.start.line { + width = (line_length - buffer.selection.?.start.col) * state.source_font_width; + sel_x += buffer.selection.?.start.col * state.source_font_width; + } else { + width = line_length * state.source_font_width; + } + } + } + + draw_rect(state, sel_x, text_y, width, state.source_font_height, .Green); + } + } } -update_file_buffer_scroll :: proc(buffer: ^FileBuffer) { - if buffer.cursor.line > (buffer.top_line + buffer.glyph_buffer_height - 5) { - buffer.top_line = math.max(buffer.cursor.line - buffer.glyph_buffer_height + 5, 0); - } else if buffer.cursor.line < (buffer.top_line + 5) { - buffer.top_line = math.max(buffer.cursor.line - 5, 0); +update_file_buffer_scroll :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) = nil) { + cursor := cursor; + if cursor == nil { + cursor = &buffer.cursor; } + + if cursor.?.line > (buffer.top_line + buffer.glyph_buffer_height - 5) { + buffer.top_line = math.max(cursor.?.line - buffer.glyph_buffer_height + 5, 0); + } else if cursor.?.line < (buffer.top_line + 5) { + buffer.top_line = math.max(cursor.?.line - 5, 0); + } + + // if buffer.cursor.line > (buffer.top_line + buffer.glyph_buffer_height - 5) { + // buffer.top_line = math.max(buffer.cursor.line - buffer.glyph_buffer_height + 5, 0); + // } else if buffer.cursor.line < (buffer.top_line + 5) { + // buffer.top_line = math.max(buffer.cursor.line - 5, 0); + // } } // TODO: don't mangle cursor -scroll_file_buffer :: proc(buffer: ^FileBuffer, dir: ScrollDir) { +scroll_file_buffer :: proc(buffer: ^FileBuffer, dir: ScrollDir, cursor: Maybe(^Cursor) = nil) { + switch dir { case .Up: { - move_cursor_up(buffer, 20); + move_cursor_up(buffer, 20, cursor); } case .Down: { - move_cursor_down(buffer, 20); + move_cursor_down(buffer, 20, cursor); } } } diff --git a/src/main.odin b/src/main.odin index 9456e4b..3df2cec 100644 --- a/src/main.odin +++ b/src/main.odin @@ -158,6 +158,75 @@ register_default_input_actions :: proc(input_map: ^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]; + + state.buffers[state.current_buffer].selection = core.new_selection(state.buffers[state.current_buffer].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]; + + state.buffers[state.current_buffer].selection = nil; + }, "exit visual mode"); + + // Cursor Movement + { + core.register_key_action(input_map, .W, proc(state: ^State) { + sel_cur := &state.buffers[state.current_buffer].selection.?; + + core.move_cursor_forward_start_of_word(&state.buffers[state.current_buffer], cursor = &sel_cur.end); + }, "move forward one word"); + core.register_key_action(input_map, .E, proc(state: ^State) { + sel_cur := &(state.buffers[state.current_buffer].selection.?); + + core.move_cursor_forward_end_of_word(&state.buffers[state.current_buffer], cursor = &sel_cur.end); + }, "move forward to end of word"); + + core.register_key_action(input_map, .B, proc(state: ^State) { + sel_cur := &(state.buffers[state.current_buffer].selection.?); + + core.move_cursor_backward_start_of_word(&state.buffers[state.current_buffer], cursor = &sel_cur.end); + }, "move backward one word"); + + core.register_key_action(input_map, .K, proc(state: ^State) { + sel_cur := &(state.buffers[state.current_buffer].selection.?); + + core.move_cursor_up(&state.buffers[state.current_buffer], cursor = &sel_cur.end); + }, "move up one line"); + core.register_key_action(input_map, .J, proc(state: ^State) { + sel_cur := &(state.buffers[state.current_buffer].selection.?); + + core.move_cursor_down(&state.buffers[state.current_buffer], cursor = &sel_cur.end); + }, "move down one line"); + core.register_key_action(input_map, .H, proc(state: ^State) { + sel_cur := &(state.buffers[state.current_buffer].selection.?); + + core.move_cursor_left(&state.buffers[state.current_buffer], cursor = &sel_cur.end); + }, "move left one char"); + core.register_key_action(input_map, .L, proc(state: ^State) { + sel_cur := &(state.buffers[state.current_buffer].selection.?); + + core.move_cursor_right(&state.buffers[state.current_buffer], cursor = &sel_cur.end); + }, "move right one char"); + + core.register_ctrl_key_action(input_map, .U, proc(state: ^State) { + sel_cur := &(state.buffers[state.current_buffer].selection.?); + + core.scroll_file_buffer(&state.buffers[state.current_buffer], .Up, cursor = &sel_cur.end); + }, "scroll buffer up"); + core.register_ctrl_key_action(input_map, .D, proc(state: ^State) { + sel_cur := &(state.buffers[state.current_buffer].selection.?); + + core.scroll_file_buffer(&state.buffers[state.current_buffer], .Down, cursor = &sel_cur.end); + }, "scroll buffer up"); + } } register_default_text_input_actions :: proc(input_map: ^core.InputActions) { @@ -327,6 +396,9 @@ ui_file_buffer :: proc(ctx: ^ui.Context, buffer: ^FileBuffer) -> ui.Interaction 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); } @@ -958,11 +1030,7 @@ main :: proc() { state.current_input_map = &state.input_map.mode[.Normal]; register_default_input_actions(&state.input_map.mode[.Normal]); - register_default_input_actions(&state.input_map.mode[.Visual]); - 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]; - }, "enter visual mode"); + register_default_visual_actions(&state.input_map.mode[.Visual]); register_default_text_input_actions(&state.input_map.mode[.Normal]); @@ -1033,6 +1101,8 @@ main :: proc() { state.screen_height = int(h); } + sdl2.SetRenderDrawBlendMode(state.sdl_renderer, .BLEND); + // Done to clear the buffer sdl2.StartTextInput(); sdl2.StopTextInput(); @@ -1855,8 +1925,6 @@ main :: proc() { key := plugin.Key(sdl_event.key.keysym.sym); if key == .ESCAPE { core.request_window_close(&state); - state.mode = .Normal; - state.current_input_map = &state.input_map.mode[.Normal]; } if key == .LCTRL { diff --git a/src/theme/theme.odin b/src/theme/theme.odin index e83ee5c..e9f1168 100644 --- a/src/theme/theme.odin +++ b/src/theme/theme.odin @@ -79,7 +79,8 @@ light_palette := []u32 { 0x7c6f64ff, 0xcc241dff, - 0x98971aff, + // FIXME: change this back to not e transparent + 0x98971a33, 0xd79921ff, 0x458588ff, 0xb16286ff,