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.

76 lines
2.7 KiB

4 months ago
4 months ago
4 months ago
3 months ago
4 months ago
3 months ago
4 months ago
3 months ago
4 months ago
3 months ago
4 months ago
3 months ago
4 months ago
3 months ago
4 months ago
  1. # AnsiWrapper cares about Ansi Colour Code \e[...
  2. class AnsiWrapper
  3. TAB_TO_SPACES = 2
  4. def self.wrap(text, length, prefix = '', continuation = prefix)
  5. if visible_length(prefix) != visible_length(continuation)
  6. raise "continuation <#{continuation.inspect}> should have the same length as prefix <#{prefix.inspect}>"
  7. end
  8. return unless text
  9. text = text.gsub("\t", ' ' * TAB_TO_SPACES)
  10. lines = split_text_to_lines(text, length - visible_length(prefix))
  11. lines = inject_continuation_and_ansi_colors_to_lines(lines, prefix, continuation)
  12. lines.join("\n")
  13. end
  14. private_class_method def self.inject_continuation_and_ansi_colors_to_lines(lines, prefix, continuation)
  15. last_ansi = ''
  16. lines.each_with_index.map do |line, index|
  17. current = index.zero? ? prefix : continuation
  18. current += last_ansi unless last_ansi.empty? || last_ansi == AnsiColors::CLEAR
  19. current += line
  20. last_ansi = scan_for_actual_ansi(line, last_ansi)
  21. current += AnsiColors::CLEAR if last_ansi.empty? || last_ansi != AnsiColors::CLEAR
  22. current
  23. end
  24. end
  25. private_class_method def self.scan_for_actual_ansi(line, last_ansi)
  26. line.scan(AnsiCommon::ANSI_REGEX).each do |match|
  27. ansi_code = match.to_s
  28. if ansi_code == AnsiColors::CLEAR
  29. last_ansi = AnsiColors::CLEAR
  30. else
  31. last_ansi += ansi_code
  32. end
  33. end
  34. last_ansi
  35. end
  36. private_class_method def self.split_text_to_lines(text, length)
  37. lines = text.split("\n")
  38. sublines = lines.map do |line|
  39. visible_length(line) > length ? visible_split(line, length) : [line]
  40. end
  41. sublines.flatten
  42. end
  43. private_class_method def self.visible_length(line)
  44. raise 'line should not contain carriage return character!' if line.match "\n"
  45. ansi_code_length = line.scan(AnsiCommon::ANSI_REGEX).map(&:length).sum
  46. line.length - ansi_code_length
  47. end
  48. # TODO: might be refactored with less complexity
  49. private_class_method def self.visible_split(line, length, stack = '') # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
  50. before, ansi_code, after = line.partition(AnsiCommon::ANSI_REGEX)
  51. stack_length = visible_length(stack)
  52. visible_length = before.length + stack_length
  53. if visible_length == length
  54. ["#{stack}#{before}#{ansi_code}"] + visible_split(after, length)
  55. elsif visible_length > length
  56. first_line = stack + before[0...length - stack_length]
  57. tail = before[length - stack_length..] + ansi_code + after
  58. [first_line] + visible_split(tail, length)
  59. elsif ansi_code.length.positive?
  60. visible_split(after, length, "#{stack}#{before}#{ansi_code}")
  61. else
  62. ["#{stack}#{before}#{ansi_code}"]
  63. end
  64. end
  65. end