1 #include "client.hh"
2 
3 #include "face_registry.hh"
4 #include "context.hh"
5 #include "buffer_manager.hh"
6 #include "buffer_utils.hh"
7 #include "file.hh"
8 #include "remote.hh"
9 #include "option.hh"
10 #include "option_types.hh"
11 #include "client_manager.hh"
12 #include "command_manager.hh"
13 #include "event_manager.hh"
14 #include "user_interface.hh"
15 #include "window.hh"
16 #include "hash_map.hh"
17 
18 #include <csignal>
19 #include <unistd.h>
20 
21 #include <utility>
22 
23 namespace Kakoune
24 {
25 
Client(std::unique_ptr<UserInterface> && ui,std::unique_ptr<Window> && window,SelectionList selections,int pid,EnvVarMap env_vars,String name,OnExitCallback on_exit)26 Client::Client(std::unique_ptr<UserInterface>&& ui,
27                std::unique_ptr<Window>&& window,
28                SelectionList selections, int pid,
29                EnvVarMap env_vars,
30                String name,
31                OnExitCallback on_exit)
32     : m_ui{std::move(ui)}, m_window{std::move(window)},
33       m_pid{pid},
34       m_on_exit{std::move(on_exit)},
35       m_env_vars(std::move(env_vars)),
36       m_input_handler{std::move(selections), Context::Flags::None,
37                       std::move(name)}
38 {
39     m_window->set_client(this);
40 
41     context().set_client(*this);
42     context().set_window(*m_window);
43 
44     m_window->set_dimensions(m_ui->dimensions());
45     m_window->options().register_watcher(*this);
46 
47     m_ui->set_ui_options(m_window->options()["ui_options"].get<UserInterface::Options>());
__anon9952c74f0102(Key key) 48     m_ui->set_on_key([this](Key key) {
49         if (key == ctrl('c'))
50         {
51             auto prev_handler = set_signal_handler(SIGINT, SIG_IGN);
52             killpg(getpgrp(), SIGINT);
53             set_signal_handler(SIGINT, prev_handler);
54         }
55         else if (key.modifiers & Key::Modifiers::Resize)
56         {
57             m_window->set_dimensions(key.coord());
58             force_redraw();
59         }
60         else
61             m_pending_keys.push_back(key);
62     });
63 
64     m_window->hooks().run_hook(Hook::WinDisplay, m_window->buffer().name(), context());
65 
66     force_redraw();
67 }
68 
~Client()69 Client::~Client()
70 {
71     m_window->options().unregister_watcher(*this);
72     m_window->set_client(nullptr);
73     // Do not move the selections here, as we need them to be valid
74     // in order to correctly destroy the input handler
75     ClientManager::instance().add_free_window(std::move(m_window),
76                                               context().selections());
77 }
78 
is_ui_ok() const79 bool Client::is_ui_ok() const
80 {
81     return m_ui->is_ok();
82 }
83 
process_pending_inputs()84 bool Client::process_pending_inputs()
85 {
86     const bool debug_keys = (bool)(context().options()["debug"].get<DebugFlags>() & DebugFlags::Keys);
87     m_window->run_resize_hook_ifn();
88     // steal keys as we might receive new keys while handling them.
89     Vector<Key, MemoryDomain::Client> keys = std::move(m_pending_keys);
90     for (auto& key : keys)
91     {
92         try
93         {
94             if (debug_keys)
95                 write_to_debug_buffer(format("Client '{}' got key '{}'",
96                                              context().name(), key_to_str(key)));
97 
98             if (key == Key::FocusIn)
99                 context().hooks().run_hook(Hook::FocusIn, context().name(), context());
100             else if (key == Key::FocusOut)
101                 context().hooks().run_hook(Hook::FocusOut, context().name(), context());
102             else
103                 m_input_handler.handle_key(key);
104 
105             context().hooks().run_hook(Hook::RawKey, key_to_str(key), context());
106         }
107         catch (Kakoune::runtime_error& error)
108         {
109             write_to_debug_buffer(format("Error: {}", error.what()));
110             context().print_status({error.what().str(), context().faces()["Error"] });
111             context().hooks().run_hook(Hook::RuntimeError, error.what(), context());
112         }
113     }
114     return not keys.empty();
115 }
116 
print_status(DisplayLine status_line)117 void Client::print_status(DisplayLine status_line)
118 {
119     m_status_line = std::move(status_line);
120     m_ui_pending |= StatusLine;
121 }
122 
123 
dimensions() const124 DisplayCoord Client::dimensions() const
125 {
126     return m_ui->dimensions();
127 }
128 
generate_context_info(const Context & context)129 String generate_context_info(const Context& context)
130 {
131     String s = "";
132     if (context.buffer().is_modified())
133         s += "[+]";
134     if (context.client().input_handler().is_recording())
135         s += format("[recording ({})]", context.client().input_handler().recording_reg());
136     if (context.hooks_disabled())
137         s += "[no-hooks]";
138     if (not(context.buffer().flags() & (Buffer::Flags::File | Buffer::Flags::Debug)))
139         s += "[scratch]";
140     if (context.buffer().flags() & Buffer::Flags::New)
141         s += "[new file]";
142     if (context.buffer().flags() & Buffer::Flags::Fifo)
143         s += "[fifo]";
144     if (context.buffer().flags() & Buffer::Flags::Debug)
145         s += "[debug]";
146     if (context.buffer().flags() & Buffer::Flags::ReadOnly)
147         s += "[readonly]";
148     return s;
149 }
150 
generate_mode_line() const151 DisplayLine Client::generate_mode_line() const
152 {
153     DisplayLine modeline;
154     try
155     {
156         const String& modelinefmt = context().options()["modelinefmt"].get<String>();
157         HashMap<String, DisplayLine> atoms{{ "mode_info", context().client().input_handler().mode_line() },
158                                            { "context_info", {generate_context_info(context()),
159                                                               context().faces()["Information"]}}};
160         auto expanded = expand(modelinefmt, context(), ShellContext{},
161                                [](String s) { return escape(s, '{', '\\'); });
162         modeline = parse_display_line(expanded, context().faces(), atoms);
163     }
164     catch (runtime_error& err)
165     {
166         write_to_debug_buffer(format("Error while parsing modelinefmt: {}", err.what()));
167         modeline.push_back({ "modelinefmt error, see *debug* buffer", context().faces()["Error"] });
168     }
169 
170     return modeline;
171 }
172 
change_buffer(Buffer & buffer)173 void Client::change_buffer(Buffer& buffer)
174 {
175     if (m_buffer_reload_dialog_opened)
176         close_buffer_reload_dialog();
177 
178     auto& client_manager = ClientManager::instance();
179     WindowAndSelections ws = client_manager.get_free_window(buffer);
180 
181     m_window->options().unregister_watcher(*this);
182     m_window->set_client(nullptr);
183     client_manager.add_free_window(std::move(m_window),
184                                    std::move(context().selections()));
185 
186     m_window = std::move(ws.window);
187     m_window->set_client(this);
188     m_window->options().register_watcher(*this);
189     context().selections_write_only() = std::move(ws.selections);
190     context().set_window(*m_window);
191 
192     m_window->set_dimensions(m_ui->dimensions());
193     m_ui->set_ui_options(m_window->options()["ui_options"].get<UserInterface::Options>());
194 
195     m_window->hooks().run_hook(Hook::WinDisplay, buffer.name(), context());
196     force_redraw();
197 }
198 
is_inline(InfoStyle style)199 static bool is_inline(InfoStyle style)
200 {
201     return style == InfoStyle::Inline or
202            style == InfoStyle::InlineAbove or
203            style == InfoStyle::InlineBelow;
204 }
205 
redraw_ifn()206 void Client::redraw_ifn()
207 {
208     Window& window = context().window();
209     if (window.needs_redraw(context()))
210         m_ui_pending |= Draw;
211 
212     DisplayLine mode_line = generate_mode_line();
213     if (mode_line.atoms() != m_mode_line.atoms())
214     {
215         m_ui_pending |= StatusLine;
216         m_mode_line = std::move(mode_line);
217     }
218 
219     if (m_ui_pending == 0)
220         return;
221 
222     const auto& faces = context().faces();
223 
224     if (m_ui_pending & Draw)
225         m_ui->draw(window.update_display_buffer(context()),
226                    faces["Default"], faces["BufferPadding"]);
227 
228     const bool update_menu_anchor = (m_ui_pending & Draw) and not (m_ui_pending & MenuHide) and
229                                     not m_menu.items.empty() and m_menu.style == MenuStyle::Inline;
230     if ((m_ui_pending & MenuShow) or update_menu_anchor)
231     {
232         auto anchor = m_menu.style == MenuStyle::Inline ?
233             window.display_position(m_menu.anchor) : DisplayCoord{};
234         if (not (m_ui_pending & MenuShow) and m_menu.ui_anchor != anchor)
235             m_ui_pending |= anchor ? (MenuShow | MenuSelect) : MenuHide;
236         m_menu.ui_anchor = anchor;
237     }
238 
239     if (m_ui_pending & MenuShow and m_menu.ui_anchor)
240         m_ui->menu_show(m_menu.items, *m_menu.ui_anchor,
241                         faces["MenuForeground"], faces["MenuBackground"],
242                         m_menu.style);
243     if (m_ui_pending & MenuSelect and m_menu.ui_anchor)
244         m_ui->menu_select(m_menu.selected);
245     if (m_ui_pending & MenuHide)
246         m_ui->menu_hide();
247 
248     const bool update_info_anchor = (m_ui_pending & Draw) and not (m_ui_pending & InfoHide) and
249                                     not m_info.content.empty() and is_inline(m_info.style);
250     if ((m_ui_pending & InfoShow) or update_info_anchor)
251     {
252         auto anchor = is_inline(m_info.style) ?
253              window.display_position(m_info.anchor) : DisplayCoord{};
254         if (not (m_ui_pending & MenuShow) and m_info.ui_anchor != anchor)
255             m_ui_pending |= anchor ? InfoShow : InfoHide;
256         m_info.ui_anchor = anchor;
257     }
258 
259     if (m_ui_pending & InfoShow and m_info.ui_anchor)
260         m_ui->info_show(m_info.title, m_info.content, *m_info.ui_anchor,
261                         faces["Information"], m_info.style);
262     if (m_ui_pending & InfoHide)
263         m_ui->info_hide();
264 
265     if (m_ui_pending & StatusLine)
266         m_ui->draw_status(m_status_line, m_mode_line, faces["StatusLine"]);
267 
268     auto cursor = m_input_handler.get_cursor_info();
269     m_ui->set_cursor(cursor.first, cursor.second);
270 
271     m_ui->refresh(m_ui_pending & Refresh);
272     m_ui_pending = 0;
273 }
274 
force_redraw()275 void Client::force_redraw()
276 {
277     m_ui_pending |= Refresh | Draw | StatusLine |
278         (m_menu.items.empty() ? MenuHide : MenuShow | MenuSelect) |
279         (m_info.content.empty() ? InfoHide : InfoShow);
280 }
281 
reload_buffer()282 void Client::reload_buffer()
283 {
284     Buffer& buffer = context().buffer();
285     try
286     {
287         reload_file_buffer(buffer);
288         context().print_status({ format("'{}' reloaded", buffer.display_name()),
289                                  context().faces()["Information"] });
290 
291         m_window->hooks().run_hook(Hook::BufReload, buffer.name(), context());
292     }
293     catch (runtime_error& error)
294     {
295         context().print_status({ format("error while reloading buffer: '{}'", error.what()),
296                                  context().faces()["Error"] });
297         buffer.set_fs_status(get_fs_status(buffer.name()));
298     }
299 }
300 
on_buffer_reload_key(Key key)301 void Client::on_buffer_reload_key(Key key)
302 {
303     auto& buffer = context().buffer();
304 
305     auto set_autoreload = [this](Autoreload autoreload) {
306         auto* option = &context().options()["autoreload"];
307         // Do not touch global autoreload, set it at least at buffer level
308         if (&option->manager() == &GlobalScope::instance().options())
309             option = &context().buffer().options().get_local_option("autoreload");
310         option->set(autoreload);
311     };
312 
313     if (key == 'y' or key == 'Y' or key == Key::Return)
314     {
315         reload_buffer();
316         if (key == 'Y')
317             set_autoreload(Autoreload::Yes);
318     }
319     else if (key == 'n' or key == 'N' or key == Key::Escape)
320     {
321         // reread timestamp in case the file was modified again
322         buffer.set_fs_status(get_fs_status(buffer.name()));
323         print_status({ format("'{}' kept", buffer.display_name()),
324                        context().faces()["Information"] });
325         if (key == 'N')
326             set_autoreload(Autoreload::No);
327     }
328     else
329     {
330         print_status({ format("'{}' is not a valid choice", key_to_str(key)),
331                        context().faces()["Error"] });
332         m_input_handler.on_next_key("buffer-reload", KeymapMode::None, [this](Key key, Context&){ on_buffer_reload_key(key); });
333         return;
334     }
335 
336     for (auto& client : ClientManager::instance())
337     {
338         if (&client->context().buffer() == &buffer and
339             client->m_buffer_reload_dialog_opened)
340             client->close_buffer_reload_dialog();
341     }
342 }
343 
close_buffer_reload_dialog()344 void Client::close_buffer_reload_dialog()
345 {
346     kak_assert(m_buffer_reload_dialog_opened);
347     // Reset first as this might check for reloading.
348     m_input_handler.reset_normal_mode();
349     m_buffer_reload_dialog_opened = false;
350     info_hide(true);
351 }
352 
check_if_buffer_needs_reloading()353 void Client::check_if_buffer_needs_reloading()
354 {
355     if (m_buffer_reload_dialog_opened)
356         return;
357 
358     Buffer& buffer = context().buffer();
359     auto reload = context().options()["autoreload"].get<Autoreload>();
360     if (not (buffer.flags() & Buffer::Flags::File) or reload == Autoreload::No)
361         return;
362 
363     const String& filename = buffer.name();
364     const timespec ts = get_fs_timestamp(filename);
365     const auto status = buffer.fs_status();
366 
367     if (ts == InvalidTime or ts == status.timestamp)
368         return;
369 
370     if (MappedFile fd{filename};
371         fd.st.st_size == status.file_size and hash_data(fd.data, fd.st.st_size) == status.hash)
372         return;
373 
374     if (reload == Autoreload::Ask)
375     {
376         StringView bufname = buffer.display_name();
377         info_show(format("reload '{}' ?", bufname),
378                   format("'{}' was modified externally\n"
379                          " y, <ret>: reload | n, <esc>: keep\n"
380                          " Y: always reload | N: always keep\n",
381                          bufname), {}, InfoStyle::Modal);
382 
383         m_buffer_reload_dialog_opened = true;
384         m_input_handler.on_next_key("buffer-reload", KeymapMode::None, [this](Key key, Context&){ on_buffer_reload_key(key); });
385     }
386     else
387         reload_buffer();
388 }
389 
get_env_var(StringView name) const390 StringView Client::get_env_var(StringView name) const
391 {
392     auto it = m_env_vars.find(name);
393     if (it == m_env_vars.end())
394         return {};
395     return it->value;
396 }
397 
on_option_changed(const Option & option)398 void Client::on_option_changed(const Option& option)
399 {
400     if (option.name() == "ui_options")
401     {
402         m_ui->set_ui_options(option.get<UserInterface::Options>());
403         m_ui_pending |= Draw;
404     }
405 }
406 
menu_show(Vector<DisplayLine> choices,BufferCoord anchor,MenuStyle style)407 void Client::menu_show(Vector<DisplayLine> choices, BufferCoord anchor, MenuStyle style)
408 {
409     m_menu = Menu{ std::move(choices), anchor, {}, style, -1 };
410     m_ui_pending |= MenuShow;
411     m_ui_pending &= ~MenuHide;
412 }
413 
menu_select(int selected)414 void Client::menu_select(int selected)
415 {
416     m_menu.selected = selected;
417     m_ui_pending |= MenuSelect;
418     m_ui_pending &= ~MenuHide;
419 }
420 
menu_hide()421 void Client::menu_hide()
422 {
423     m_menu = Menu{};
424     m_ui_pending |= MenuHide;
425     m_ui_pending &= ~(MenuShow | MenuSelect);
426 }
427 
info_show(DisplayLine title,DisplayLineList content,BufferCoord anchor,InfoStyle style)428 void Client::info_show(DisplayLine title, DisplayLineList content, BufferCoord anchor, InfoStyle style)
429 {
430     if (m_info.style == InfoStyle::Modal) // We already have a modal info opened, do not touch it.
431         return;
432 
433     m_info = Info{ std::move(title), std::move(content), anchor, {}, style };
434     m_ui_pending |= InfoShow;
435     m_ui_pending &= ~InfoHide;
436 }
437 
info_show(StringView title,StringView content,BufferCoord anchor,InfoStyle style)438 void Client::info_show(StringView title, StringView content, BufferCoord anchor, InfoStyle style)
439 {
440     if (not content.empty() and content.back() == '\n')
441         content = content.substr(0, content.length() - 1);
442     info_show(title.empty() ? DisplayLine{} : DisplayLine{title.str(), Face{}},
443               content | split<StringView>('\n')
444                       | transform([](StringView s) { return DisplayLine{replace(s, '\t', ' '), Face{}}; })
445                       | gather<DisplayLineList>(),
446               anchor, style);
447 }
448 
info_hide(bool even_modal)449 void Client::info_hide(bool even_modal)
450 {
451     if (not even_modal and m_info.style == InfoStyle::Modal)
452         return;
453 
454     m_info = Info{};
455     m_ui_pending |= InfoHide;
456     m_ui_pending &= ~InfoShow;
457 }
458 
459 }
460