1 #include "display_buffer.hh"
2
3 #include "assert.hh"
4 #include "buffer.hh"
5 #include "buffer_utils.hh"
6 #include "face_registry.hh"
7 #include "utf8.hh"
8
9 #include "face_registry.hh"
10
11 namespace Kakoune
12 {
13
get_iterator(const Buffer & buffer,BufferCoord coord)14 BufferIterator get_iterator(const Buffer& buffer, BufferCoord coord)
15 {
16 // Correct one past the end of line as next line
17 if (not buffer.is_end(coord) and coord.column == buffer[coord.line].length())
18 coord = coord.line+1;
19 return buffer.iterator_at(coord);
20 }
21
content() const22 StringView DisplayAtom::content() const
23 {
24 switch (m_type)
25 {
26 case Range:
27 {
28 auto line = (*m_buffer)[m_range.begin.line];
29 if (m_range.begin.line == m_range.end.line)
30 return line.substr(m_range.begin.column, m_range.end.column - m_range.begin.column);
31 else if (m_range.begin.line+1 == m_range.end.line and m_range.end.column == 0)
32 return line.substr(m_range.begin.column);
33 break;
34 }
35 case Text:
36 case ReplacedRange:
37 return m_text;
38 }
39 kak_assert(false);
40 return {};
41 }
42
length() const43 ColumnCount DisplayAtom::length() const
44 {
45 switch (m_type)
46 {
47 case Range:
48 return utf8::column_distance(get_iterator(*m_buffer, m_range.begin),
49 get_iterator(*m_buffer, m_range.end));
50 case Text:
51 case ReplacedRange:
52 return m_text.column_length();
53 }
54 kak_assert(false);
55 return 0;
56 }
57
trim_begin(ColumnCount count)58 void DisplayAtom::trim_begin(ColumnCount count)
59 {
60 if (m_type == Range)
61 m_range.begin = utf8::advance(get_iterator(*m_buffer, m_range.begin),
62 get_iterator(*m_buffer, m_range.end),
63 count).coord();
64 else
65 m_text = m_text.substr(count).str();
66 }
67
trim_end(ColumnCount count)68 void DisplayAtom::trim_end(ColumnCount count)
69 {
70 if (m_type == Range)
71 m_range.end = utf8::advance(get_iterator(*m_buffer, m_range.end),
72 get_iterator(*m_buffer, m_range.begin),
73 -count).coord();
74 else
75 m_text = m_text.substr(0, m_text.column_length() - count).str();
76 }
77
DisplayLine(AtomList atoms)78 DisplayLine::DisplayLine(AtomList atoms)
79 : m_atoms(std::move(atoms))
80 {
81 compute_range();
82 }
83
split(iterator it,BufferCoord pos)84 DisplayLine::iterator DisplayLine::split(iterator it, BufferCoord pos)
85 {
86 kak_assert(it->type() == DisplayAtom::Range);
87 kak_assert(it->begin() < pos);
88 kak_assert(it->end() > pos);
89
90 DisplayAtom atom = *it;
91 atom.m_range.end = pos;
92 it->m_range.begin = pos;
93 return m_atoms.insert(it, std::move(atom));
94 }
95
split(iterator it,ColumnCount count)96 DisplayLine::iterator DisplayLine::split(iterator it, ColumnCount count)
97 {
98 kak_assert(count > 0);
99 kak_assert(count < it->length());
100
101 if (it->type() == DisplayAtom::Text or it->type() == DisplayAtom::ReplacedRange)
102 {
103 DisplayAtom atom = *it;
104 atom.m_text = atom.m_text.substr(0, count).str();
105 it->m_text = it->m_text.substr(count).str();
106 return m_atoms.insert(it, std::move(atom));
107 }
108 auto pos = utf8::advance(get_iterator(it->buffer(), it->begin()),
109 get_iterator(it->buffer(), it->end()),
110 count).coord();
111 return split(it, pos);
112 }
113
split(BufferCoord pos)114 DisplayLine::iterator DisplayLine::split(BufferCoord pos)
115 {
116 auto it = find_if(begin(), end(), [pos](const DisplayAtom& a) {
117 return (a.has_buffer_range() && a.begin() >= pos) ||
118 (a.type() == DisplayAtom::Range and a.end() > pos);
119 });
120 if (it == end() or it->begin() >= pos)
121 return it;
122 return ++split(it, pos);
123 }
124
insert(iterator it,DisplayAtom atom)125 DisplayLine::iterator DisplayLine::insert(iterator it, DisplayAtom atom)
126 {
127 if (atom.has_buffer_range())
128 {
129 m_range.begin = std::min(m_range.begin, atom.begin());
130 m_range.end = std::max(m_range.end, atom.end());
131 }
132 auto res = m_atoms.insert(it, std::move(atom));
133 compute_range();
134 return res;
135 }
136
push_back(DisplayAtom atom)137 void DisplayLine::push_back(DisplayAtom atom)
138 {
139 if (atom.has_buffer_range())
140 {
141 m_range.begin = std::min(m_range.begin, atom.begin());
142 m_range.end = std::max(m_range.end, atom.end());
143 }
144 m_atoms.push_back(std::move(atom));
145 }
146
erase(iterator beg,iterator end)147 DisplayLine::iterator DisplayLine::erase(iterator beg, iterator end)
148 {
149 auto res = m_atoms.erase(beg, end);
150 compute_range();
151 return res;
152 }
153
optimize()154 void DisplayLine::optimize()
155 {
156 if (m_atoms.empty())
157 return;
158
159 auto atom_it = m_atoms.begin();
160 for (auto next_it = atom_it + 1; next_it != m_atoms.end(); ++next_it)
161 {
162 auto& atom = *atom_it;
163 auto& next = *next_it;
164
165 const auto type = atom.type();
166 if (type == next.type() and atom.face == next.face)
167 {
168 if (type == DisplayAtom::Text)
169 atom.m_text += next.m_text;
170 else if ((type == DisplayAtom::Range or
171 type == DisplayAtom::ReplacedRange) and
172 next.begin() == atom.end())
173 {
174 atom.m_range.end = next.end();
175 if (type == DisplayAtom::ReplacedRange)
176 atom.m_text += next.m_text;
177 }
178 else
179 *++atom_it = std::move(*next_it);
180 }
181 else
182 *++atom_it = std::move(*next_it);
183 }
184 m_atoms.erase(atom_it+1, m_atoms.end());
185 }
186
length() const187 ColumnCount DisplayLine::length() const
188 {
189 ColumnCount len = 0;
190 for (auto& atom : m_atoms)
191 len += atom.length();
192 return len;
193 }
194
trim(ColumnCount first_col,ColumnCount col_count)195 bool DisplayLine::trim(ColumnCount first_col, ColumnCount col_count)
196 {
197 for (auto it = begin(); first_col > 0 and it != end(); )
198 {
199 auto len = it->length();
200 if (len <= first_col)
201 {
202 m_atoms.erase(it);
203 first_col -= len;
204 }
205 else
206 {
207 it->trim_begin(first_col);
208 first_col = 0;
209 }
210 }
211 auto it = begin();
212 for (; it != end() and col_count > 0; ++it)
213 col_count -= it->length();
214
215 bool did_trim = it != end() || col_count < 0;
216 if (col_count < 0)
217 (it-1)->trim_end(-col_count);
218 m_atoms.erase(it, end());
219
220 compute_range();
221 return did_trim;
222 }
223
224 const BufferRange init_range{ {INT_MAX, INT_MAX}, {INT_MIN, INT_MIN} };
225
compute_range()226 void DisplayLine::compute_range()
227 {
228 m_range = init_range;
229 for (auto& atom : m_atoms)
230 {
231 if (not atom.has_buffer_range())
232 continue;
233 m_range.begin = std::min(m_range.begin, atom.begin());
234 m_range.end = std::max(m_range.end, atom.end());
235 }
236 if (m_range == init_range)
237 m_range = { { 0, 0 }, { 0, 0 } };
238 kak_assert(m_range.begin <= m_range.end);
239 }
240
compute_range()241 void DisplayBuffer::compute_range()
242 {
243 m_range = init_range;
244 for (auto& line : m_lines)
245 {
246 m_range.begin = std::min(line.range().begin, m_range.begin);
247 m_range.end = std::max(line.range().end, m_range.end);
248 }
249 if (m_range == init_range)
250 m_range = { { 0, 0 }, { 0, 0 } };
251 kak_assert(m_range.begin <= m_range.end);
252 }
253
optimize()254 void DisplayBuffer::optimize()
255 {
256 for (auto& line : m_lines)
257 line.optimize();
258 }
259
parse_display_line(StringView line,Face & face,const FaceRegistry & faces,const HashMap<String,DisplayLine> & builtins)260 DisplayLine parse_display_line(StringView line, Face& face, const FaceRegistry& faces, const HashMap<String, DisplayLine>& builtins)
261 {
262 DisplayLine res;
263 bool was_antislash = false;
264 auto pos = line.begin();
265 String content;
266 for (auto it = line.begin(), end = line.end(); it != end; ++it)
267 {
268 const char c = *it;
269 if (c == '{')
270 {
271 if (was_antislash)
272 {
273 content += StringView{pos, it};
274 content.back() = '{';
275 pos = it + 1;
276 }
277 else
278 {
279 content += StringView{pos, it};
280 if (not content.empty())
281 res.push_back({std::move(content), face});
282 content.clear();
283 auto closing = std::find(it+1, end, '}');
284 if (closing == end)
285 throw runtime_error("unclosed face definition");
286 if (*(it+1) == '{' and closing+1 != end and *(closing+1) == '}')
287 {
288 auto builtin_it = builtins.find(StringView{it+2, closing});
289 if (builtin_it == builtins.end())
290 throw runtime_error(format("undefined atom {}", StringView{it+2, closing}));
291 for (auto& atom : builtin_it->value)
292 res.push_back(atom);
293 // closing is now at the first char of "}}", advance it to the second
294 ++closing;
295 }
296 else if (closing == it+2 and *(it+1) == '\\')
297 {
298 pos = closing + 1;
299 break;
300 }
301 else
302 face = faces[{it+1, closing}];
303 it = closing;
304 pos = closing + 1;
305 }
306 }
307 if (c == '\n' or c == '\t') // line breaks and tabs are forbidden, replace with space
308 {
309 content += StringView{pos, it+1};
310 content.back() = ' ';
311 pos = it + 1;
312 }
313
314 if (c == '\\')
315 {
316 if (was_antislash)
317 {
318 content += StringView{pos, it};
319 pos = it + 1;
320 was_antislash = false;
321 }
322 else
323 was_antislash = true;
324 }
325 else
326 was_antislash = false;
327 }
328 content += StringView{pos, line.end()};
329 if (not content.empty())
330 res.push_back({std::move(content), face});
331 return res;
332 }
333
parse_display_line(StringView line,const FaceRegistry & faces,const HashMap<String,DisplayLine> & builtins)334 DisplayLine parse_display_line(StringView line, const FaceRegistry& faces, const HashMap<String, DisplayLine>& builtins)
335 {
336 Face face{};
337 return parse_display_line(line, face, faces, builtins);
338 }
339
parse_display_line_list(StringView content,const FaceRegistry & faces,const HashMap<String,DisplayLine> & builtins)340 DisplayLineList parse_display_line_list(StringView content, const FaceRegistry& faces, const HashMap<String, DisplayLine>& builtins)
341 {
342 return content | split<StringView>('\n')
343 | transform([&, face=Face{}](StringView s) mutable {
344 return parse_display_line(s, face, faces, builtins);
345 })
346 | gather<DisplayLineList>();
347 }
348
349 }
350