Files
editor/edit.jai

409 lines
11 KiB
Plaintext

// Todos:
// - move by word
// - select text
// - copy selected text
// - cut selected text
// - move line up
// - move line down
// - duplicate line
// - refactor to make cursor_x be aligned with characters themselves
FPS :: 60;
MS_PER_FRAME :: 1000 / FPS;
State :: struct {
file_name: string;
cursor_x: int;
cursor_y: int;
window_width: int;
window_height: int;
buffer: string;
lines: [..][..]u8;
line_height: int;
first_line: int;
my_font: *Simp.Dynamic_Font;
dt: int;
}
White :: Vector4.{1, 1, 1, 1};
Special_Character_Lookup : [10]u8 : .[
xx #char ")",
xx #char "!",
xx #char "@",
xx #char "#",
xx #char "$",
xx #char "%",
xx #char "^",
xx #char "&",
xx #char "*",
xx #char "(",
];
run_editor :: (file_name: string) {
state: State;
state.window_width = 800;
state.window_height = 600;
state.file_name = file_name;
window := create_window(state.window_width, state.window_height, state.file_name);
my_init_fonts(*state);
Simp.set_render_target(window);
should_quit := false;
prev := get_time();
assert(read_file_lines(*state, state.file_name), "Must be able to read file!");
while !should_quit {
reset_temporary_storage();
now := get_time();
state.dt = now - prev;
prev = now;
Input.update_window_events();
handle_window_resizes(*state, window);
for event : Input.events_this_frame {
if event.type == .QUIT {
should_quit = true;
break;
}
if event.type == .KEYBOARD && event.key_pressed {
if event.key_code == {
case .ESCAPE; should_quit = true;
case .BACKSPACE; handle_backspace(*state);
case .ENTER; handle_enter(*state);
case .ARROW_UP; #through;
case .ARROW_DOWN; #through;
case .ARROW_LEFT; #through;
case .ARROW_RIGHT; handle_arrow(*state, event);
}
if #char "A" <= event.key_code && event.key_code <= #char "z" {
key := event.key_code;
if !event.shift_pressed {
key += #char "a" - #char "A";
}
if event.ctrl_pressed {
if event.key_code == {
case #char "S"; #through;
case #char "s"; handle_save(*state);
}
} else {
insert_character_at_cursor(*state, cast(u8) key);
}
} else if 33 <= event.key_code && event.key_code <= 126 {
// NOTE: this doesn't quite work for some keys, like ` or []
key := event.key_code;
insert_character_at_cursor(*state, cast(u8) key);
}
}
}
Simp.clear_render_target(.0, .0, .0, 1);
render_edit_buffer(state);
Simp.swap_buffers(window);
now = get_time();
if now - prev < MS_PER_FRAME {
sleep_milliseconds(xx (now - prev));
}
}
}
render_edit_buffer :: (using state: State) {
visible_lines := get_visible_lines(state);
for line_buffer: visible_lines {
Simp.set_shader_for_text();
line_str := array_view_to_string(line_buffer);
prefix: string;
suffix: string;
has_cursor_on_line := it_index == cursor_y;
if has_cursor_on_line {
prefix, suffix = string_to_line(line_str, cursor_x);
} else {
prefix, suffix = string_to_line(line_str);
}
prefix_width := Simp.prepare_text(my_font, prefix);
top_of_line := window_height - (it_index + 1) * line_height;
Left_Max :: 5;
Simp.draw_prepared_text(my_font, Left_Max, top_of_line, White);
if has_cursor_on_line {
Simp.set_shader_for_color();
start := ifx cursor_x == 0 then Left_Max else prefix_width + Left_Max;
Simp.immediate_quad(
cast(float) start, cast(float) top_of_line + line_height,
cast(float) start + 1, cast(float) top_of_line,
White);
Simp.set_shader_for_text();
Simp.prepare_text(my_font, suffix);
Simp.draw_prepared_text(my_font, start, top_of_line, White);
}
}
}
insert_character_at_cursor :: (using state: *State, char: u8) {
line := *get_visible_lines(state)[cursor_y];
array_insert_at(line, char, cursor_x);
cursor_x += 1;
}
handle_save :: (using state: *State) {
out_builder: String_Builder;
init_string_builder(*out_builder);
for line: lines {
line_str := string.{count=line.count, data=line.data};
append(*out_builder, line_str);
if it_index != lines.count - 1 {
append(*out_builder, "\n");
}
}
content := builder_to_string(*out_builder);
out_file_name := tprint("%.new", file_name);
write_entire_file(out_file_name, content);
}
handle_window_resizes :: (using state: *State, window: Window_Type) {
for Input.get_window_resizes() {
Simp.update_window(it.window);
if it.window == window {
should_reinit := (it.width != window_width) || (it.height != window_height);
window_width = it.width;
window_height = it.height;
if should_reinit {
my_init_fonts(state);
}
}
}
}
handle_enter :: (using state: *State) {
cursor_line_index := cursor_y + first_line;
current_line := *lines[cursor_line_index];
new_line := [..]u8.{};
array_add(*new_line, array_view(current_line.*, cursor_x));
array_insert_at(*lines, new_line, cursor_line_index + 1);
current_line.count = cursor_x;
handle_arrow(state, .ARROW_DOWN);
cursor_x = 0;
}
handle_backspace :: (using state: *State) {
if cursor_x == 0 && cursor_y == 0 && first_line == 0 {
return;
}
cursor_line_index := cursor_y + first_line;
current_line := *lines[cursor_line_index];
if cursor_x == 0 {
prev_line := *lines[cursor_line_index - 1];
prev_count := prev_line.count;
array_add(prev_line, current_line.*);
array_remove_at(*lines, cursor_line_index);
handle_arrow(state, .ARROW_UP);
cursor_x = prev_count;
} else {
array_remove_at(current_line, cursor_x - 1);
cursor_x -= 1;
}
}
handle_arrow :: (state: *State, key_code: Input.Key_Code) {
handle_arrow(state, .{key_code=key_code});
}
handle_arrow :: (using state: *State, using event: Input.Event) {
visible_lines := get_visible_lines(state);
if key_code == {
case .ARROW_UP;
if cursor_y == 0 && first_line == 0 return;
if cursor_y == 0 {
first_line -= 1;
} else {
cursor_y -= 1;
}
case .ARROW_DOWN;
if cursor_y == visible_lines.count - 1 {
first_line = min(first_line + 1, lines.count);
} else {
cursor_y += 1;
}
case .ARROW_LEFT;
if cursor_x == 0 && cursor_y != 0 {
prev_line := visible_lines[cursor_y - 1];
len := prev_line.count;
cursor_y = max(0, cursor_y - 1);
cursor_x = len;
} else if cursor_x != 0 && ctrl_pressed {
} else if cursor_x != 0 {
cursor_x -= 1;
}
case .ARROW_RIGHT;
line := visible_lines[cursor_y];
len := line.count;
if cursor_x == len {
cursor_x = 0;
cursor_y += 1;
} else if ctrl_pressed {
} else {
cursor_x += 1;
}
}
}
get_visible_lines :: (using state: State) -> [][..]u8 {
num_lines_in_screen := window_height / line_height - 1;
return array_view(lines, first_line, num_lines_in_screen);
}
string_to_line :: (str: string, cursor_char_index := -1) -> string, string {
prefix_builder: String_Builder;
prefix_builder.allocator = temporary_allocator;
suffix_builder: String_Builder;
suffix_builder.allocator = temporary_allocator;
init_string_builder(*prefix_builder);
init_string_builder(*suffix_builder);
start := 0;
char_index := 0;
cursor_index := 0;
current_builder := *prefix_builder;
while char_index < str.count {
defer char_index += 1;
c := str.data[char_index];
if c == #char "\t" {
// s := string.{data=str.data + start, count=(char_index + 1) - start};
// append(current_builder, s);
// append(current_builder, " ");
// start = char_index + 1;
// cursor_index += 4;
} else {
cursor_index += 1;
}
if cursor_index == cursor_char_index {
s := string.{data=str.data + start, count=(char_index + 1) - start};
append(current_builder, s);
current_builder = *suffix_builder;
start = char_index + 1;
}
}
s := string.{data=str.data + start, count=str.count - start};
append(current_builder, s);
prefix := builder_to_string(*prefix_builder,, temp);
suffix := builder_to_string(*suffix_builder,, temp);
return prefix, suffix;
}
get_time :: () -> s64 {
val, ok := to_milliseconds(current_time_monotonic());
assert(ok, "Must be able to convert from apollo to milliseconds");
return val;
}
read_file_lines :: (using state: *State, file_path: string) -> bool {
file_contents, ok := read_entire_file(file_path);
if !ok return ok;
for lines free(*it);
free(lines.data);
lines = [..][..]u8.{};
lines_temp := split(file_contents, "\n");
defer free(lines_temp.data);
for line: lines_temp {
builder: [..]u8;
array_add(*builder, array_view(line));
array_add(*lines, builder);
}
return ok;
}
my_init_fonts :: (using state: *State) {
line_height = 16;
base_path := path_strip_filename(get_path_of_running_executable());
my_font = Simp.get_font_at_size("/home/grant/.local/share/fonts/otf/BerkeleyMono/", "BerkeleyMono-Regular.otf", line_height);
// my_font = Simp.get_font_at_size(base_path, "Anonymous Pro.ttf", line_height);
assert(my_font != null);
}
array_add :: (xs: *[..]$T, to_append: []T) {
array_reserve(xs, xs.count + to_append.count);
memcpy(xs.data + xs.count, to_append.data, to_append.count * size_of(T));
xs.count += to_append.count;
}
array_view :: (s: string) -> []u8 {
return []u8.{count=s.count, data=s.data};
}
array_view_to_string :: (chars: []u8) -> string {
return string.{count=chars.count, data=chars.data};
}
array_remove_at :: (xs: *[..]$T, index: int) {
xs.count -= 1;
memcpy(xs.data + index, xs.data + index + 1, xs.count - index);
}
#import "Basic";
#import "File";
#import "Math";
#import "String";
#import "System";
#import "Window_Creation";
Input :: #import "Input";
Simp :: #import "Simp";