You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
78 lines
2.7 KiB
78 lines
2.7 KiB
# AnsiWrapper cares about Ansi Colour Code \e[...
|
|
class AnsiWrapper
|
|
TAB_TO_SPACES = 2
|
|
ANSI_REGEX = /\e\[[0-9;]*m/ # TODO: support for \x1b and \033
|
|
ANSI_RESET = "\e[0m".freeze
|
|
|
|
def self.wrap(text, length, prefix = '', continuation = prefix)
|
|
if visible_length(prefix) != visible_length(continuation)
|
|
raise "continuation <#{continuation.inspect}> should have the same length as prefix <#{prefix.inspect}>"
|
|
end
|
|
return unless text
|
|
|
|
text = text.gsub("\t", ' ' * TAB_TO_SPACES)
|
|
|
|
lines = split_text_to_lines(text, length - visible_length(prefix))
|
|
lines = inject_continuation_and_ansi_colors_to_lines(lines, prefix, continuation)
|
|
lines.join("\n")
|
|
end
|
|
|
|
private_class_method def self.inject_continuation_and_ansi_colors_to_lines(lines, prefix, continuation)
|
|
last_ansi = ''
|
|
lines.each_with_index.map do |line, index|
|
|
current = index.zero? ? prefix : continuation
|
|
current += last_ansi unless last_ansi.empty? || last_ansi == ANSI_RESET
|
|
current += line
|
|
|
|
last_ansi = scan_for_actual_ansi(line, last_ansi)
|
|
|
|
current += ANSI_RESET if last_ansi.empty? || last_ansi != ANSI_RESET
|
|
current
|
|
end
|
|
end
|
|
|
|
private_class_method def self.scan_for_actual_ansi(line, last_ansi)
|
|
line.scan(ANSI_REGEX).each do |match|
|
|
ansi_code = match.to_s
|
|
if ansi_code == ANSI_RESET
|
|
last_ansi = ANSI_RESET
|
|
else
|
|
last_ansi += ansi_code
|
|
end
|
|
end
|
|
last_ansi
|
|
end
|
|
|
|
private_class_method def self.split_text_to_lines(text, length)
|
|
lines = text.split("\n")
|
|
sublines = lines.map do |line|
|
|
visible_length(line) > length ? visible_split(line, length) : [line]
|
|
end
|
|
sublines.flatten
|
|
end
|
|
|
|
private_class_method def self.visible_length(line)
|
|
raise 'line should not contain carriage return character!' if line.match "\n"
|
|
|
|
ansi_code_length = line.scan(ANSI_REGEX).map(&:length).sum
|
|
line.length - ansi_code_length
|
|
end
|
|
|
|
# TODO: might be refactored with less complexity
|
|
private_class_method def self.visible_split(line, length, stack = '') # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
before, ansi_code, after = line.partition(ANSI_REGEX)
|
|
stack_length = visible_length(stack)
|
|
visible_length = before.length + stack_length
|
|
if visible_length == length
|
|
["#{stack}#{before}#{ansi_code}"] + visible_split(after, length)
|
|
elsif visible_length > length
|
|
first_line = stack + before[0...length - stack_length]
|
|
tail = before[length - stack_length..] + ansi_code + after
|
|
[first_line] + visible_split(tail, length)
|
|
elsif ansi_code.length.positive?
|
|
visible_split(after, length, "#{stack}#{before}#{ansi_code}")
|
|
else
|
|
["#{stack}#{before}#{ansi_code}"]
|
|
end
|
|
end
|
|
end
|