1 #include "window.hh"
2 
3 #include "assert.hh"
4 #include "clock.hh"
5 #include "context.hh"
6 #include "highlighter.hh"
7 #include "hook_manager.hh"
8 #include "input_handler.hh"
9 #include "client.hh"
10 #include "buffer_utils.hh"
11 #include "option.hh"
12 #include "option_types.hh"
13 
14 #include <algorithm>
15 
16 namespace Kakoune
17 {
18 
19 // Implementation in highlighters.cc
20 void setup_builtin_highlighters(HighlighterGroup& group);
21 
Window(Buffer & buffer)22 Window::Window(Buffer& buffer)
23     : Scope(buffer),
24       m_buffer(&buffer),
25       m_builtin_highlighters{highlighters()}
26 {
27     run_hook_in_own_context(Hook::WinCreate, buffer.name());
28 
29     options().register_watcher(*this);
30 
31     setup_builtin_highlighters(m_builtin_highlighters.group());
32 
33     // gather as on_option_changed can mutate the option managers
34     for (auto& option : options().flatten_options()
__anon6ce7e1880102(auto& ptr) 35                       | transform([](auto& ptr) { return ptr.get(); })
36                       | gather<Vector<const Option*>>())
37         on_option_changed(*option);
38 }
39 
~Window()40 Window::~Window()
41 {
42     options().unregister_watcher(*this);
43 }
44 
scroll(LineCount offset)45 void Window::scroll(LineCount offset)
46 {
47     m_position.line = std::max(0_line, m_position.line + offset);
48 }
49 
display_line_at(LineCount buffer_line,LineCount display_line)50 void Window::display_line_at(LineCount buffer_line, LineCount display_line)
51 {
52     if (display_line >= 0 or display_line < m_dimensions.line)
53         m_position.line = std::max(0_line, buffer_line - display_line);
54 }
55 
center_line(LineCount buffer_line)56 void Window::center_line(LineCount buffer_line)
57 {
58     display_line_at(buffer_line, m_dimensions.line/2_line);
59 }
60 
scroll(ColumnCount offset)61 void Window::scroll(ColumnCount offset)
62 {
63     m_position.column = std::max(0_col, m_position.column + offset);
64 }
65 
display_column_at(ColumnCount buffer_column,ColumnCount display_column)66 void Window::display_column_at(ColumnCount buffer_column, ColumnCount display_column)
67 {
68     if (display_column >= 0 or display_column < m_dimensions.column)
69         m_position.column = std::max(0_col, buffer_column - display_column);
70 }
71 
center_column(ColumnCount buffer_column)72 void Window::center_column(ColumnCount buffer_column)
73 {
74     display_column_at(buffer_column, m_dimensions.column/2_col);
75 }
76 
compute_faces_hash(const FaceRegistry & faces)77 static uint32_t compute_faces_hash(const FaceRegistry& faces)
78 {
79     uint32_t hash = 0;
80     for (auto&& face : faces.flatten_faces() | transform(&FaceRegistry::FaceMap::Item::value))
81         hash = combine_hash(hash, face.base.empty() ? hash_value(face.face) : hash_value(face.base));
82     return hash;
83 }
84 
build_setup(const Context & context) const85 Window::Setup Window::build_setup(const Context& context) const
86 {
87     Vector<BufferRange, MemoryDomain::Display> selections;
88     for (auto& sel : context.selections())
89         selections.push_back({sel.cursor(), sel.anchor()});
90 
91     return { m_position, m_dimensions,
92              context.buffer().timestamp(),
93              compute_faces_hash(context.faces()),
94              context.selections().main_index(),
95              std::move(selections) };
96 }
97 
needs_redraw(const Context & context) const98 bool Window::needs_redraw(const Context& context) const
99 {
100     auto& selections = context.selections();
101 
102     if (m_position != m_last_setup.position or
103         m_dimensions != m_last_setup.dimensions or
104         context.buffer().timestamp() != m_last_setup.timestamp or
105         selections.main_index() != m_last_setup.main_selection or
106         selections.size() != m_last_setup.selections.size() or
107         compute_faces_hash(context.faces()) != m_last_setup.faces_hash)
108         return true;
109 
110     for (int i = 0; i < selections.size(); ++i)
111     {
112         if (selections[i].cursor() != m_last_setup.selections[i].begin or
113             selections[i].anchor() != m_last_setup.selections[i].end)
114             return true;
115     }
116 
117     return false;
118 }
119 
update_display_buffer(const Context & context)120 const DisplayBuffer& Window::update_display_buffer(const Context& context)
121 {
122     const bool profile = context.options()["debug"].get<DebugFlags>() &
123                         DebugFlags::Profile;
124 
125     auto start_time = profile ? Clock::now() : Clock::time_point{};
126 
127     if (m_display_buffer.timestamp() != -1)
128     {
129         for (auto&& change : buffer().changes_since(m_display_buffer.timestamp()))
130         {
131             if (change.type == Buffer::Change::Insert and change.begin.line < m_position.line)
132                 m_position.line += change.end.line - change.begin.line;
133             if (change.type == Buffer::Change::Erase and change.begin.line < m_position.line)
134                 m_position.line = std::max(m_position.line - (change.end.line - change.begin.line), change.begin.line);
135         }
136     }
137 
138     DisplayLineList& lines = m_display_buffer.lines();
139     m_display_buffer.set_timestamp(buffer().timestamp());
140     lines.clear();
141 
142     if (m_dimensions == DisplayCoord{0,0})
143         return m_display_buffer;
144 
145     kak_assert(&buffer() == &context.buffer());
146     const DisplaySetup setup = compute_display_setup(context);
147 
148     const int tabstop = context.options()["tabstop"].get<int>();
149     for (LineCount line = 0; line < setup.window_range.line; ++line)
150     {
151         LineCount buffer_line = setup.window_pos.line + line;
152         if (buffer_line >= buffer().line_count())
153             break;
154         auto beg_byte = get_byte_to_column(buffer(), tabstop, {buffer_line, setup.window_pos.column});
155         auto end_byte = setup.full_lines ?
156             buffer()[buffer_line].length()
157           : get_byte_to_column(buffer(), tabstop, {buffer_line, setup.window_pos.column + setup.window_range.column});
158 
159         // The display buffer always has at least one buffer atom, which might be empty if
160         // beg_byte == end_byte
161         lines.emplace_back(AtomList{ {buffer(), {buffer_line, beg_byte}, {buffer_line, end_byte}} });
162     }
163 
164     m_display_buffer.compute_range();
165     const BufferRange range{{0,0}, buffer().end_coord()};
166     for (auto pass : { HighlightPass::Wrap, HighlightPass::Move, HighlightPass::Colorize })
167         m_builtin_highlighters.highlight({context, setup, pass, {}}, m_display_buffer, range);
168 
169     m_display_buffer.optimize();
170 
171     set_position(setup.window_pos);
172     m_last_setup = build_setup(context);
173 
174     if (profile and not (buffer().flags() & Buffer::Flags::Debug))
175     {
176         using namespace std::chrono;
177         auto duration = duration_cast<microseconds>(Clock::now() - start_time);
178         write_to_debug_buffer(format("window display update for '{}' took {} us",
179                                      buffer().display_name(), (size_t)duration.count()));
180     }
181 
182     return m_display_buffer;
183 }
184 
set_position(DisplayCoord position)185 void Window::set_position(DisplayCoord position)
186 {
187     m_position.line = clamp(position.line, 0_line, buffer().line_count()-1);
188     m_position.column = std::max(0_col, position.column);
189 }
190 
set_dimensions(DisplayCoord dimensions)191 void Window::set_dimensions(DisplayCoord dimensions)
192 {
193     if (m_dimensions != dimensions)
194     {
195         m_dimensions = dimensions;
196         m_resize_hook_pending = true;
197     }
198 }
199 
run_resize_hook_ifn()200 void Window::run_resize_hook_ifn()
201 {
202     if (m_resize_hook_pending)
203     {
204         m_resize_hook_pending = false;
205         run_hook_in_own_context(Hook::WinResize,
206                                 format("{}.{}", m_dimensions.line, m_dimensions.column));
207     }
208 }
209 
check_display_setup(const DisplaySetup & setup,const Window & window)210 static void check_display_setup(const DisplaySetup& setup, const Window& window)
211 {
212     kak_assert(setup.window_pos.line >= 0 and setup.window_pos.line < window.buffer().line_count());
213     kak_assert(setup.window_pos.column >= 0);
214     kak_assert(setup.window_range.column >= 0);
215     kak_assert(setup.window_range.line >= 0);
216 }
217 
compute_display_setup(const Context & context) const218 DisplaySetup Window::compute_display_setup(const Context& context) const
219 {
220     auto win_pos = m_position;
221 
222     DisplayCoord offset = options()["scrolloff"].get<DisplayCoord>();
223     offset.line = std::min(offset.line, (m_dimensions.line + 1) / 2);
224     offset.column = std::min(offset.column, (m_dimensions.column + 1) / 2);
225 
226     const int tabstop = context.options()["tabstop"].get<int>();
227     const auto& cursor = context.selections().main().cursor();
228 
229     // Ensure cursor line is visible
230     if (cursor.line - offset.line < win_pos.line)
231         win_pos.line = std::max(0_line, cursor.line - offset.line);
232     if (cursor.line + offset.line >= win_pos.line + m_dimensions.line)
233         win_pos.line = std::min(buffer().line_count()-1, cursor.line + offset.line - m_dimensions.line + 1);
234 
235     DisplaySetup setup{
236         win_pos,
237         m_dimensions,
238         {cursor.line - win_pos.line,
239          get_column(buffer(), tabstop, cursor) - win_pos.column},
240         offset,
241         false
242     };
243     for (auto pass : { HighlightPass::Move, HighlightPass::Wrap })
244         m_builtin_highlighters.compute_display_setup({context, setup, pass, {}}, setup);
245     check_display_setup(setup, *this);
246 
247     // now ensure the cursor column is visible
248     {
249         auto underflow = std::max(-setup.window_pos.column,
250                                   setup.cursor_pos.column - setup.scroll_offset.column);
251         if (underflow < 0)
252         {
253             setup.window_pos.column += underflow;
254             setup.cursor_pos.column -= underflow;
255         }
256         auto overflow = setup.cursor_pos.column + setup.scroll_offset.column - setup.window_range.column + 1;
257         if (overflow > 0)
258         {
259             setup.window_pos.column += overflow;
260             setup.cursor_pos.column -= overflow;
261         }
262         check_display_setup(setup, *this);
263     }
264 
265     return setup;
266 }
267 
268 namespace
269 {
find_display_column(const DisplayLine & line,const Buffer & buffer,BufferCoord coord)270 ColumnCount find_display_column(const DisplayLine& line, const Buffer& buffer,
271                                 BufferCoord coord)
272 {
273     ColumnCount column = 0;
274     for (auto& atom : line)
275     {
276         if (atom.has_buffer_range() and
277             coord >= atom.begin() and coord < atom.end())
278         {
279             if (atom.type() == DisplayAtom::Range)
280                 column += utf8::column_distance(get_iterator(buffer, atom.begin()),
281                                                 get_iterator(buffer, coord));
282             return column;
283         }
284         column += atom.length();
285     }
286     return column;
287 }
288 
find_buffer_coord(const DisplayLine & line,const Buffer & buffer,ColumnCount column)289 BufferCoord find_buffer_coord(const DisplayLine& line, const Buffer& buffer,
290                               ColumnCount column)
291 {
292     const auto& range = line.range();
293     for (auto& atom : line)
294     {
295         ColumnCount len = atom.length();
296         if (atom.has_buffer_range() and column < len)
297         {
298             if (atom.type() == DisplayAtom::Range)
299                 return buffer.clamp(
300                     utf8::advance(get_iterator(buffer, atom.begin()),
301                                   get_iterator(buffer, range.end),
302                                   std::max(0_col, column)).coord());
303              return buffer.clamp(atom.begin());
304         }
305         column -= len;
306     }
307     return range.end == BufferCoord{0,0} ?
308         range.end : buffer.prev(range.end);
309 }
310 }
311 
display_position(BufferCoord coord) const312 Optional<DisplayCoord> Window::display_position(BufferCoord coord) const
313 {
314     if (m_display_buffer.timestamp() != buffer().timestamp())
315         return {};
316 
317     LineCount l = 0;
318     for (auto& line : m_display_buffer.lines())
319     {
320         auto& range = line.range();
321         if (range.begin <= coord and coord < range.end)
322             return DisplayCoord{l, find_display_column(line, buffer(), coord)};
323         ++l;
324     }
325     return {};
326 }
327 
buffer_coord(DisplayCoord coord) const328 BufferCoord Window::buffer_coord(DisplayCoord coord) const
329 {
330     if (m_display_buffer.timestamp() != buffer().timestamp() or
331         m_display_buffer.lines().empty())
332         return {0, 0};
333     if (coord <= 0_line)
334         coord = {0,0};
335     if ((size_t)coord.line >= m_display_buffer.lines().size())
336         coord = DisplayCoord{(int)m_display_buffer.lines().size()-1, INT_MAX};
337 
338     return find_buffer_coord(m_display_buffer.lines()[(int)coord.line],
339                              buffer(), coord.column);
340 }
341 
clear_display_buffer()342 void Window::clear_display_buffer()
343 {
344     m_display_buffer = DisplayBuffer{};
345 }
346 
on_option_changed(const Option & option)347 void Window::on_option_changed(const Option& option)
348 {
349     run_hook_in_own_context(Hook::WinSetOption, format("{}={}", option.name(), option.get_desc_string()));
350     // a highlighter might depend on the option, so we need to redraw
351     force_redraw();
352 }
353 
354 
run_hook_in_own_context(Hook hook,StringView param,String client_name)355 void Window::run_hook_in_own_context(Hook hook, StringView param, String client_name)
356 {
357     if (m_buffer->flags() & Buffer::Flags::NoHooks)
358         return;
359 
360     InputHandler hook_handler{{ *m_buffer, Selection{} },
361                               Context::Flags::Draft,
362                               std::move(client_name)};
363     hook_handler.context().set_window(*this);
364     if (m_client)
365         hook_handler.context().set_client(*m_client);
366 
367     hooks().run_hook(hook, param, hook_handler.context());
368 }
369 }
370