Class: Canopus::Markdown

Inherits:
Object
  • Object
show all
Defined in:
lib/canopus/markdown.rb,
sig/markdown.rbs

Overview

Bounded hover content. Rouge already understands Markdown and fenced code; HTML and images remain text and never fetch resources or execute anything.

Defined Under Namespace

Classes: Run

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source, width: 72, max_rows: 18, markup: true) ⇒ Markdown

Returns a new instance of Markdown.

Parameters:

  • source (String)
  • width: (Integer) (defaults to: 72)
  • max_rows: (Integer) (defaults to: 18)
  • markup: (Boolean) (defaults to: true)

Raises:

  • (ArgumentError)


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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/canopus/markdown.rb', line 13

def initialize(source, width: 72, max_rows: 18, markup: true)
  raise ArgumentError, "invalid hover dimensions" unless width.is_a?(Integer) && width.positive? && max_rows.is_a?(Integer) && max_rows.positive?
  source = source.to_s.byteslice(0, 65_536).to_s.encode(Encoding::UTF_8, invalid: :replace, undef: :replace).scrub
  tokens = markup ? Rouge::Lexer.find("markdown").new.lex(source).map { |token, value| [token.qualname, value] } : [["Text", source]]
  @rows, column, index = [[]], 0, 0
  while index < tokens.length && @rows.length <= max_rows
    kind, value = tokens[index]
    style, url = :text, nil
    if markup && value == "[" && tokens[index + 1]&.first == "Name.Variable" && tokens[index + 2]&.last == "](" && tokens[index + 3]&.first == "Literal.String.Other" && tokens[index + 4]&.last == ")"
      value, url, style = tokens[index + 1].last, tokens[index + 3].last, :link
      index += 4
    elsif markup
      case kind
      when "Generic.Heading", "Generic.Subheading"
        value, style = value.sub(/\A\#{1,6}\s*/, ""), :heading
      when "Generic.Strong" then value, style = value[2...-2], :strong
      when "Generic.Emph" then value, style = value[1...-1], :emphasis
      when "Literal.String.Backtick" then value, style = value.sub(/\A`+/, "").sub(/`+\z/, ""), :code
      when "Generic.Traceback" then value, style = value.sub(/\A>\s?/, "│ "), :quote
      when "Punctuation"
        if value.match?(/\A(?:`{3,}|~{3,})\z/)
          index += 1 if tokens[index + 1]&.first == "Name.Label"
          value = ""
        end
      else
        style = kind unless kind == "Text"
      end
    end
    value.to_s.each_grapheme_cluster do |grapheme|
      if grapheme.include?("\n")
        @rows << []
        column = 0
        break if @rows.length > max_rows
        next
      end
      cells = Unicode::DisplayWidth.of(grapheme, emoji: :rgi)
      if column.positive? && column + cells > width
        @rows << []
        column = 0
        break if @rows.length > max_rows
      end
      previous = @rows.last.last
      if previous && previous.style == style && previous.url == url
        previous.text << grapheme
      else
        @rows.last << Run.new(+grapheme, style, url)
      end
      column += cells
    end
    index += 1
  end
  @rows = @rows.first(max_rows)
  @rows.pop while @rows.length > 1 && @rows.last.empty?
  @rows.each { |row| row.each { |run| run.text.freeze }; row.freeze }.freeze
end

Instance Attribute Details

#rows ⇒ Array[Array[Run]] (readonly)

Returns the value of attribute rows.

Returns:

  • (Array[Array[Run]])


11
12
13
# File 'lib/canopus/markdown.rb', line 11

def rows
  @rows
end