Module: Rvim::DisplayMotion
- Defined in:
- lib/rvim/display_motion.rb
Class Method Summary collapse
-
.byte_at_display_column(text, target_col) ⇒ Object
Walk grapheme clusters and stop on the one that covers target_col; land at its start byte.
- .display_column_in(text, byte_offset) ⇒ Object
-
.land_on_segment(line_index, segment, desired_col) ⇒ Object
Convert a desired display column into a byte offset within the segment text, then return [line_index, absolute_byte].
- .locate_segment(segments, byte_pointer) ⇒ Object
-
.next_position(lines, line_index, byte_pointer, width, direction, splitter:) ⇒ Object
Compute the next/prev position for display-line motion (gj/gk).
-
.snap_back_to_char_boundary(text, byte) ⇒ Object
If ‘byte` lands on a UTF-8 continuation byte (10xxxxxx, 0x80-0xBF), walk back to the leading byte of that codepoint so callers don’t produce mid-character cursor positions.
Class Method Details
.byte_at_display_column(text, target_col) ⇒ Object
Walk grapheme clusters and stop on the one that covers target_col; land at its start byte. If target_col is at/past EOL, clamp to the last character’s start byte.
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/rvim/display_motion.rb', line 70 def self.byte_at_display_column(text, target_col) return 0 if text.nil? || text.empty? || target_col <= 0 cur_col = 0 cur_byte = 0 text.grapheme_clusters.each do |gc| mbchar = gc.encode(Encoding::UTF_8) w = Reline::Unicode.get_mbchar_width(mbchar) return cur_byte if cur_col + w > target_col cur_col += w cur_byte += gc.bytesize end return 0 if cur_byte.zero? last_size = Reline::Unicode.get_prev_mbchar_size(text, cur_byte) [cur_byte - last_size, 0].max rescue ArgumentError 0 end |
.display_column_in(text, byte_offset) ⇒ Object
60 61 62 63 64 65 |
# File 'lib/rvim/display_motion.rb', line 60 def self.display_column_in(text, byte_offset) slice = text.byteslice(0, [byte_offset, text.bytesize].min) || '' Reline::Unicode.calculate_width(slice.to_s) rescue ArgumentError 0 end |
.land_on_segment(line_index, segment, desired_col) ⇒ Object
Convert a desired display column into a byte offset within the segment text, then return [line_index, absolute_byte].
54 55 56 57 58 |
# File 'lib/rvim/display_motion.rb', line 54 def self.land_on_segment(line_index, segment, desired_col) seg_off, seg_text = segment byte = byte_at_display_column(seg_text, desired_col) [line_index, seg_off + byte] end |
.locate_segment(segments, byte_pointer) ⇒ Object
45 46 47 48 49 50 |
# File 'lib/rvim/display_motion.rb', line 45 def self.locate_segment(segments, byte_pointer) return 0 if segments.empty? idx = (segments.size - 1).downto(0).find { |i| segments[i][0] <= byte_pointer } idx || 0 end |
.next_position(lines, line_index, byte_pointer, width, direction, splitter:) ⇒ Object
Compute the next/prev position for display-line motion (gj/gk). Returns [new_line_index, new_byte_pointer] or nil when at the buffer edge. ‘splitter` is a callable: splitter.call(line_text, width) -> Array of [byte_offset, segment_text].
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/rvim/display_motion.rb', line 9 def self.next_position(lines, line_index, byte_pointer, width, direction, splitter:) return nil if width.nil? || width <= 0 cur_line = lines[line_index] || '' segments = splitter.call(cur_line, width) cur_seg_idx = locate_segment(segments, byte_pointer) seg_off, seg_text = segments[cur_seg_idx] byte_in_seg = byte_pointer - seg_off # Preserve the *display column* (terminal cells), not the byte offset, # so wrapping from "1234..." to "あいうえお" keeps the cursor's visual # column instead of dumping it mid-codepoint. desired_col = display_column_in(seg_text, byte_in_seg) if direction == :down if cur_seg_idx + 1 < segments.size land_on_segment(line_index, segments[cur_seg_idx + 1], desired_col) else return nil if line_index >= lines.size - 1 next_line = lines[line_index + 1] || '' next_segs = splitter.call(next_line, width) land_on_segment(line_index + 1, next_segs[0], desired_col) end else if cur_seg_idx > 0 land_on_segment(line_index, segments[cur_seg_idx - 1], desired_col) else return nil if line_index <= 0 prev_line = lines[line_index - 1] || '' prev_segs = splitter.call(prev_line, width) land_on_segment(line_index - 1, prev_segs[-1], desired_col) end end end |
.snap_back_to_char_boundary(text, byte) ⇒ Object
If ‘byte` lands on a UTF-8 continuation byte (10xxxxxx, 0x80-0xBF), walk back to the leading byte of that codepoint so callers don’t produce mid-character cursor positions.
94 95 96 97 98 99 |
# File 'lib/rvim/display_motion.rb', line 94 def self.snap_back_to_char_boundary(text, byte) while byte.positive? && (b = text.getbyte(byte)) && b >= 0x80 && b < 0xC0 byte -= 1 end byte end |