|
19 | 19 |
|
20 | 20 | module MoonScript |
21 | 21 | module TerminalSnippet |
22 | | - record Line, contents: String, index: Int64, offset: Int64 do |
| 22 | + record Line, contents : String, index : Int64, offset : Int64 do |
23 | 23 | def contains?(position) |
24 | 24 | position >= offset && position <= (offset + size) |
25 | 25 | end |
26 | 26 |
|
27 | | - def fully_contains?(form, to) |
| 27 | + def fully_contains?(from, to) |
28 | 28 | contains?(from, to) && (to - from) < contents.size |
29 | 29 | end |
30 | 30 |
|
31 | 31 | def contains?(from, to) |
32 | | - diff_from = from - offset |
| 32 | + diff_from = |
| 33 | + from - offset |
33 | 34 |
|
34 | | - diff_to = to - offset |
| 35 | + diff_to = |
| 36 | + to - offset |
35 | 37 |
|
36 | 38 | diff_to > 0 && diff_from < contents.size |
37 | 39 | end |
38 | 40 |
|
39 | 41 | def size |
40 | 42 | contents.size |
41 | 43 | end |
| 44 | + |
| 45 | + def highlight(from, to) |
| 46 | + diff_from = |
| 47 | + {from - offset, 0}.max |
| 48 | + |
| 49 | + diff_to = |
| 50 | + {[to - offset, contents.size].min, 0}.max |
| 51 | + |
| 52 | + left = |
| 53 | + self[0, diff_from] |
| 54 | + |
| 55 | + center = |
| 56 | + self[diff_from, diff_to] |
| 57 | + |
| 58 | + right = |
| 59 | + self[diff_to, contents.size] |
| 60 | + |
| 61 | + highlighted = |
| 62 | + left + center.colorize.on(:white).fore(:red).to_s + right |
| 63 | + |
| 64 | + arrows = |
| 65 | + (" " * left.size) + ("⌃" * center.size) |
| 66 | + |
| 67 | + {highlighted, arrows} |
| 68 | + end |
| 69 | + |
| 70 | + def [](from, to) |
| 71 | + contents[from, to - from] |
| 72 | + rescue IndexError | ArgumentError |
| 73 | + "" |
| 74 | + end |
| 75 | + end |
| 76 | + |
| 77 | + extend self |
| 78 | + |
| 79 | + def render(input : String, filename : String, from : Int64, to : Int64, padding = 4, width = 80) |
| 80 | + path = |
| 81 | + if (full = Path[filename]).absolute? |
| 82 | + if full.expand.to_s.starts_with?(Dir.current.to_s) |
| 83 | + full.relative_to?(Dir.current).try(&.to_s) |
| 84 | + end |
| 85 | + end || filename |
| 86 | + |
| 87 | + # Transform each line into a record for further use. |
| 88 | + lines = |
| 89 | + input.lines.reduce({[] of Line, 0}) do |memo, raw| |
| 90 | + items, index = memo |
| 91 | + |
| 92 | + offset = |
| 93 | + items.reduce(0) { |acc, item| acc + item.contents.size + 1 } |
| 94 | + |
| 95 | + line = |
| 96 | + Line.new(contents: raw, index: index, offset: offset) |
| 97 | + |
| 98 | + {items + [line], index + 1} |
| 99 | + end[0] |
| 100 | + |
| 101 | + selected = |
| 102 | + lines.select(&.contains?(from, to)) |
| 103 | + |
| 104 | + fully_highlighted = |
| 105 | + selected.size == lines.size |
| 106 | + |
| 107 | + # Get the first line which is the one we want to highlight minus the padding. |
| 108 | + start_line = |
| 109 | + {0, (lines.find(&.contains?(from)).try(&.index) || 0) - padding}.max |
| 110 | + |
| 111 | + # Get the last line which is the one we want to highlight plus the padding. |
| 112 | + end_line = |
| 113 | + {(lines.find(&.contains?(to)).try(&.index) || 0) + padding + 1, lines.size}.min |
| 114 | + |
| 115 | + # We need to calucluate the width of the gutter so we can pad later lon. |
| 116 | + gutter_width = { |
| 117 | + (start_line + 1).to_s.size, |
| 118 | + (end_line + 1).to_s.size, |
| 119 | + }.max |
| 120 | + |
| 121 | + relevant_lines = |
| 122 | + lines[start_line, end_line - start_line] |
| 123 | + |
| 124 | + result = |
| 125 | + relevant_lines.map do |line| |
| 126 | + line_number = |
| 127 | + (line.index + 1).to_s.rjust(gutter_width) |
| 128 | + |
| 129 | + highlighted = |
| 130 | + line.fully_contains?(from, to) |
| 131 | + |
| 132 | + gutter = |
| 133 | + if highlighted |
| 134 | + "#{line_number}│".colorize(:light_yellow).mode(:bright) |
| 135 | + else |
| 136 | + "#{line_number}│".colorize.mode(:dim) |
| 137 | + end |
| 138 | + |
| 139 | + if fully_highlighted |
| 140 | + elsif selected.size > 1 && selected.last == line |
| 141 | + max_size = |
| 142 | + selected.max_of(&.contents.size) |
| 143 | + |
| 144 | + whitespace = |
| 145 | + selected.min_of do |item| |
| 146 | + count = 0 |
| 147 | + item.contents.each_char do |char| |
| 148 | + break unless char.ascii_whitespace? |
| 149 | + count += 1 |
| 150 | + end || 0 |
| 151 | + count |
| 152 | + end |
| 153 | + |
| 154 | + content_size = |
| 155 | + max_size - whitespace |
| 156 | + |
| 157 | + arrows = |
| 158 | + "#{" " * whitespace}#{"⌃"*content_size}" |
| 159 | + |
| 160 | + "#{gutter} #{line.contents}\n#{" " * gutter_width}│ #{arrows}" |
| 161 | + elsif selected.size > 1 && selected.first == line |
| 162 | + max_size = |
| 163 | + selected.max_of(&.contents.size) |
| 164 | + |
| 165 | + whitespace = |
| 166 | + selected.min_of do |item| |
| 167 | + count = 0 |
| 168 | + item.contents.each_char do |char| |
| 169 | + break unless char.ascii_whitespace? |
| 170 | + count += 1 |
| 171 | + end || 0 |
| 172 | + count |
| 173 | + end |
| 174 | + |
| 175 | + content_size = |
| 176 | + max_size - whitespace |
| 177 | + |
| 178 | + arrows = |
| 179 | + "#{" " * whitespace}#{"⌄"*content_size}" |
| 180 | + |
| 181 | + "#{" " * gutter_width}│ #{arrows}\n#{gutter} #{line.contents}" |
| 182 | + elsif highlighted |
| 183 | + a, b = |
| 184 | + line.highlight(from, to) |
| 185 | + |
| 186 | + "#{gutter} #{a}\n#{" " * gutter_width}│ #{b}" |
| 187 | + elsif (from == input.size && line.offset + line.contents.size == from) || |
| 188 | + (line.offset + line.contents.size == (to - 1)) |
| 189 | + "#{gutter} #{line.contents}\n#{" " * gutter_width}│ #{" " * line.contents.size}⌃⌃⌃⌃" |
| 190 | + end || "#{gutter} #{line.contents}" |
| 191 | + end |
| 192 | + |
| 193 | + line = |
| 194 | + input[0..from].lines.size |
| 195 | + |
| 196 | + column = |
| 197 | + input[0..from].lines.last.size |
| 198 | + |
| 199 | + title = |
| 200 | + "#{path}:#{line}:#{column}" |
| 201 | + |
| 202 | + gutter_divider = |
| 203 | + " " * gutter_width |
| 204 | + |
| 205 | + header_start = |
| 206 | + "#{gutter_divider}┌ ".colorize.mode(:dim) |
| 207 | + |
| 208 | + title_colorized = |
| 209 | + title.colorize.mode(:bold) |
| 210 | + |
| 211 | + header = |
| 212 | + "#{header_start}#{title_colorized}" |
| 213 | + |
| 214 | + header_divider_width = |
| 215 | + [ |
| 216 | + header.uncolorize.size - (gutter_width + 1), |
| 217 | + relevant_lines.max_of(&.size) + 1, |
| 218 | + ].max |
| 219 | + |
| 220 | + header_divider = |
| 221 | + "#{gutter_divider}├" + ("─" * header_divider_width) |
| 222 | + |
| 223 | + result = |
| 224 | + result.join('\n') |
| 225 | + |
| 226 | + "#{header}\n#{header_divider}\n#{result}" |
42 | 227 | end |
43 | 228 | end |
44 | 229 | end |
0 commit comments