From 4cb1e810ef1ef6fe0e6d0fce309a70c0f4f840df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20M=C3=A5rdbrink?= Date: Tue, 26 Aug 2025 12:00:43 +0200 Subject: [PATCH] Refine character deletion --- cli/cli.odin | 156 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 92 insertions(+), 64 deletions(-) diff --git a/cli/cli.odin b/cli/cli.odin index d1899f1..2ade5f4 100644 --- a/cli/cli.odin +++ b/cli/cli.odin @@ -10,6 +10,7 @@ import "core:mem" import "core:slice" import "core:bytes" import "core:strings" +import "core:unicode" import "core:sys/posix" import "core:path/filepath" import "core:unicode/utf8" @@ -37,20 +38,7 @@ Term :: struct { term := Term{} -init_cli :: proc () { - mem_err: mem.Allocator_Error - term.input.buffer, mem_err = make([dynamic]rune, context.allocator) - log.assertf(mem_err == nil, "Memory allocation failed") - - term.history.data, mem_err = make([dynamic][]rune, context.allocator) - log.assertf(mem_err == nil, "Memory allocation failed") - - home_path := string(posix.getenv("HOME")) - log.assertf(home_path != "", "Home path not found") - - term.history.file = filepath.join({home_path, config.HISTORY_FILE}, context.allocator) - log.assertf(mem_err == nil, "Memory allocation failed") - +fill_history_cache :: proc () { USER_PERMISSIONS :: 0o600 if !os.exists(term.history.file) { handle, ferr := os.open(term.history.file, os.O_CREATE | os.O_RDWR, USER_PERMISSIONS) @@ -66,7 +54,23 @@ init_cli :: proc () { for line in strings.split_lines_iterator(&history_content_it) { append(&term.history.data, utf8.string_to_runes(line)) } +} +init_cli :: proc () { + mem_err: mem.Allocator_Error + term.input.buffer, mem_err = make([dynamic]rune, context.allocator) + log.assertf(mem_err == nil, "Memory allocation failed") + + term.history.data, mem_err = make([dynamic][]rune, context.allocator) + log.assertf(mem_err == nil, "Memory allocation failed") + + home_path := string(posix.getenv("HOME")) + log.assertf(home_path != "", "Home path not found") + + term.history.file = filepath.join({home_path, config.HISTORY_FILE}, context.allocator) + log.assertf(mem_err == nil, "Memory allocation failed") + + fill_history_cache() } deinit_cli :: proc () { @@ -95,7 +99,6 @@ get_maybe_git_prompt :: proc(dir: string) -> Maybe(string) { GIT_FOLDER :: ".git" git_path := filepath.join({dir, GIT_FOLDER}, context.temp_allocator) uses_git := os.exists(git_path) - if !uses_git do return nil HEAD_FILE_NAME :: "HEAD" @@ -107,20 +110,22 @@ get_maybe_git_prompt :: proc(dir: string) -> Maybe(string) { ref_parts, merr := strings.split(string(head_data), "/", context.temp_allocator) log.assertf(merr == nil, "Memory allocation failed") - branch, prompt_part: string + branch: string ok: bool is_raw_hash := len(ref_parts) == 1 if is_raw_hash { HASH_MAX_LEN :: 9 - branch, ok = strings.substring(ref_parts[0], 0, HASH_MAX_LEN) + branch, ok = strings.substring(string(head_data), 0, HASH_MAX_LEN) if !ok do return nil } else { branch = strings.trim_right_space(ref_parts[len(ref_parts) - 1]) } // todo: add if branch has changes or not + prompt_part: string prompt_part, merr = strings.concatenate({"‹", branch, "›"}, context.temp_allocator) log.assertf(merr == nil, "Memory allocation failed") + return prompt_part } @@ -147,23 +152,40 @@ get_prompt_prefix :: proc() -> string { user = string(pw.pw_name) } + git_prompt := get_maybe_git_prompt(dir).? or_else "" + + FIRST_DETAIL :: " :: " + SPACE :: " " + SECOND_DETAIL :: " » " prompt_parts := []string { config.USER_COLOR, user, config.DETAILS_COLOR, - " :: ", + FIRST_DETAIL, config.DIR_COLOR, prompt_dir, - " ", + SPACE, config.GIT_COLOR, - get_maybe_git_prompt(dir).? or_else "", + git_prompt, config.DETAILS_COLOR, - " » ", - config.BASE_COLOR} + SECOND_DETAIL, + config.BASE_COLOR + } prompt, err := strings.concatenate(prompt_parts[:], context.temp_allocator) log.assertf(err == nil, "Memory allocation failed") + + raw_prompt := []string { + user, + FIRST_DETAIL, + prompt_dir, + SPACE, + git_prompt, + SECOND_DETAIL, + } + raw_prompt_str, _ := strings.concatenate(raw_prompt[:], context.temp_allocator) + term.input.prompt_size = i32(len(utf8.string_to_runes(raw_prompt_str, context.temp_allocator))) return prompt } @@ -197,21 +219,18 @@ handle_nav :: proc(in_stream: ^io.Stream) { return } - pop_runes(i32(len(term.input.buffer))) - history_idx := term.history.maybe_idx.? or_else i32(len(term.history.data)) history_idx -= 1 term.history.maybe_idx = history_idx new_input := term.history.data[history_idx] - write_runes(new_input[:]) + + replace_input(new_input) case DOWN: history_idx, scrolling_history := term.history.maybe_idx.? if !scrolling_history do return - pop_runes(i32(len(term.input.buffer))) - history_idx += 1 history_len := i32(len(term.history.data)) new_input: []rune @@ -227,7 +246,8 @@ handle_nav :: proc(in_stream: ^io.Stream) { term.history.maybe_idx = history_idx new_input = term.history.data[history_idx] } - write_runes(new_input[:]) + + replace_input(new_input) case RIGHT: if term.input.cursor_offset > 0 { @@ -244,45 +264,47 @@ handle_nav :: proc(in_stream: ^io.Stream) { } -@(private) -write_runes :: proc(rns: []rune) { - append(&term.input.buffer, ..rns[:]) - fmt.print(utf8.runes_to_string(rns[:])) -} - @(private) write_rune :: proc(rn: rune) { - append(&term.input.buffer, rn) - fmt.print(rn) + idx_to_inject := i32(len(term.input.buffer)) - term.input.cursor_offset + inject_at(&term.input.buffer, idx_to_inject, rn) + CLEAR_ALL :: "\r\x1b[2K" + fmt.print(CLEAR_ALL) + print_prompt() + + fmt.print(utf8.runes_to_string(term.input.buffer[:], context.temp_allocator)) } @(private) -pop_runes :: proc(amount: i32) { - DELETE_AND_REVERSE :: "\b \b" - log.assertf(amount <= i32(len(term.input.buffer)), "Cannot remove more runes that written in buffer") - - start_idx := i32(len(term.input.buffer)) - amount - runes := term.input.buffer[start_idx:] - _, _, width := utf8.grapheme_count(utf8.runes_to_string(runes, context.temp_allocator)) - resize(&term.input.buffer, i32(len(term.input.buffer)) - amount) +replace_input :: proc(rns: []rune) { + clear_input() - for _ in 0.. Maybe(string) { case '\n': fmt.println() if len(term.input.buffer) == 0 do return "" - + term.input.cursor_offset = 0 append_history_file(term.input.buffer[:]) input := utf8.runes_to_string(term.input.buffer[:], context.temp_allocator) @@ -337,8 +362,11 @@ run_prompt :: proc() -> Maybe(string) { case '\f': return "clear" // This is a terminal history wipe, usually isn't + case '\t': + + case '\u007f': - pop_rune_at_cursor() + remove_rune_at_cursor() case '\x1b': handle_esc_seq(&in_stream)