class Verse::Wrapping
A class responsible for text wrapping
Constants
- DEFAULT_WIDTH
Attributes
text[R]
The text to wrap
@api private
Public Class Methods
new(text, options = {})
click to toggle source
Initialize a Wrapping
@param [String] text
the text to be wrapped
@param [Hash] options
@option options [Symbol] :padding the desired spacing
@api public
# File lib/verse/wrapping.rb, line 17 def initialize(text, options = {}) @text = text @line_width = options.fetch(:line_width) { DEFAULT_WIDTH } end
wrap(text, wrap_at, options = {})
click to toggle source
Wrap a text into lines no longer than wrap_at
@api public
# File lib/verse/wrapping.rb, line 25 def self.wrap(text, wrap_at, options = {}) new(text, options).wrap(wrap_at) end
Public Instance Methods
wrap(wrap_at = DEFAULT_WIDTH)
click to toggle source
Wrap a text into lines no longer than wrap_at length. Preserves existing lines and existing word boundaries.
@example
wrapping = Verse::Wrapping.new "Some longish text" wrapping.wrap(8) # => >Some >longish >text
@api public
# File lib/verse/wrapping.rb, line 41 def wrap(wrap_at = DEFAULT_WIDTH) if text.length < wrap_at.to_i || wrap_at.to_i.zero? return text end ansi_stack = [] text.split(NEWLINE, -1).map do |paragraph| format_paragraph(paragraph, wrap_at, ansi_stack) end * NEWLINE end
Protected Instance Methods
display_width(string)
click to toggle source
Visible width of string
@api private
# File lib/verse/wrapping.rb, line 187 def display_width(string) Unicode::DisplayWidth.of(Sanitizer.sanitize(string)) end
format_paragraph(paragraph, wrap_at, ansi_stack)
click to toggle source
Format paragraph to be maximum of wrap_at length
@param [String] paragraph
the paragraph to format
@param [Integer] wrap_at
the maximum length to wrap the paragraph
@return [Array]
the wrapped lines
@api private
# File lib/verse/wrapping.rb, line 69 def format_paragraph(paragraph, wrap_at, ansi_stack) cleared_para = Sanitizer.replace(paragraph) lines = [] line = '' word = '' word_length = 0 line_length = 0 char_length = 0 # visible char length text_length = display_width(cleared_para) total_length = 0 ansi = '' matched = nil to_chars(cleared_para) do |char| if char == ANSI # found ansi ansi << char && next end if ansi.length > 0 ansi << char if Sanitizer.ansi?(ansi) # we found ansi let's consume matched = ansi elsif matched ansi_stack << [matched[0...-1], line_length + word_length] matched = nil ansi = '' end next if ansi.length > 0 end char_length = display_width(char) total_length += char_length if line_length + word_length + char_length <= wrap_at if char == SPACE || total_length == text_length line << word + char line_length += word_length + char_length word = '' word_length = 0 else word << char word_length += char_length end next end if char == SPACE # ends with space lines << insert_ansi(ansi_stack, line) line = '' line_length = 0 word += char word_length += char_length elsif word_length + char_length <= wrap_at lines << insert_ansi(ansi_stack, line) line = word + char line_length = word_length + char_length word = '' word_length = 0 else # hyphenate word - too long to fit a line lines << insert_ansi(ansi_stack, word) line_length = 0 word = char word_length = char_length end end lines << insert_ansi(ansi_stack, line) unless line.empty? lines << insert_ansi(ansi_stack, word) unless word.empty? lines end
insert_ansi(ansi_stack, string)
click to toggle source
Insert ANSI code into string
Check if there are any ANSI states, if present insert ANSI codes at given positions unwinding the stack.
@param [Array[Array[String, Integer]]] ansi_stack
the ANSI codes to apply
@param [String] string
the string to insert ANSI codes into
@return [String]
@api private
# File lib/verse/wrapping.rb, line 151 def insert_ansi(ansi_stack, string) return string if ansi_stack.empty? to_remove = 0 reset_index = -1 output = string.dup resetting = false ansi_stack.reverse_each do |state| if state[0] =~ /#{Regexp.quote(RESET)}/ resetting = true reset_index = state[1] to_remove += 2 next elsif !resetting reset_index = -1 resetting = false end color, color_index = *state output.insert(reset_index, RESET).insert(color_index, color) end ansi_stack.pop(to_remove) # remove used states output end
to_chars(text, &block)
click to toggle source
@api private
# File lib/verse/wrapping.rb, line 176 def to_chars(text, &block) if block_given? UnicodeUtils.each_grapheme(text, &block) else UnicodeUtils.each_grapheme(text) end end