diff --git a/README.md b/README.md index 38a4499..1fa9604 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,16 @@ A TUI IDE. # TODO +- [ ] Fix both scrolling functions / they are absolutely messed up. +- [ ] Change `next_line` to set `out_len` and also re-add `line` to the iterator. +- [ ] Add `hooks` in files that can be set/unset/jumped to. - [ ] Add support for text selection (slect range / all). - [ ] Add support for delete key. +- [ ] Add support for ctrl + arrow key. +- [ ] Add support for ctrl + backspace. +- [ ] Add underline highlight for current word and all occurences. +- [ ] Add bg highlight for current line. +- [ ] Add line numbers. - [ ] Add support for copy/cut/paste. - [ ] Add mouse support. - [ ] Add modes for editing - insert, select, normal, etc. @@ -15,5 +23,8 @@ A TUI IDE. - [ ] Add feature where doing enter uses tree-sitter to add newline with indentation. 1. it should also put stuff like `}` on the next line. - [ ] Add support for brackets/quotes to auto-close. +- [ ] Add support for LSP. +- [ ] Add support for virtual cursor where edits apply at all the places. +- [ ] Add search / replace along with search / virtual cursors - [ ] Add scm files for all the supported languages. (2/14) Done. -- [ ] Add support for wide characters with wrapping. +- [ ] Add codeium/copilot support. diff --git a/grammar/ruby.scm b/grammar/ruby.scm index 7c70da7..3dde384 100644 --- a/grammar/ruby.scm +++ b/grammar/ruby.scm @@ -94,7 +94,7 @@ "ensure" ] @keyword.exception -;; #aad84c #000000 0 0 0 1 +;; #aad84c #000000 0 0 0 3 "defined?" @function ;; #aad84c #000000 0 0 0 3 diff --git a/include/editor.h b/include/editor.h index d1e9ec6..750dd57 100644 --- a/include/editor.h +++ b/include/editor.h @@ -107,15 +107,13 @@ Editor *new_editor(const char *filename, Coord position, Coord size); void free_editor(Editor *editor); void render_editor(Editor *editor); void fold(Editor *editor, uint32_t start_line, uint32_t end_line); -void scroll_up(Editor *editor, uint32_t lines); -void scroll_down(Editor *editor, uint32_t lines); void cursor_up(Editor *editor, uint32_t number); void cursor_down(Editor *editor, uint32_t number); void cursor_left(Editor *editor, uint32_t number); void cursor_right(Editor *editor, uint32_t number); void ensure_scroll(Editor *editor); void apply_edit(std::vector &spans, uint32_t x, int64_t y); -void edit_erase(Editor *editor, uint32_t pos, uint32_t len); +void edit_erase(Editor *editor, uint32_t pos, int64_t len); void edit_insert(Editor *editor, uint32_t pos, char *data, uint32_t len); #endif diff --git a/include/knot.h b/include/knot.h index 91309a5..8753373 100644 --- a/include/knot.h +++ b/include/knot.h @@ -26,7 +26,6 @@ typedef struct LineIterator { Knot *node; uint8_t top; uint32_t offset; - uint32_t line; Knot *stack[64]; } LineIterator; @@ -116,7 +115,7 @@ LineIterator *begin_l_iter(Knot *root, uint32_t start_line); // After getting the necessary lines free the iterator (no need to go upto the // end) returns null if there are no more lines All return strings `must` be // freed by the caller -char *next_line(LineIterator *it); +char *next_line(LineIterator *it, uint32_t *out_len); // Used to start an iterator over leaf data // root is the root of the rope diff --git a/include/ui.h b/include/ui.h index 40bcd9b..f1aa7cd 100644 --- a/include/ui.h +++ b/include/ui.h @@ -46,10 +46,6 @@ #define CNTRL_ALT 3 #define SHIFT 4 -const char VS16_BYTE_A = '\xEF'; -const char VS16_BYTE_B = '\xB8'; -const char VS16_BYTE_C = '\x8F'; - enum CellFlags : uint8_t { CF_NONE = 0, CF_ITALIC = 1 << 0, @@ -86,9 +82,6 @@ extern std::vector old_screen; extern std::mutex screen_mutex; extern std::atomic running; -void get_terminal_size(); -void enable_raw_mode(); -void disable_raw_mode(); Coord start_screen(); void end_screen(); void update(uint32_t row, uint32_t col, const char *utf8, uint32_t fg, @@ -100,6 +93,4 @@ Coord get_size(); int read_input(char *buf, size_t buflen); KeyEvent read_key(); -int display_width(const char *str); - #endif diff --git a/include/utils.h b/include/utils.h index 43b4bb0..fb6d027 100644 --- a/include/utils.h +++ b/include/utils.h @@ -36,9 +36,11 @@ struct Coord { uint32_t col; }; -uint32_t visual_width(const char *s); -uint32_t get_visual_col_from_bytes(const char *line, uint32_t byte_limit); -uint32_t get_bytes_from_visual_col(const char *line, +// std::vector visual_width(const char *s, uint32_t max_width); +int display_width(const char *str, size_t len); +uint32_t get_visual_col_from_bytes(const char *line, uint32_t len, + uint32_t byte_limit); +uint32_t get_bytes_from_visual_col(const char *line, uint32_t len, uint32_t target_visual_col); void log(const char *fmt, ...); std::string get_exe_dir(); diff --git a/samples/ruby.rb b/samples/ruby.rb index 4bee5a4..df82868 100644 --- a/samples/ruby.rb +++ b/samples/ruby.rb @@ -1,4 +1,87 @@ #!/usr/bin/env ruby + +# Unicode / Emoji / CJK stress-test Ruby file +# Purpose: Test syntax highlighting + width calculation in your editor +# --------------------------------------------------------------- + +# Basic output +def greet + puts "Hello, ไธ–็•Œ! ๐Ÿ‘‹๐ŸŒ" +end + +# Emoji-heavy strings +emojis = "๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ๐Ÿ‘‹๐ŸŒ" + +# Mixed-width CJK blocks +cjk_samples = [ + "ๆผขๅญ—ใƒ†ใ‚นใƒˆ", + "ๆธฌ่ฉฆไธญๆ–‡ๅญ—ไธฒ", + "ํ•œ๊ตญ์–ด ํ…Œ์ŠคํŠธ", + "ใฒใ‚‰ใŒใชใ‚ซใ‚ฟใ‚ซใƒŠ๐Ÿ˜€ๆททๅˆ", + "ๅคง้‡ใฎๆ–‡ๅญ—ๅˆ—๐Ÿš€๐Ÿš€๐Ÿš€", +] + +# Ruby regex with unicode +unicode_regex = /[ไธ€-้พฏใ-ใ‚“ใ‚ก-ใƒถใƒผใ€…ใ€†ใ€ค]/ + +# Unicode identifiers (valid in Ruby) +ๅ˜้‡ = 123 +ฯ€ = 3.14159 +ๆŒจๆ‹ถ = -> { "ใ“ใ‚“ใซใกใฏ" } + +# Method using unicode variable names +def math_test + puts "ฯ€ * 2 = #{ฯ€ * 2}" +end + +# Iterate through CJK samples +cjk_samples.each_with_index do |str, idx| + puts "CJK[#{idx}] => #{str} (len=#{str.length})" +end + +# Test emoji width behaviors +puts "Emoji count: #{emojis.length}" + +# Multi-line string with unicode +multi = <<~EOF + ใ“ใ‚Œใฏ่ค‡ๆ•ฐ่กŒใƒ†ใ‚ญใ‚นใƒˆใงใ™ใ€‚ + Emojis inside heredoc: ๐ŸŽ‰๐Ÿ”ฅโœจ๐Ÿ’€โค๏ธ๐Ÿงก๐Ÿ’›๐Ÿ’š๐Ÿ’™๐Ÿ’œ๐Ÿ–ค๐Ÿค๐ŸคŽ + End of block. +EOF + +puts multi + +# Arrays mixing everything +mixed = [ + "๐Ÿ Ruby + Python? sacrilege! ๐Ÿ", + "ๆ—ฅๆœฌ่ชžใจEnglishใจ๐Ÿ”งmix", + "Spacing test โ†’โ†’โ†’โ†’โ†’โ†’โ†’", + "Zero-width joiner test: ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ family emoji", +] + +mixed.each { |m| puts m } + +# Unicode in comments โ€” highlight me! +# ใ‚ณใƒกใƒณใƒˆ๏ผšใ‚จใƒ‡ใ‚ฃใ‚ฟใฎใƒใ‚คใƒฉใ‚คใƒˆใ‚’็ขบ่ชใ—ใพใ™โœจ +# Emojis should not break formatting: ๐Ÿฆ€๐ŸฆŠ๐Ÿฑโ€๐Ÿ‘ค๐Ÿค– + +# Dummy Ruby logic +5.times do |i| + puts "Loop #{i}: ๐ŸŒŸ #{cjk_samples[i % cjk_samples.size]}" +end + +# String escape sequences + unicode +escaped = "Line1\nLine2\tTabbed ๐Ÿ˜€" +puts escaped + +# Frozen string literal test +# frozen_string_literal: true +const_str = "ๅฎšๆ•ฐๆ–‡ๅญ—ๅˆ—๐Ÿ”’".freeze +puts const_str + +# End marker +puts "--- END OF UNICODE TEST FILE ---" + # Ruby syntax highlighting test =begin diff --git a/src/editor.cc b/src/editor.cc index e771c5b..913f482 100644 --- a/src/editor.cc +++ b/src/editor.cc @@ -49,129 +49,21 @@ void free_editor(Editor *editor) { delete editor; } -void scroll_up(Editor *editor, uint32_t number) { - if (!editor || !editor->root || number == 0) - return; - uint32_t count = 0; - uint32_t visible_lines_checked = 0; - int32_t current_check_row = editor->scroll.row; - while (visible_lines_checked < number + 1 && current_check_row >= 0) { - if (editor->folded[current_check_row] != 1) - visible_lines_checked++; - count++; - current_check_row--; - } - if (current_check_row < 0) - count = editor->scroll.row; - LineIterator *it = begin_l_iter(editor->root, editor->scroll.row - count + 1); - std::vector> stack; - stack.reserve(count); - uint32_t lines_iterated = 0; - uint32_t start_row = editor->scroll.row - count + 1; - while (lines_iterated < count) { - char *line_content = next_line(it); - uint32_t current_idx = start_row + lines_iterated; - int fold_state = editor->folded[current_idx]; - if (fold_state == 2) { - stack.push_back({1, current_idx}); - } else if (fold_state == 0) { - uint32_t len = (line_content != nullptr) ? visual_width(line_content) : 0; - stack.push_back({len, current_idx}); - } - if (line_content) - free(line_content); - lines_iterated++; - } - uint32_t ln = 0; - uint32_t wrap_limit = editor->size.col; - for (int i = stack.size() - 1; i >= 0; i--) { - uint32_t len = stack[i].first; - uint32_t row_idx = stack[i].second; - uint32_t segments = - (wrap_limit > 0 && len > 0) ? (len + wrap_limit - 1) / wrap_limit : 1; - if (len == 0) - segments = 1; - for (int seg = (row_idx == editor->scroll.row) - ? editor->scroll.col / wrap_limit - : segments - 1; - seg >= 0; seg--) { - ln++; - if (ln == number + 1) { - editor->scroll.row = row_idx; - editor->scroll.col = seg * wrap_limit; - free(it); - return; - } - } - } - if (ln < number + 1) { - editor->scroll.row = 0; - editor->scroll.col = 0; - } - free(it); -} - -void scroll_down(Editor *editor, uint32_t number) { - if (!editor || !editor->root || number == 0) - return; - LineIterator *it = begin_l_iter(editor->root, editor->scroll.row); - uint32_t current_row = editor->scroll.row; - uint32_t lines_moved = 0; - uint32_t wrap_limit = editor->size.col; - if (wrap_limit == 0) - wrap_limit = 1; - while (lines_moved < number) { - char *line_content = next_line(it); - if (line_content == nullptr) - break; - int fold_state = editor->folded[current_row]; - if (fold_state == 1) { - free(line_content); - current_row++; - continue; - } - uint32_t segments = 1; - if (fold_state == 2) { - segments = 1; - } else { - uint32_t len = visual_width(line_content); - segments = (len > 0) ? (len + wrap_limit - 1) / wrap_limit : 1; - } - uint32_t start_seg = (current_row == editor->scroll.row) - ? (editor->scroll.col / wrap_limit) - : 0; - for (uint32_t seg = start_seg; seg < segments; seg++) { - if (current_row == editor->scroll.row && seg == start_seg) - continue; - lines_moved++; - if (lines_moved == number) { - editor->scroll.row = current_row; - editor->scroll.col = seg * wrap_limit; - free(line_content); - free(it); - return; - } - } - free(line_content); - current_row++; - } - free(it); -} - void cursor_down(Editor *editor, uint32_t number) { if (!editor || !editor->root || number == 0) return; LineIterator *it = begin_l_iter(editor->root, editor->cursor.row); - char *line_content = next_line(it); + uint32_t len; + char *line_content = next_line(it, &len); if (line_content == nullptr) return; if (editor->cursor_preffered == UINT32_MAX) editor->cursor_preffered = - get_visual_col_from_bytes(line_content, editor->cursor.col); + get_visual_col_from_bytes(line_content, len, editor->cursor.col); uint32_t visual_col = editor->cursor_preffered; do { free(line_content); - line_content = next_line(it); + line_content = next_line(it, &len); editor->cursor.row += 1; if (editor->cursor.row >= editor->root->line_count) { editor->cursor.row = editor->root->line_count - 1; @@ -183,7 +75,7 @@ void cursor_down(Editor *editor, uint32_t number) { free(it); if (line_content == nullptr) return; - editor->cursor.col = get_bytes_from_visual_col(line_content, visual_col); + editor->cursor.col = get_bytes_from_visual_col(line_content, len, visual_col); free(line_content); } @@ -191,14 +83,15 @@ void cursor_up(Editor *editor, uint32_t number) { if (!editor || !editor->root || number == 0) return; LineIterator *it = begin_l_iter(editor->root, editor->cursor.row); - char *line_content = next_line(it); + uint32_t len; + char *line_content = next_line(it, &len); if (!line_content) { free(it); return; } if (editor->cursor_preffered == UINT32_MAX) editor->cursor_preffered = - get_visual_col_from_bytes(line_content, editor->cursor.col); + get_visual_col_from_bytes(line_content, len, editor->cursor.col); uint32_t visual_col = editor->cursor_preffered; free(line_content); while (number > 0 && editor->cursor.row > 0) { @@ -209,12 +102,12 @@ void cursor_up(Editor *editor, uint32_t number) { } free(it); it = begin_l_iter(editor->root, editor->cursor.row); - line_content = next_line(it); + line_content = next_line(it, &len); if (!line_content) { free(it); return; } - editor->cursor.col = get_bytes_from_visual_col(line_content, visual_col); + editor->cursor.col = get_bytes_from_visual_col(line_content, len, visual_col); free(line_content); free(it); } @@ -223,13 +116,13 @@ void cursor_right(Editor *editor, uint32_t number) { if (!editor || !editor->root || number == 0) return; LineIterator *it = begin_l_iter(editor->root, editor->cursor.row); - char *line = next_line(it); + uint32_t line_len; + char *line = next_line(it, &line_len); free(it); if (!line) return; - uint32_t line_len = strlen(line); if (line[line_len - 1] == '\n') - line[--line_len] = '\0'; + --line_len; while (number > 0) { if (editor->cursor.col >= line_len) { free(line); @@ -245,13 +138,12 @@ void cursor_right(Editor *editor, uint32_t number) { editor->cursor.row = next_row; editor->cursor.col = 0; it = begin_l_iter(editor->root, editor->cursor.row); - line = next_line(it); + line = next_line(it, &line_len); free(it); if (!line) break; - line_len = strlen(line); if (line[line_len - 1] == '\n') - line[--line_len] = '\0'; + --line_len; } else { uint32_t inc = grapheme_next_character_break_utf8( line + editor->cursor.col, line_len - editor->cursor.col); @@ -262,14 +154,14 @@ void cursor_right(Editor *editor, uint32_t number) { number--; } LineIterator *it2 = begin_l_iter(editor->root, editor->cursor.row); - char *cur_line = next_line(it2); + uint32_t len2; + char *cur_line = next_line(it2, &len2); free(it2); if (cur_line) { - uint32_t len2 = strlen(cur_line); if (len2 > 0 && cur_line[len2 - 1] == '\n') - cur_line[--len2] = '\0'; + --len2; editor->cursor_preffered = - get_visual_col_from_bytes(cur_line, editor->cursor.col); + get_visual_col_from_bytes(cur_line, len2, editor->cursor.col); free(cur_line); } else { editor->cursor_preffered = UINT32_MAX; @@ -282,11 +174,11 @@ void cursor_left(Editor *editor, uint32_t number) { if (!editor || !editor->root || number == 0) return; LineIterator *it = begin_l_iter(editor->root, editor->cursor.row); - char *line = next_line(it); + uint32_t len; + char *line = next_line(it, &len); free(it); if (!line) return; - uint32_t len = strlen(line); if (line[len - 1] == '\n') line[--len] = '\0'; while (number > 0) { @@ -302,19 +194,17 @@ void cursor_left(Editor *editor, uint32_t number) { break; editor->cursor.row = prev_row; it = begin_l_iter(editor->root, editor->cursor.row); - line = next_line(it); + line = next_line(it, &len); free(it); if (!line) break; - uint32_t len = strlen(line); if (line[len - 1] == '\n') - line[--len] = '\0'; + --len; editor->cursor.col = len; } else { uint32_t col = editor->cursor.col; uint32_t new_col = 0; uint32_t visual_col = 0; - uint32_t len = strlen(line); while (new_col < col) { uint32_t inc = grapheme_next_character_break_utf8(line + new_col, len - new_col); @@ -328,14 +218,14 @@ void cursor_left(Editor *editor, uint32_t number) { number--; } LineIterator *it2 = begin_l_iter(editor->root, editor->cursor.row); - char *cur_line = next_line(it2); + uint32_t len2; + char *cur_line = next_line(it2, &len2); free(it2); if (cur_line) { - uint32_t len2 = strlen(cur_line); if (len2 > 0 && cur_line[len2 - 1] == '\n') - cur_line[--len2] = '\0'; + --len2; editor->cursor_preffered = - get_visual_col_from_bytes(cur_line, editor->cursor.col); + get_visual_col_from_bytes(cur_line, len2, editor->cursor.col); free(cur_line); } else { editor->cursor_preffered = UINT32_MAX; @@ -345,53 +235,145 @@ void cursor_left(Editor *editor, uint32_t number) { } void ensure_scroll(Editor *editor) { - if (editor->cursor.row < editor->scroll.row) { - uint32_t visual_delta = 0; + std::shared_lock knot_lock(editor->knot_mtx); + if (editor->cursor.row < editor->scroll.row || + (editor->cursor.row == editor->scroll.row && + editor->cursor.col <= editor->scroll.col)) { LineIterator *it = begin_l_iter(editor->root, editor->cursor.row); - for (uint32_t i = editor->cursor.row; i < editor->scroll.row; i++) { - char *line = next_line(it); + if (!it) + return; + uint32_t len; + char *line = next_line(it, &len); + if (!line) { + free(it); + return; + } + if (len > 0 && line[len - 1] == '\n') + --len; + uint32_t rows = 1; + uint32_t cols = 0; + uint32_t offset = 0; + uint32_t old_offset = 0; + while (offset < len) { + uint32_t inc = + grapheme_next_character_break_utf8(line + offset, len - offset); + int width = display_width(line + offset, inc); + if (cols + width > editor->size.col) { + rows++; + cols = 0; + if (editor->cursor.col > old_offset && editor->cursor.col <= offset) { + editor->scroll.row = editor->cursor.row; + editor->scroll.col = old_offset; + free(line); + free(it); + return; + } + old_offset = offset; + } + cols += width; + offset += inc; + } + free(line); + free(it); + editor->scroll.row = editor->cursor.row; + editor->scroll.col = (editor->cursor.col == 0) ? 0 : old_offset; + } else { + uint32_t line_index = editor->scroll.row; + LineIterator *it = begin_l_iter(editor->root, line_index); + if (!it) + return; + uint32_t max_visual_lines = editor->size.row; + Coord *scroll_queue = (Coord *)malloc(sizeof(Coord) * max_visual_lines); + uint32_t q_head = 0; + uint32_t q_size = 0; + bool first_visual_line = true; + while (true) { + if (editor->folded[line_index]) { + if (editor->folded[line_index] == 2) { + Coord fold_coord = {line_index, 0}; + if (q_size < max_visual_lines) { + scroll_queue[(q_head + q_size) % max_visual_lines] = fold_coord; + q_size++; + } else { + scroll_queue[q_head] = fold_coord; + q_head = (q_head + 1) % max_visual_lines; + } + if (line_index == editor->cursor.row) { + editor->scroll = scroll_queue[q_head]; + break; + } + } + do { + char *line = next_line(it, nullptr); + if (!line) + break; + free(line); + line_index++; + } while (line_index < editor->size.row && + editor->folded[line_index] == 1); + continue; + } + uint32_t line_len; + char *line = next_line(it, &line_len); if (!line) break; - uint32_t len = visual_width(line); - visual_delta += (len + editor->size.col - 1) / editor->size.col; + if (line_len > 0 && line[line_len - 1] == '\n') + line_len--; + uint32_t current_byte_offset = 0; + if (first_visual_line) { + current_byte_offset += editor->scroll.col; + first_visual_line = false; + } + while (current_byte_offset < line_len || + (line_len == 0 && current_byte_offset == 0)) { + Coord current_coord = {line_index, current_byte_offset}; + if (q_size < max_visual_lines) { + scroll_queue[(q_head + q_size) % max_visual_lines] = current_coord; + q_size++; + } else { + scroll_queue[q_head] = current_coord; + q_head = (q_head + 1) % max_visual_lines; + } + uint32_t col = 0; + uint32_t local_render_offset = 0; + uint32_t line_left = line_len - current_byte_offset; + while (line_left > 0 && col < editor->size.col) { + uint32_t cluster_len = grapheme_next_character_break_utf8( + line + current_byte_offset + local_render_offset, line_left); + int width = display_width( + line + current_byte_offset + local_render_offset, cluster_len); + if (col + width > editor->size.col) + break; + local_render_offset += cluster_len; + line_left -= cluster_len; + col += width; + } + if (line_index == editor->cursor.row) { + bool cursor_found = false; + if (editor->cursor.col >= current_byte_offset && + editor->cursor.col < current_byte_offset + local_render_offset) + cursor_found = true; + else if (editor->cursor.col == line_len && + current_byte_offset + local_render_offset == line_len) + cursor_found = true; + if (cursor_found) { + editor->scroll = scroll_queue[q_head]; + free(line); + free(scroll_queue); + free(it); + return; + } + } + current_byte_offset += local_render_offset; + if (line_len == 0) + break; + } + line_index++; free(line); } + free(scroll_queue); free(it); - scroll_up(editor, visual_delta); - return; } - uint32_t current_visual_y = 0; - LineIterator *it = begin_l_iter(editor->root, editor->scroll.row); - uint32_t i = editor->scroll.row; - char *line = nullptr; - bool found_cursor = false; - while ((line = next_line(it)) != nullptr) { - uint32_t lines_in_chunk; - uint32_t offset = (i == editor->scroll.row) ? editor->scroll.col : 0; - if (i == editor->cursor.row) { - uint32_t cursor_sub_row = editor->cursor.col / editor->size.col; - if (i == editor->scroll.row) - cursor_sub_row -= offset / editor->size.col; - current_visual_y += cursor_sub_row; - found_cursor = true; - free(line); - break; - } - uint32_t len = visual_width(line); - uint32_t visible_len = len - offset; - if (visible_len == 0) - visible_len = 1; - lines_in_chunk = (visible_len + editor->size.col - 1) / editor->size.col; - current_visual_y += lines_in_chunk; - free(line); - i++; - } - free(it); - if (found_cursor) - if (current_visual_y >= editor->size.row) { - uint32_t needed_scroll = current_visual_y - editor->size.row + 1; - scroll_down(editor, needed_scroll); - } } void fold(Editor *editor, uint32_t start_line, uint32_t end_line) { @@ -435,34 +417,62 @@ void apply_edit(std::vector &spans, uint32_t x, int64_t y) { } } -void edit_erase(Editor *editor, uint32_t pos, uint32_t len) { +// Reverse erase will update the cursor position +void edit_erase(Editor *editor, uint32_t pos, int64_t len) { if (len == 0) return; - std::shared_lock lock_1(editor->knot_mtx); - uint32_t col; - uint32_t row = byte_to_line(editor->root, pos, &col); - TSPoint start_point = {row, col}; - row = byte_to_line(editor->root, pos + len, &col); - TSPoint old_end_point = {row, col}; - lock_1.unlock(); - std::unique_lock lock_2(editor->knot_mtx); - editor->root = erase(editor->root, pos, len); - lock_2.unlock(); - if (editor->tree) { - TSInputEdit edit = { - .start_byte = pos, - .old_end_byte = pos + len, - .new_end_byte = pos, - .start_point = start_point, - .old_end_point = old_end_point, - .new_end_point = start_point, - }; - editor->edit_queue.push(edit); + if (len < 0) { + std::shared_lock lock_1(editor->knot_mtx); + TSPoint old_point = {editor->cursor.row, editor->cursor.col}; + cursor_left(editor, -len); + uint32_t start = line_to_byte(editor->root, editor->cursor.row, nullptr) + + editor->cursor.col; + lock_1.unlock(); + std::unique_lock lock_2(editor->knot_mtx); + editor->root = erase(editor->root, start, pos - start); + lock_2.unlock(); + if (editor->tree) { + TSInputEdit edit = { + .start_byte = start, + .old_end_byte = pos, + .new_end_byte = start, + .start_point = {editor->cursor.row, editor->cursor.col}, + .old_end_point = old_point, + .new_end_point = {editor->cursor.row, editor->cursor.col}, + }; + editor->edit_queue.push(edit); + } + std::unique_lock lock_3(editor->spans.mtx); + apply_edit(editor->spans.spans, start, start - pos); + if (editor->spans.mid_parse) + editor->spans.edits.push({start, start - pos}); + } else { + std::shared_lock lock_1(editor->knot_mtx); + uint32_t col; + uint32_t row = byte_to_line(editor->root, pos, &col); + TSPoint start_point = {row, col}; + row = byte_to_line(editor->root, pos + len, &col); + TSPoint old_end_point = {row, col}; + lock_1.unlock(); + std::unique_lock lock_2(editor->knot_mtx); + editor->root = erase(editor->root, pos, len); + lock_2.unlock(); + if (editor->tree) { + TSInputEdit edit = { + .start_byte = pos, + .old_end_byte = pos + (uint32_t)len, + .new_end_byte = pos, + .start_point = start_point, + .old_end_point = old_end_point, + .new_end_point = start_point, + }; + editor->edit_queue.push(edit); + } + std::unique_lock lock_3(editor->spans.mtx); + apply_edit(editor->spans.spans, pos, -(int32_t)len); + if (editor->spans.mid_parse) + editor->spans.edits.push({pos, -(int32_t)len}); } - std::unique_lock lock_3(editor->spans.mtx); - apply_edit(editor->spans.spans, pos, -(int32_t)len); - if (editor->spans.mid_parse) - editor->spans.edits.push({pos, -(int32_t)len}); } void edit_insert(Editor *editor, uint32_t pos, char *data, uint32_t len) { @@ -522,11 +532,12 @@ void render_editor(Editor *editor) { rendered_rows++; } do { - char *line = next_line(it); + uint32_t line_len; + char *line = next_line(it, &line_len); if (!line) break; - global_byte_offset += strlen(line); - if (line[strlen(line) - 1] == '\n') + global_byte_offset += line_len; + if (line_len > 0 && line[line_len - 1] == '\n') global_byte_offset--; global_byte_offset++; free(line); @@ -535,37 +546,20 @@ void render_editor(Editor *editor) { editor->folded[line_index] == 1); continue; } - char *line = next_line(it); + uint32_t line_len; + char *line = next_line(it, &line_len); if (!line) break; - uint32_t line_len = strlen(line); if (line_len > 0 && line[line_len - 1] == '\n') line_len--; uint32_t current_byte_offset = 0; - if (rendered_rows == 0 && editor->scroll.col > 0) { - uint32_t skipped_cols = 0; - while (skipped_cols < editor->scroll.col && - current_byte_offset < line_len) { - uint32_t len = grapheme_next_character_break_utf8( - line + current_byte_offset, line_len - current_byte_offset); - current_byte_offset += len; - skipped_cols++; - } - } + if (rendered_rows == 0) + current_byte_offset += editor->scroll.col; while (current_byte_offset < line_len && rendered_rows < editor->size.row) { - uint32_t slice_byte_len = 0; - uint32_t slice_visual_cols = 0; - uint32_t probe_offset = current_byte_offset; - while (slice_visual_cols < editor->size.col && probe_offset < line_len) { - uint32_t len = grapheme_next_character_break_utf8( - line + probe_offset, line_len - probe_offset); - slice_byte_len += len; - probe_offset += len; - slice_visual_cols++; - } uint32_t col = 0; uint32_t local_render_offset = 0; - while (local_render_offset < slice_byte_len) { + uint32_t line_left = line_len - current_byte_offset; + while (line_left > 0 && col < editor->size.col) { if (line_index == editor->cursor.row && editor->cursor.col == (current_byte_offset + local_render_offset)) { cursor.row = editor->position.row + rendered_rows; @@ -578,19 +572,23 @@ void render_editor(Editor *editor) { uint32_t bg = hl ? hl->bg : 0; uint8_t fl = hl ? hl->flags : 0; uint32_t cluster_len = grapheme_next_character_break_utf8( - line + current_byte_offset + local_render_offset, - slice_byte_len - local_render_offset); - if (cluster_len == 0) - cluster_len = 1; + line + current_byte_offset + local_render_offset, line_left); std::string cluster(line + current_byte_offset + local_render_offset, cluster_len); + int width = display_width(cluster.c_str(), cluster_len); + if (col + width > editor->size.col) + break; update(editor->position.row + rendered_rows, editor->position.col + col, cluster.c_str(), fg, bg, fl); local_render_offset += cluster_len; - col++; + line_left -= cluster_len; + col += width; + while (width-- > 1) + update(editor->position.row + rendered_rows, + editor->position.col + col - width, "\x1b", fg, bg, fl); } if (line_index == editor->cursor.row && - editor->cursor.col == (current_byte_offset + slice_byte_len)) { + editor->cursor.col == (current_byte_offset + local_render_offset)) { cursor.row = editor->position.row + rendered_rows; cursor.col = editor->position.col + col; } @@ -600,7 +598,7 @@ void render_editor(Editor *editor) { col++; } rendered_rows++; - current_byte_offset += slice_byte_len; + current_byte_offset += local_render_offset; } if (line_len == 0 || (current_byte_offset >= line_len && rendered_rows == 0)) { diff --git a/src/knot.cc b/src/knot.cc index 702eca9..aaf8d7f 100644 --- a/src/knot.cc +++ b/src/knot.cc @@ -470,7 +470,6 @@ LineIterator *begin_l_iter(Knot *root, uint32_t start_line) { if (!it) return nullptr; it->top = 0; - it->line = start_line; it->node = nullptr; if (start_line == 0) { it->offset = 0; @@ -485,7 +484,6 @@ LineIterator *begin_l_iter(Knot *root, uint32_t start_line) { } Knot *curr = root; uint32_t relative_line = --start_line; - it->line = start_line; while (curr) { it->stack[it->top++] = curr; if (!curr->left && !curr->right) { @@ -518,7 +516,7 @@ LineIterator *begin_l_iter(Knot *root, uint32_t start_line) { } } } - free(next_line(it)); + free(next_line(it, nullptr)); return it; } @@ -547,7 +545,7 @@ static inline void iter_advance_leaf(LineIterator *it) { it->node = nullptr; } -char *next_line(LineIterator *it) { +char *next_line(LineIterator *it, uint32_t *out_len) { if (!it || !it->node) return nullptr; size_t capacity = 128; @@ -586,13 +584,15 @@ char *next_line(LineIterator *it) { it->offset += chunk_len; if (found_newline) { buffer[len] = '\0'; - it->line++; + if (out_len) + *out_len = len; return buffer; } } if (len > 0) { buffer[len] = '\0'; - it->line++; + if (out_len) + *out_len = len; return buffer; } free(buffer); diff --git a/src/main.cc b/src/main.cc index 6d1eeee..aaa913d 100644 --- a/src/main.cc +++ b/src/main.cc @@ -59,7 +59,7 @@ void handle_editor_event(Editor *editor, KeyEvent event) { edit_insert(editor, line_to_byte(editor->root, editor->cursor.row, nullptr) + editor->cursor.col, - (char *)" ", 2); + (char *)"\t", 1); cursor_right(editor, 2); } if (event.key_type == KEY_CHAR && (event.c == '\n' || event.c == '\r')) { @@ -73,8 +73,7 @@ void handle_editor_event(Editor *editor, KeyEvent event) { edit_erase(editor, line_to_byte(editor->root, editor->cursor.row, nullptr) + editor->cursor.col, - 1); - cursor_left(editor, 1); + -1); } ensure_scroll(editor); } @@ -83,7 +82,8 @@ int main(int argc, char *argv[]) { Coord screen = start_screen(); const char *filename = (argc > 1) ? argv[1] : ""; - Editor *editor = new_editor(filename, {0, 0}, {screen.row, screen.col}); + Editor *editor = + new_editor(filename, {10, 10}, {screen.row - 20, screen.col - 20}); if (!editor) { end_screen(); fprintf(stderr, "Failed to load editor\n"); diff --git a/src/renderer.cc b/src/renderer.cc index aaa8563..dda10de 100644 --- a/src/renderer.cc +++ b/src/renderer.cc @@ -1,8 +1,4 @@ // includes -extern "C" { -#include "../libs/libgrapheme/grapheme.h" -#include "../libs/unicode_width/unicode_width.h" -} #include "../include/ui.h" uint32_t rows, cols; @@ -12,39 +8,13 @@ std::vector old_screen; std::mutex screen_mutex; termios orig_termios; -int display_width(const char *str) { - if (!str || !*str) - return 0; - if (str[0] == '\t') - return 4; - unicode_width_state_t state; - unicode_width_init(&state); - int width = 0; - for (size_t j = 0; j < strlen(str); j++) { - unsigned char c = str[j]; - if (c < 128) { - int char_width = unicode_width_process(&state, c); - if (char_width > 0) - width += char_width; - } else { - uint_least32_t cp; - size_t bytes = grapheme_decode_utf8(str + j, strlen(str) - j, &cp); - if (bytes > 1) { - int char_width = unicode_width_process(&state, cp); - if (char_width > 0) - width += char_width; - j += bytes - 1; - } - } +void disable_raw_mode() { + std::string os = "\x1b[?1049l\x1b[2 q\x1b[?1002l\x1b[?25h"; + write(STDOUT_FILENO, os.c_str(), os.size()); + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) { + perror("tcsetattr"); + exit(EXIT_FAILURE); } - return width; -} - -void get_terminal_size() { - struct winsize w; - ioctl(0, TIOCGWINSZ, &w); - rows = w.ws_row; - cols = w.ws_col; } void enable_raw_mode() { @@ -68,18 +38,12 @@ void enable_raw_mode() { write(STDOUT_FILENO, os.c_str(), os.size()); } -void disable_raw_mode() { - std::string os = "\x1b[?1049l\x1b[2 q\x1b[?1002l\x1b[?25h"; - write(STDOUT_FILENO, os.c_str(), os.size()); - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) { - perror("tcsetattr"); - exit(EXIT_FAILURE); - } -} - Coord start_screen() { enable_raw_mode(); - get_terminal_size(); + struct winsize w; + ioctl(0, TIOCGWINSZ, &w); + rows = w.ws_row; + cols = w.ws_col; screen.assign(rows * cols, {}); // allocate & zero-init old_screen.assign(rows * cols, {}); // allocate & zero-init return {rows, cols}; @@ -103,15 +67,6 @@ void update(uint32_t row, uint32_t col, const char *utf8, uint32_t fg, screen[idx].flags = flags; } -bool ends_with(const std::string &string_to_check) { - size_t len = string_to_check.size(); - if (len < 3) - return false; - return (string_to_check[len - 3] == VS16_BYTE_A) && - (string_to_check[len - 2] == VS16_BYTE_B) && - (string_to_check[len - 1] == VS16_BYTE_C); -} - void render() { static bool first_render = true; uint32_t current_fg = 0; @@ -195,25 +150,12 @@ void render() { current_underline = underline; } if (!new_cell.utf8.empty()) { - if (new_cell.utf8[0] == '\t') { + if (new_cell.utf8[0] == '\t') out.append(" "); - } else { - // HACK: This is a hack to work around the fact that emojis should be - // double width but handling them as so requires a lot of - // calculations for word wrapping so eventually have to do that - // and render them as the 2 wide they should be. - if (new_cell.utf8.size() > 1) { - if (new_cell.utf8.size() >= 3 && ends_with(new_cell.utf8)) { - out.append(new_cell.utf8.substr(0, new_cell.utf8.size() - 3)); - out.append("\xEF\xB8\x8E"); - } else { - out.append(new_cell.utf8); - out.append("\xEF\xB8\x8E"); - } - } else { - out.append(new_cell.utf8); - } - } + else if (new_cell.utf8[0] == '\x1b') + out.append(""); + else + out.append(new_cell.utf8); } else { out.append(1, ' '); } diff --git a/src/utils.cc b/src/utils.cc index 20da742..b8d61f9 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -1,5 +1,6 @@ extern "C" { #include "../libs/libgrapheme/grapheme.h" +#include "../libs/unicode_width/unicode_width.h" } #include "../include/utils.h" #include @@ -15,6 +16,34 @@ extern "C" { #include #include +int display_width(const char *str, size_t len) { + if (!str || !*str) + return 0; + if (str[0] == '\t') + return 4; + unicode_width_state_t state; + unicode_width_init(&state); + int width = 0; + for (size_t j = 0; j < len; j++) { + unsigned char c = str[j]; + if (c < 128) { + int char_width = unicode_width_process(&state, c); + if (char_width > 0) + width += char_width; + } else { + uint_least32_t cp; + size_t bytes = grapheme_decode_utf8(str + j, strlen(str) - j, &cp); + if (bytes > 1) { + int char_width = unicode_width_process(&state, cp); + if (char_width > 0) + width += char_width; + j += bytes - 1; + } + } + } + return width; +} + std::string get_exe_dir() { char exe_path[PATH_MAX]; ssize_t count = readlink("/proc/self/exe", exe_path, PATH_MAX); @@ -25,27 +54,12 @@ std::string get_exe_dir() { return path.substr(0, path.find_last_of('/')); } -uint32_t visual_width(const char *s) { - if (!s) - return 0; - uint32_t count = 0; - const char *p = s; - while (*p) { - uint32_t next = grapheme_next_character_break_utf8(p, UINT32_MAX); - if (!next) - next = 1; - p += next; - count++; - } - return count; -} - -uint32_t get_visual_col_from_bytes(const char *line, uint32_t byte_limit) { +uint32_t get_visual_col_from_bytes(const char *line, uint32_t len, + uint32_t byte_limit) { if (!line) return 0; uint32_t visual_col = 0; uint32_t current_byte = 0; - uint32_t len = strlen(line); if (len > 0 && line[len - 1] == '\n') len--; while (current_byte < byte_limit && current_byte < len) { @@ -53,26 +67,33 @@ uint32_t get_visual_col_from_bytes(const char *line, uint32_t byte_limit) { len - current_byte); if (current_byte + inc > byte_limit) break; + int w = display_width(line + current_byte, inc); + if (w < 0) + w = 0; + visual_col += (uint32_t)w; current_byte += inc; - visual_col++; } return visual_col; } -uint32_t get_bytes_from_visual_col(const char *line, +uint32_t get_bytes_from_visual_col(const char *line, uint32_t len, uint32_t target_visual_col) { if (!line) return 0; - uint32_t visual_col = 0; uint32_t current_byte = 0; - uint32_t len = strlen(line); + uint32_t visual_col = 0; if (len > 0 && line[len - 1] == '\n') len--; - while (visual_col < target_visual_col && current_byte < len) { + while (current_byte < len && visual_col < target_visual_col) { uint32_t inc = grapheme_next_character_break_utf8(line + current_byte, len - current_byte); + int w = display_width(line + current_byte, inc); + if (w < 0) + w = 0; + if (visual_col + (uint32_t)w > target_visual_col) + return current_byte; + visual_col += (uint32_t)w; current_byte += inc; - visual_col++; } return current_byte; }