Skip to content

Commit 78962c6

Browse files
committed
utils: terminal snippet
1 parent f849944 commit 78962c6

File tree

1 file changed

+189
-4
lines changed

1 file changed

+189
-4
lines changed

src/utils/terminal_snippet.cr

Lines changed: 189 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,211 @@
1919

2020
module MoonScript
2121
module TerminalSnippet
22-
record Line, contents: String, index: Int64, offset: Int64 do
22+
record Line, contents : String, index : Int64, offset : Int64 do
2323
def contains?(position)
2424
position >= offset && position <= (offset + size)
2525
end
2626

27-
def fully_contains?(form, to)
27+
def fully_contains?(from, to)
2828
contains?(from, to) && (to - from) < contents.size
2929
end
3030

3131
def contains?(from, to)
32-
diff_from = from - offset
32+
diff_from =
33+
from - offset
3334

34-
diff_to = to - offset
35+
diff_to =
36+
to - offset
3537

3638
diff_to > 0 && diff_from < contents.size
3739
end
3840

3941
def size
4042
contents.size
4143
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}"
42227
end
43228
end
44229
end

0 commit comments

Comments
 (0)