diff options
| author | Enrico Borba <enricozb@users.noreply.github.com> | 2024-12-23 09:23:58 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-12-23 09:23:58 +0100 |
| commit | 52125e6336d596aebdd4da91080b3178ddca7449 (patch) | |
| tree | 27d3e5c01660d567f22fee621c97753f294256b0 /src | |
| parent | 14cb35f62b36b2f1aa530adb5e31c05ff1347bfc (diff) | |
| parent | 9c458c50661446fc6e7295787b06422137af099d (diff) | |
Merge branch 'master' into enricozb/daemon-stdin
Diffstat (limited to 'src')
96 files changed, 1359 insertions, 1148 deletions
diff --git a/src/alias_registry.cc b/src/alias_registry.cc index 96f2e4ef..4263c317 100644 --- a/src/alias_registry.cc +++ b/src/alias_registry.cc @@ -1,7 +1,6 @@ #include "alias_registry.hh" #include "command_manager.hh" -#include "ranges.hh" namespace Kakoune { diff --git a/src/alias_registry.hh b/src/alias_registry.hh index 1b7a49eb..32f9e2fb 100644 --- a/src/alias_registry.hh +++ b/src/alias_registry.hh @@ -3,6 +3,7 @@ #include "safe_ptr.hh" #include "string.hh" +#include "ranges.hh" #include "hash_map.hh" namespace Kakoune diff --git a/src/array.hh b/src/array.hh new file mode 100644 index 00000000..ec8b098b --- /dev/null +++ b/src/array.hh @@ -0,0 +1,48 @@ +#ifndef array_hh_INCLUDED +#define array_hh_INCLUDED + +#include <utility> +#include <stddef.h> + +#include "array_view.hh" + +namespace Kakoune +{ + +template<typename T, size_t N> +struct Array +{ + constexpr size_t size() const { return N; } + constexpr const T& operator[](int i) const { return m_data[i]; } + constexpr const T* begin() const { return m_data; } + constexpr const T* end() const { return m_data+N; } + + constexpr T& operator[](int i) { return m_data[i]; } + constexpr T* begin() { return m_data; } + constexpr T* end() { return m_data+N; } + + constexpr operator ArrayView<T>() { return {m_data, N}; } + constexpr operator ConstArrayView<T>() const { return {m_data, N}; } + + T m_data[N]; +}; + +template<typename T, typename... U> requires (std::is_same_v<T, U> and ...) +Array(T, U...) -> Array<T, 1 + sizeof...(U)>; + +template<typename T, size_t N, size_t... Indices> +constexpr Array<T, N> make_array(const T (&data)[N], std::index_sequence<Indices...>) +{ + static_assert(sizeof...(Indices) == N, "size mismatch"); + return {{data[Indices]...}}; +} + +template<typename T, size_t N> +constexpr Array<T, N> make_array(const T (&data)[N]) +{ + return make_array(data, std::make_index_sequence<N>()); +} + +} + +#endif // array_hh_INCLUDED diff --git a/src/array_view.hh b/src/array_view.hh index 98f3c811..1409ebc3 100644 --- a/src/array_view.hh +++ b/src/array_view.hh @@ -77,6 +77,9 @@ template<typename It> requires std::contiguous_iterator<It> ArrayView(It begin, It end) -> ArrayView<std::iter_value_t<It>>; +template<typename Container> +ArrayView(Container& c) -> ArrayView<std::remove_reference_t<decltype(*c.data())>>; + template<typename T> using ConstArrayView = ArrayView<const T>; diff --git a/src/assert.cc b/src/assert.cc index e755e8ba..c27fb1e8 100644 --- a/src/assert.cc +++ b/src/assert.cc @@ -1,12 +1,13 @@ #include "assert.hh" #include "backtrace.hh" -#include "buffer_utils.hh" +#include "format.hh" +#include "file.hh" #include "exception.hh" +#include "debug.hh" #include <sys/types.h> #include <unistd.h> -#include <signal.h> namespace Kakoune { diff --git a/src/backtrace.cc b/src/backtrace.cc index 1b3c6d09..04b772ad 100644 --- a/src/backtrace.cc +++ b/src/backtrace.cc @@ -1,7 +1,7 @@ #include "backtrace.hh" #include "string.hh" -#include "string_utils.hh" +#include "format.hh" #if defined(__GLIBC__) || defined(__APPLE__) # include <execinfo.h> diff --git a/src/buffer.cc b/src/buffer.cc index ada61ad8..f0205c54 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -570,17 +570,17 @@ void Buffer::notify_saved(FsStatus status) m_fs_status = status; } -BufferCoord Buffer::advance(BufferCoord coord, ByteCount count) const +BufferCoord Buffer::advance(ArrayView<const StringDataPtr> lines, BufferCoord coord, ByteCount count) { if (count > 0) { auto line = coord.line; count += coord.column; - while (count >= m_lines[line].length()) + while (count >= lines[(size_t)line]->length) { - count -= m_lines[line++].length(); - if (line == line_count()) - return end_coord(); + count -= lines[(size_t)line++]->length; + if ((size_t)line == lines.size()) + return line; } return { line, count }; } @@ -592,7 +592,7 @@ BufferCoord Buffer::advance(BufferCoord coord, ByteCount count) const { if (--line < 0) return {0, 0}; - count += m_lines[line].length(); + count += lines[(size_t)line]->length; } return { line, count }; } diff --git a/src/buffer.hh b/src/buffer.hh index 7b671c0e..50423502 100644 --- a/src/buffer.hh +++ b/src/buffer.hh @@ -3,7 +3,7 @@ #include "clock.hh" #include "coord.hh" -#include "constexpr_utils.hh" +#include "array.hh" #include "enum.hh" #include "file.hh" #include "optional.hh" @@ -64,8 +64,9 @@ public: // costly, so this is not strictly random access using iterator_category = std::bidirectional_iterator_tag; - BufferIterator() noexcept : m_buffer{nullptr}, m_line{} {} + BufferIterator() = default; BufferIterator(const Buffer& buffer, BufferCoord coord) noexcept; + BufferIterator(const StringDataPtr* lines, LineCount line_count, BufferCoord coord) noexcept; bool operator== (const BufferIterator& iterator) const noexcept; auto operator<=>(const BufferIterator& iterator) const noexcept; @@ -75,7 +76,7 @@ public: const char& operator[](size_t n) const noexcept; size_t operator- (const BufferIterator& iterator) const; - explicit operator bool() const { return static_cast<bool>(m_buffer); } + explicit operator bool() const { return m_lines; } BufferIterator operator+ (ByteCount size) const; BufferIterator operator- (ByteCount size) const; @@ -94,9 +95,10 @@ public: using Sentinel = BufferCoord; private: - SafePtr<const Buffer> m_buffer; + const StringDataPtr* m_lines; + [[no_unique_address]] StringView m_line; + [[no_unique_address]] LineCount m_line_count; BufferCoord m_coord; - StringView m_line; }; using BufferLines = Vector<StringDataPtr, MemoryDomain::BufferContent>; @@ -122,6 +124,7 @@ public: ReadOnly = 1 << 6, }; friend constexpr bool with_bit_ops(Meta::Type<Flags>) { return true; } + friend class BufferIterator; enum class HistoryId : size_t { First = 0, Invalid = (size_t)-1 }; @@ -157,9 +160,12 @@ public: String string(BufferCoord begin, BufferCoord end) const; StringView substr(BufferCoord begin, BufferCoord end) const; + static BufferCoord advance(ArrayView<const StringDataPtr> lines, BufferCoord coord, ByteCount count); + static ByteCount distance(ArrayView<const StringDataPtr> lines, BufferCoord begin, BufferCoord end); + const char& byte_at(BufferCoord c) const; - ByteCount distance(BufferCoord begin, BufferCoord end) const; - BufferCoord advance(BufferCoord coord, ByteCount count) const; + ByteCount distance(BufferCoord begin, BufferCoord end) const { return distance(m_lines, begin, end); } + BufferCoord advance(BufferCoord coord, ByteCount count) const { return advance(m_lines, coord, count); } BufferCoord next(BufferCoord coord) const; BufferCoord prev(BufferCoord coord) const; diff --git a/src/buffer.inl.hh b/src/buffer.inl.hh index acaa699f..890a5cbd 100644 --- a/src/buffer.inl.hh +++ b/src/buffer.inl.hh @@ -27,16 +27,16 @@ inline BufferCoord Buffer::prev(BufferCoord coord) const return { coord.line, coord.column - 1 }; } -inline ByteCount Buffer::distance(BufferCoord begin, BufferCoord end) const +inline ByteCount Buffer::distance(ArrayView<const StringDataPtr> lines, BufferCoord begin, BufferCoord end) { if (begin > end) - return -distance(end, begin); + return -distance(lines, end, begin); if (begin.line == end.line) return end.column - begin.column; - ByteCount res = m_lines[begin.line].length() - begin.column; + ByteCount res = lines[(size_t)begin.line]->length - begin.column; for (LineCount l = begin.line+1; l < end.line; ++l) - res += m_lines[l].length(); + res += lines[(size_t)l]->length; res += end.column; return res; } @@ -99,18 +99,23 @@ inline BufferCoord Buffer::end_coord() const } inline BufferIterator::BufferIterator(const Buffer& buffer, BufferCoord coord) noexcept - : m_buffer{&buffer}, m_coord{coord}, - m_line{coord.line < buffer.line_count() ? (*m_buffer)[coord.line] : StringView{}} {} + : BufferIterator{buffer.m_lines.data(), buffer.line_count(), coord} {} + +inline BufferIterator::BufferIterator(const StringDataPtr* lines, LineCount line_count, BufferCoord coord) noexcept + : m_lines{lines}, + m_line{coord.line < line_count ? m_lines[(size_t)coord.line]->strview() : StringView{}}, + m_line_count{line_count}, + m_coord{coord} {} inline bool BufferIterator::operator==(const BufferIterator& iterator) const noexcept { - kak_assert(m_buffer == iterator.m_buffer); + kak_assert(m_lines == iterator.m_lines); return m_coord == iterator.m_coord; } inline auto BufferIterator::operator<=>(const BufferIterator& iterator) const noexcept { - kak_assert(m_buffer == iterator.m_buffer); + kak_assert(m_lines == iterator.m_lines); return (m_coord <=> iterator.m_coord); } @@ -127,37 +132,38 @@ inline const char& BufferIterator::operator*() const noexcept inline const char& BufferIterator::operator[](size_t n) const noexcept { - return m_buffer->byte_at(m_buffer->advance(m_coord, n)); + auto coord = Buffer::advance({m_lines, (size_t)(int)m_line_count}, m_coord, n); + return m_lines[(size_t)coord.line]->strview()[coord.column]; } inline size_t BufferIterator::operator-(const BufferIterator& iterator) const { - kak_assert(m_buffer == iterator.m_buffer); - return (size_t)m_buffer->distance(iterator.m_coord, m_coord); + kak_assert(m_lines == iterator.m_lines); + return (size_t)Buffer::distance({m_lines, (size_t)(int)m_line_count}, iterator.m_coord, m_coord); } inline BufferIterator BufferIterator::operator+(ByteCount size) const { - kak_assert(m_buffer); - return { *m_buffer, m_buffer->advance(m_coord, size) }; + kak_assert(*this); + return { m_lines, m_line_count, Buffer::advance({m_lines, (size_t)(int)m_line_count}, m_coord, size) }; } inline BufferIterator BufferIterator::operator-(ByteCount size) const { - return { *m_buffer, m_buffer->advance(m_coord, -size) }; + return { m_lines, m_line_count, Buffer::advance({m_lines, (size_t)(int)m_line_count}, m_coord, -size) }; } inline BufferIterator& BufferIterator::operator+=(ByteCount size) { - m_coord = m_buffer->advance(m_coord, size); - m_line = (*m_buffer)[m_coord.line]; + m_coord = Buffer::advance({m_lines, (size_t)(int)m_line_count}, m_coord, size); + m_line = m_lines[(size_t)m_coord.line]->strview(); return *this; } inline BufferIterator& BufferIterator::operator-=(ByteCount size) { - m_coord = m_buffer->advance(m_coord, -size); - m_line = (*m_buffer)[m_coord.line]; + m_coord = Buffer::advance({m_lines, (size_t)(int)m_line_count}, m_coord, -size); + m_line = m_lines[(size_t)m_coord.line]->strview(); return *this; } @@ -165,8 +171,8 @@ inline BufferIterator& BufferIterator::operator++() { if (++m_coord.column == m_line.length()) { - m_line = (++m_coord.line < m_buffer->line_count()) ? - (*m_buffer)[m_coord.line] : StringView{}; + m_line = ((size_t)++m_coord.line < m_line_count) ? + m_lines[(size_t)m_coord.line]->strview() : StringView{}; m_coord.column = 0; } return *this; @@ -176,7 +182,7 @@ inline BufferIterator& BufferIterator::operator--() { if (m_coord.column == 0) { - m_line = (*m_buffer)[--m_coord.line]; + m_line = m_lines[(size_t)--m_coord.line]->strview(); m_coord.column = m_line.length() - 1; } else diff --git a/src/buffer_utils.cc b/src/buffer_utils.cc index 6a66b000..a4fb639b 100644 --- a/src/buffer_utils.cc +++ b/src/buffer_utils.cc @@ -1,6 +1,7 @@ #include "buffer_utils.hh" #include "buffer_manager.hh" +#include "coord.hh" #include "event_manager.hh" #include "file.hh" #include "selection.hh" @@ -165,7 +166,7 @@ void reload_file_buffer(Buffer& buffer) buffer.flags() &= ~Buffer::Flags::New; } -Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, bool scroll) +Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, AutoScroll scroll) { static ValueId fifo_watcher_id = get_free_value_id(); @@ -185,7 +186,7 @@ Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, bool scroll struct FifoWatcher : FDWatcher { - FifoWatcher(int fd, Buffer& buffer, bool scroll) + FifoWatcher(int fd, Buffer& buffer, AutoScroll scroll) : FDWatcher(fd, FdEvents::Read, EventMode::Normal, [](FDWatcher& watcher, FdEvents, EventMode mode) { if (mode == EventMode::Normal) @@ -214,7 +215,7 @@ Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, bool scroll bool closed = false; size_t loop = 0; char data[buffer_size]; - BufferCoord insert_coord = m_buffer.back_coord(); + Optional<BufferCoord> insert_begin; const int fifo = fd(); { @@ -232,17 +233,27 @@ Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, bool scroll auto pos = m_buffer.back_coord(); const bool is_first = pos == BufferCoord{0,0}; - if (not m_scroll and (is_first or m_had_trailing_newline)) + if ((m_scroll == AutoScroll::No and (is_first or m_had_trailing_newline)) + or (m_scroll == AutoScroll::NotInitially and is_first)) pos = m_buffer.next(pos); - pos = m_buffer.insert(pos, StringView(data, data+count)).end; + auto inserted_range = m_buffer.insert(pos, StringView(data, data+count)); + if (not insert_begin) + insert_begin = inserted_range.begin; + pos = inserted_range.end; bool have_trailing_newline = (data[count-1] == '\n'); - if (not m_scroll) + if (m_scroll != AutoScroll::Yes) { if (is_first) + { m_buffer.erase({0,0}, m_buffer.next({0,0})); - else if (not m_had_trailing_newline and have_trailing_newline) + --insert_begin->line; + if (m_scroll == AutoScroll::NotInitially and have_trailing_newline) + m_buffer.insert(m_buffer.end_coord(), "\n"); + } + else if (m_scroll == AutoScroll::No and + not m_had_trailing_newline and have_trailing_newline) m_buffer.erase(m_buffer.prev(pos), pos); } m_had_trailing_newline = have_trailing_newline; @@ -250,17 +261,21 @@ Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, bool scroll while (++loop < max_loop and fd_readable(fifo)); } - if (insert_coord != m_buffer.back_coord()) + if (insert_begin) + { + auto insert_back = (m_had_trailing_newline and m_scroll == AutoScroll::No) + ? m_buffer.back_coord() : m_buffer.prev(m_buffer.back_coord()); m_buffer.run_hook_in_own_context( Hook::BufReadFifo, - selection_to_string(ColumnType::Byte, m_buffer, {insert_coord, m_buffer.back_coord()})); + selection_to_string(ColumnType::Byte, m_buffer, {*insert_begin, insert_back})); + } if (closed) m_buffer.values().erase(fifo_watcher_id); // will delete this } Buffer& m_buffer; - bool m_scroll; + AutoScroll m_scroll; bool m_had_trailing_newline = false; }; @@ -271,36 +286,6 @@ Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, bool scroll return buffer; } -void write_to_debug_buffer(StringView str) -{ - if (not BufferManager::has_instance()) - { - write(2, str); - write(2, "\n"); - return; - } - - constexpr StringView debug_buffer_name = "*debug*"; - // Try to ensure we keep an empty line at the end of the debug buffer - // where the user can put its cursor to scroll with new messages - const bool eol_back = not str.empty() and str.back() == '\n'; - if (Buffer* buffer = BufferManager::instance().get_buffer_ifp(debug_buffer_name)) - { - buffer->flags() &= ~Buffer::Flags::ReadOnly; - auto restore = on_scope_end([buffer] { buffer->flags() |= Buffer::Flags::ReadOnly; }); - - buffer->insert(buffer->back_coord(), eol_back ? str : str + "\n"); - } - else - { - String line = str + (eol_back ? "\n" : "\n\n"); - create_buffer_from_string( - debug_buffer_name.str(), Buffer::Flags::NoUndo | Buffer::Flags::Debug | Buffer::Flags::ReadOnly, - line); - } -} - - auto to_string(Buffer::HistoryId id) { using Result = decltype(to_string(size_t{})); diff --git a/src/buffer_utils.hh b/src/buffer_utils.hh index e0b40425..bc0dab68 100644 --- a/src/buffer_utils.hh +++ b/src/buffer_utils.hh @@ -72,7 +72,8 @@ ColumnCount column_length(const Buffer& buffer, ColumnCount tabstop, LineCount l ByteCount get_byte_to_column(const Buffer& buffer, ColumnCount tabstop, DisplayCoord coord); -Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, bool scroll = false); +enum class AutoScroll { No, NotInitially, Yes }; +Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, AutoScroll scroll); Buffer* create_buffer_from_string(String name, Buffer::Flags flags, StringView data); Buffer* open_file_buffer(StringView filename, Buffer::Flags flags = Buffer::Flags::None); diff --git a/src/client.cc b/src/client.cc index 7cfea960..2fa8e8fb 100644 --- a/src/client.cc +++ b/src/client.cc @@ -1,16 +1,17 @@ #include "client.hh" -#include "face_registry.hh" +#include "clock.hh" #include "context.hh" -#include "buffer_manager.hh" #include "buffer_utils.hh" +#include "debug.hh" #include "file.hh" #include "remote.hh" #include "option.hh" #include "option_types.hh" #include "client_manager.hh" -#include "command_manager.hh" #include "event_manager.hh" +#include "shell_manager.hh" +#include "command_manager.hh" #include "user_interface.hh" #include "window.hh" #include "hash_map.hh" @@ -130,6 +131,7 @@ void Client::print_status(DisplayLine status_line) { m_status_line = std::move(status_line); m_ui_pending |= StatusLine; + m_pending_clear &= ~PendingClear::StatusLine; } @@ -255,7 +257,7 @@ void Client::redraw_ifn() if ((m_ui_pending & MenuShow) or update_menu_anchor) { auto anchor = m_menu.style == MenuStyle::Inline ? - window.display_position(m_menu.anchor) : DisplayCoord{}; + window.display_coord(m_menu.anchor) : DisplayCoord{}; if (not (m_ui_pending & MenuShow) and m_menu.ui_anchor != anchor) m_ui_pending |= anchor ? (MenuShow | MenuSelect) : MenuHide; m_menu.ui_anchor = anchor; @@ -275,7 +277,7 @@ void Client::redraw_ifn() if ((m_ui_pending & InfoShow) or update_info_anchor) { auto anchor = is_inline(m_info.style) ? - window.display_position(m_info.anchor) : DisplayCoord{}; + window.display_coord(m_info.anchor) : DisplayCoord{}; if (not (m_ui_pending & MenuShow) and m_info.ui_anchor != anchor) m_ui_pending |= anchor ? InfoShow : InfoHide; m_info.ui_anchor = anchor; @@ -467,6 +469,7 @@ void Client::info_show(DisplayLine title, DisplayLineList content, BufferCoord a m_info = Info{ std::move(title), std::move(content), anchor, {}, style }; m_ui_pending |= InfoShow; m_ui_pending &= ~InfoHide; + m_pending_clear &= ~PendingClear::Info; } void Client::info_show(StringView title, StringView content, BufferCoord anchor, InfoStyle style) @@ -490,4 +493,52 @@ void Client::info_hide(bool even_modal) m_ui_pending &= ~InfoShow; } +void Client::schedule_clear() +{ + if (not (m_ui_pending & InfoShow)) + m_pending_clear |= PendingClear::Info; + if (not (m_ui_pending & StatusLine)) + m_pending_clear |= PendingClear::StatusLine; +} + +void Client::clear_pending() +{ + if (m_pending_clear & PendingClear::StatusLine) + print_status({}); + if (m_pending_clear & PendingClear::Info) + info_hide(); + m_pending_clear = PendingClear::None; +} + +constexpr std::chrono::seconds wait_timeout{1}; + +BusyIndicator::BusyIndicator(const Context& context, + std::function<DisplayLine(std::chrono::seconds)> status_message, + TimePoint wait_time) + : m_context(context), + m_timer{wait_time + wait_timeout, + [this, status_message = std::move(status_message), wait_time](Timer& timer) { + if (not m_context.has_client()) + return; + using namespace std::chrono; + const auto now = Clock::now(); + timer.set_next_date(now + wait_timeout); + + auto& client = m_context.client(); + if (not m_previous_status) + m_previous_status = client.current_status(); + + client.print_status(status_message(duration_cast<seconds>(now - wait_time))); + client.redraw_ifn(); + }, EventMode::Urgent} {} + +BusyIndicator::~BusyIndicator() +{ + if (m_previous_status and std::uncaught_exceptions() == 0) // restore the status line + { + m_context.print_status(std::move(*m_previous_status)); + m_context.client().redraw_ifn(); + } +} + } diff --git a/src/client.hh b/src/client.hh index 75a2a02f..43584c42 100644 --- a/src/client.hh +++ b/src/client.hh @@ -1,7 +1,8 @@ #ifndef client_hh_INCLUDED #define client_hh_INCLUDED -#include "constexpr_utils.hh" +#include "array.hh" +#include "clock.hh" #include "display_buffer.hh" #include "env_vars.hh" #include "input_handler.hh" @@ -48,12 +49,17 @@ public: void info_show(DisplayLine title, DisplayLineList content, BufferCoord anchor, InfoStyle style); void info_show(StringView title, StringView content, BufferCoord anchor, InfoStyle style); void info_hide(bool even_modal = false); + bool info_pending() const { return m_ui_pending & PendingUI::InfoShow; }; + bool status_line_pending() const { return m_ui_pending & PendingUI::StatusLine; }; void print_status(DisplayLine status_line); const DisplayLine& current_status() const { return m_status_line; } DisplayCoord dimensions() const; + void schedule_clear(); + void clear_pending(); + void force_redraw(bool full = false); void redraw_ifn(); @@ -109,6 +115,16 @@ private: }; int m_ui_pending = 0; + enum class PendingClear + { + None = 0, + Info = 0b01, + StatusLine = 0b10 + }; + friend constexpr bool with_bit_ops(Meta::Type<PendingClear>) { return true; } + PendingClear m_pending_clear = PendingClear::None; + + struct Menu { Vector<DisplayLine> items; @@ -150,6 +166,19 @@ constexpr auto enum_desc(Meta::Type<Autoreload>) }); } +class BusyIndicator +{ +public: + BusyIndicator(const Context& context, + std::function<DisplayLine(std::chrono::seconds)> status_message, + TimePoint wait_time = Clock::now()); + ~BusyIndicator(); +private: + const Context& m_context; + Timer m_timer; + Optional<DisplayLine> m_previous_status; +}; + } #endif // client_hh_INCLUDED diff --git a/src/client_manager.cc b/src/client_manager.cc index 0ad5392f..3c8a7b6e 100644 --- a/src/client_manager.cc +++ b/src/client_manager.cc @@ -2,7 +2,6 @@ #include "buffer_manager.hh" #include "command_manager.hh" -#include "event_manager.hh" #include "face_registry.hh" #include "file.hh" #include "ranges.hh" diff --git a/src/color.cc b/src/color.cc index 145a5381..71ef46b6 100644 --- a/src/color.cc +++ b/src/color.cc @@ -2,7 +2,7 @@ #include "exception.hh" #include "ranges.hh" -#include "string_utils.hh" +#include "format.hh" #include <cstdio> diff --git a/src/command_manager.cc b/src/command_manager.cc index f7d094bb..0db97e94 100644 --- a/src/command_manager.cc +++ b/src/command_manager.cc @@ -2,10 +2,11 @@ #include "alias_registry.hh" #include "assert.hh" -#include "buffer_utils.hh" #include "context.hh" +#include "debug.hh" #include "flags.hh" #include "file.hh" +#include "hook_manager.hh" #include "optional.hh" #include "option_types.hh" #include "profile.hh" @@ -13,6 +14,7 @@ #include "regex.hh" #include "register_manager.hh" #include "shell_manager.hh" +#include "scope.hh" #include "utils.hh" #include "unit_tests.hh" @@ -177,6 +179,13 @@ ParseResult parse_quoted_balanced(ParseState& state) return {String{String::NoCopy{}, {beg, pos - terminated}}, terminated}; } +bool is_ascii_horizontal_blank(char c) +{ + return c == '\t' or + c == '\f' or + c == ' '; +} + String parse_unquoted(ParseState& state) { const char* beg = state.pos; @@ -187,7 +196,7 @@ String parse_unquoted(ParseState& state) while (state.pos != end) { const char c = *state.pos; - if (is_command_separator(c) or is_horizontal_blank(c)) + if (is_command_separator(c) or is_ascii_horizontal_blank(c)) { str += StringView{beg, state.pos}; if (state.pos != beg and *(state.pos - 1) == '\\') @@ -233,8 +242,8 @@ void skip_blanks_and_comments(ParseState& state) { while (state) { - const Codepoint c = *state.pos; - if (is_horizontal_blank(c)) + const char c = *state.pos; + if (is_ascii_horizontal_blank(c)) ++state.pos; else if (c == '\\' and state.pos + 1 != state.str.end() and state.pos[1] == '\n') @@ -348,7 +357,8 @@ void expand_token(Token&& token, const Context& context, const ShellContext& she case Token::Type::ShellExpand: { auto str = ShellManager::instance().eval( - content, context, {}, ShellManager::Flags::WaitForStdout, + content, context, StringView{}, + ShellManager::Flags::WaitForStdout, shell_context).first; if (not str.empty() and str.back() == '\n') @@ -655,7 +665,7 @@ Completions CommandManager::complete_module_name(StringView query) const | transform(&ModuleMap::Item::key))}; } -static Completions complete_expansion(const Context& context, CompletionFlags flags, +static Completions complete_expansion(const Context& context, Token token, ByteCount start, ByteCount cursor_pos, ByteCount pos_in_token) { @@ -671,7 +681,7 @@ static Completions complete_expansion(const Context& context, CompletionFlags fl token.content, pos_in_token) }; case Token::Type::ShellExpand: - return offset_pos(shell_complete(context, flags, token.content, + return offset_pos(shell_complete(context, token.content, pos_in_token), start); case Token::Type::ValExpand: @@ -692,7 +702,7 @@ static Completions complete_expansion(const Context& context, CompletionFlags fl } } -static Completions complete_expand(const Context& context, CompletionFlags flags, +static Completions complete_expand(const Context& context, StringView prefix, ByteCount start, ByteCount cursor_pos, ByteCount pos_in_token) { @@ -710,7 +720,7 @@ static Completions complete_expand(const Context& context, CompletionFlags flags continue; if (token.type == Token::Type::Raw or token.type == Token::Type::RawQuoted) return {}; - return complete_expansion(context, flags, token, + return complete_expansion(context, token, start + token.pos, cursor_pos, pos_in_token - token.pos); } @@ -745,8 +755,7 @@ static Completions requote(Completions completions, Token::Type token_type) } Completions CommandManager::Completer::operator()( - const Context& context, CompletionFlags flags, - StringView command_line, ByteCount cursor_pos) + const Context& context, StringView command_line, ByteCount cursor_pos) { auto prefix = command_line.substr(0_byte, cursor_pos); CommandParser parser{prefix}; @@ -797,7 +806,7 @@ Completions CommandManager::Completer::operator()( case Token::Type::ShellExpand: case Token::Type::ValExpand: case Token::Type::FileExpand: - return complete_expansion(context, flags, token, start, cursor_pos, pos_in_token); + return complete_expansion(context, token, start, cursor_pos, pos_in_token); case Token::Type::Raw: case Token::Type::RawQuoted: @@ -837,7 +846,7 @@ Completions CommandManager::Completer::operator()( const auto& switch_desc = command.param_desc.switches.get(raw_params.at(raw_params.size() - 2).substr(1_byte)); if (not *switch_desc.arg_completer) return Completions{}; - return offset_pos(requote((*switch_desc.arg_completer)(context, flags, raw_params.back(), pos_in_token), token.type), start); + return offset_pos(requote((*switch_desc.arg_completer)(context, raw_params.back(), pos_in_token), token.type), start); } case ParametersParser::State::Positional: break; @@ -849,10 +858,10 @@ Completions CommandManager::Completer::operator()( Vector<String> params{parser.begin(), parser.end()}; auto index = params.size() - 1; - return offset_pos(requote(m_command_completer(context, flags, params, index, pos_in_token), token.type), start); + return offset_pos(requote(m_command_completer(context, params, index, pos_in_token), token.type), start); } case Token::Type::Expand: - return complete_expand(context, flags, token.content, start, cursor_pos, pos_in_token); + return complete_expand(context, token.content, start, cursor_pos, pos_in_token); default: break; } @@ -860,7 +869,7 @@ Completions CommandManager::Completer::operator()( } Completions CommandManager::NestedCompleter::operator()( - const Context& context, CompletionFlags flags, CommandParameters params, + const Context& context, CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) { StringView prefix = params[token_to_complete].substr(0, pos_in_token); @@ -878,7 +887,7 @@ Completions CommandManager::NestedCompleter::operator()( } return m_command_completer - ? m_command_completer(context, flags, params.subrange(1), token_to_complete-1, pos_in_token) + ? m_command_completer(context, params.subrange(1), token_to_complete-1, pos_in_token) : Completions{}; } diff --git a/src/command_manager.hh b/src/command_manager.hh index 878ca129..3f18e423 100644 --- a/src/command_manager.hh +++ b/src/command_manager.hh @@ -1,7 +1,6 @@ #ifndef command_manager_hh_INCLUDED #define command_manager_hh_INCLUDED -#include "coord.hh" #include "completion.hh" #include "array_view.hh" #include "shell_manager.hh" @@ -12,7 +11,6 @@ #include "hash_map.hh" #include <functional> -#include <initializer_list> namespace Kakoune { @@ -24,7 +22,6 @@ using CommandFunc = std::function<void (const ParametersParser& parser, const ShellContext& shell_context)>; using CommandCompleter = std::function<Completions (const Context& context, - CompletionFlags, CommandParameters, size_t, ByteCount)>; @@ -120,7 +117,7 @@ public: struct Completer { - Completions operator()(const Context& context, CompletionFlags flags, + Completions operator()(const Context& context, StringView command_line, ByteCount cursor_pos); private: @@ -130,8 +127,8 @@ public: struct NestedCompleter { - Completions operator()(const Context& context, CompletionFlags flags, - CommandParameters params, size_t token_to_complete, ByteCount pos_in_token); + Completions operator()(const Context& context, CommandParameters params, + size_t token_to_complete, ByteCount pos_in_token); private: String m_last_complete_command; diff --git a/src/commands.cc b/src/commands.cc index d22c0647..0f892004 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -8,6 +8,7 @@ #include "command_manager.hh" #include "completion.hh" #include "context.hh" +#include "debug.hh" #include "event_manager.hh" #include "face_registry.hh" #include "file.hh" @@ -31,7 +32,6 @@ #include "user_interface.hh" #include "window.hh" -#include <functional> #include <utility> #include <sys/types.h> @@ -76,14 +76,14 @@ Buffer* open_fifo(StringView name, StringView filename, Buffer::Flags flags, boo if (fd < 0) throw runtime_error(format("unable to open '{}'", filename)); - return create_fifo_buffer(name.str(), fd, flags, scroll); + return create_fifo_buffer(name.str(), fd, flags, scroll ? AutoScroll::Yes : AutoScroll::No); } template<typename... Completers> struct PerArgumentCommandCompleter; template<> struct PerArgumentCommandCompleter<> { - Completions operator()(const Context&, CompletionFlags, CommandParameters, + Completions operator()(const Context&, CommandParameters, size_t, ByteCount) const { return {}; } }; @@ -96,7 +96,7 @@ struct PerArgumentCommandCompleter<Completer, Rest...> : PerArgumentCommandCompl : PerArgumentCommandCompleter<Rest...>(std::forward<R>(rest)...), m_completer(std::forward<C>(completer)) {} - Completions operator()(const Context& context, CompletionFlags flags, + Completions operator()(const Context& context, CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) { @@ -104,10 +104,10 @@ struct PerArgumentCommandCompleter<Completer, Rest...> : PerArgumentCommandCompl { const String& arg = token_to_complete < params.size() ? params[token_to_complete] : String(); - return m_completer(context, flags, arg, pos_in_token); + return m_completer(context, arg, pos_in_token); } return PerArgumentCommandCompleter<Rest...>::operator()( - context, flags, params.subrange(1), + context, params.subrange(1), token_to_complete-1, pos_in_token); } @@ -125,8 +125,8 @@ template<typename Completer> auto add_flags(Completer completer, Completions::Flags completions_flags) { return [completer=std::move(completer), completions_flags] - (const Context& context, CompletionFlags flags, StringView prefix, ByteCount cursor_pos) { - Completions res = completer(context, flags, prefix, cursor_pos); + (const Context& context, StringView prefix, ByteCount cursor_pos) { + Completions res = completer(context, prefix, cursor_pos); res.flags |= completions_flags; return res; }; @@ -140,7 +140,7 @@ auto menu(Completer completer) template<bool menu> auto filename_completer = make_completer( - [](const Context& context, CompletionFlags flags, StringView prefix, ByteCount cursor_pos) + [](const Context& context, StringView prefix, ByteCount cursor_pos) { return Completions{ 0_byte, cursor_pos, complete_filename(prefix, context.options()["ignored_files"].get<Regex>(), @@ -149,7 +149,7 @@ auto filename_completer = make_completer( template<bool menu> auto filename_arg_completer = - [](const Context& context, CompletionFlags flags, StringView prefix, ByteCount cursor_pos) -> Completions + [](const Context& context, StringView prefix, ByteCount cursor_pos) -> Completions { return { 0_byte, cursor_pos, complete_filename(prefix, context.options()["ignored_files"].get<Regex>(), @@ -157,20 +157,19 @@ auto filename_arg_completer = menu ? Completions::Flags::Menu : Completions::Flags::None }; }; auto client_arg_completer = - [](const Context& context, CompletionFlags flags, StringView prefix, ByteCount cursor_pos) -> Completions + [](const Context& context, StringView prefix, ByteCount cursor_pos) -> Completions { return { 0_byte, cursor_pos, ClientManager::instance().complete_client_name(prefix, cursor_pos), Completions::Flags::Menu }; }; auto arg_completer = [](auto candidates) -> PromptCompleter { - return [=](const Context& context, CompletionFlags flags, StringView prefix, ByteCount cursor_pos) -> Completions { + return [=](const Context& context, StringView prefix, ByteCount cursor_pos) -> Completions { return Completions{ 0_byte, cursor_pos, complete(prefix, cursor_pos, candidates), Completions::Flags::Menu }; }; }; template<bool ignore_current = false> -static Completions complete_buffer_name(const Context& context, CompletionFlags flags, - StringView prefix, ByteCount cursor_pos) +static Completions complete_buffer_name(const Context& context, StringView prefix, ByteCount cursor_pos) { struct RankedMatchAndBuffer : RankedMatch { @@ -219,7 +218,7 @@ template<typename Func> auto make_single_word_completer(Func&& func) { return make_completer( - [func = std::move(func)](const Context& context, CompletionFlags flags, + [func = std::move(func)](const Context& context, StringView prefix, ByteCount cursor_pos) -> Completions { auto candidate = { func(context) }; return { 0_byte, cursor_pos, complete(prefix, cursor_pos, candidate) }; }); @@ -230,21 +229,21 @@ const ParameterDesc single_param{ {}, ParameterDesc::Flags::None, 1, 1 }; const ParameterDesc single_optional_param{ {}, ParameterDesc::Flags::None, 0, 1 }; const ParameterDesc double_params{ {}, ParameterDesc::Flags::None, 2, 2 }; -static Completions complete_scope(const Context&, CompletionFlags, +static Completions complete_scope(const Context&, StringView prefix, ByteCount cursor_pos) { static constexpr StringView scopes[] = { "global", "buffer", "window", "local"}; return { 0_byte, cursor_pos, complete(prefix, cursor_pos, scopes) }; } -static Completions complete_scope_including_current(const Context&, CompletionFlags, +static Completions complete_scope_including_current(const Context&, StringView prefix, ByteCount cursor_pos) { static constexpr StringView scopes[] = { "global", "buffer", "window", "local", "current" }; return { 0_byte, cursor_pos, complete(prefix, cursor_pos, scopes) }; } -static Completions complete_scope_no_global(const Context&, CompletionFlags, +static Completions complete_scope_no_global(const Context&, StringView prefix, ByteCount cursor_pos) { static constexpr StringView scopes[] = { "buffer", "window", "local", "current" }; @@ -252,105 +251,127 @@ static Completions complete_scope_no_global(const Context&, CompletionFlags, } -static Completions complete_command_name(const Context& context, CompletionFlags, +static Completions complete_command_name(const Context& context, StringView prefix, ByteCount cursor_pos) { return CommandManager::instance().complete_command_name( context, prefix.substr(0, cursor_pos)); } -struct ShellScriptCompleter +struct AsyncShellScript { - ShellScriptCompleter(String shell_script, - Completions::Flags flags = Completions::Flags::None) + AsyncShellScript(String shell_script, + Completions::Flags flags = Completions::Flags::None) : m_shell_script{std::move(shell_script)}, m_flags(flags) {} - Completions operator()(const Context& context, CompletionFlags flags, - CommandParameters params, size_t token_to_complete, - ByteCount pos_in_token) + AsyncShellScript(const AsyncShellScript& other) : m_shell_script{other.m_shell_script}, m_flags(other.m_flags) {} + AsyncShellScript& operator=(const AsyncShellScript& other) { m_shell_script = other.m_shell_script; m_flags = other.m_flags; return *this; } + +protected: + void spawn_script(const Context& context, const ShellContext& shell_context, auto&& handle_line) { - if (flags & CompletionFlags::Fast) // no shell on fast completion - return Completions{}; + m_handle_line = handle_line; + m_running_script.emplace(ShellManager::instance().spawn(m_shell_script, context, false, shell_context)); + m_watcher.emplace((int)m_running_script->out, FdEvents::Read, EventMode::Urgent, + [this, &input_handler=context.input_handler()](auto&&... args) { read_stdout(input_handler); }); + } - ShellContext shell_context{ - params, - { { "token_to_complete", to_string(token_to_complete) }, - { "pos_in_token", to_string(pos_in_token) } } - }; - String output = ShellManager::instance().eval(m_shell_script, context, {}, - ShellManager::Flags::WaitForStdout, - shell_context).first; - CandidateList candidates; - for (auto&& candidate : output | split<StringView>('\n') - | filter([](auto s) { return not s.empty(); })) - candidates.push_back(candidate.str()); + void read_stdout(InputHandler& input_handler) + { + char buffer[2048]; + bool closed = false; + int fd = (int)m_running_script->out; + while (fd_readable(fd)) + { + int size = read(fd, buffer, sizeof(buffer)); + if (size == 0) + { + closed = true; + break; + } + m_stdout_buffer.insert(m_stdout_buffer.end(), buffer, buffer + size); + } + auto end = closed ? m_stdout_buffer.end() : find(m_stdout_buffer | reverse(), '\n').base(); + for (auto c : ArrayView(m_stdout_buffer.begin(), end) | split<StringView>('\n') + | filter([](auto s) { return not s.empty(); })) + m_handle_line(c); - return {0_byte, pos_in_token, std::move(candidates), m_flags}; + m_stdout_buffer.erase(m_stdout_buffer.begin(), end); + + input_handler.refresh_ifn(); + if (closed) + { + m_running_script.reset(); + m_watcher.reset(); + m_handle_line = {}; + } } -private: + String m_shell_script; + Optional<Shell> m_running_script; + Optional<FDWatcher> m_watcher; + Vector<char, MemoryDomain::Completion> m_stdout_buffer; + std::function<void (StringView)> m_handle_line; Completions::Flags m_flags; }; -struct ShellCandidatesCompleter +struct ShellScriptCompleter : AsyncShellScript { - ShellCandidatesCompleter(String shell_script, - Completions::Flags flags = Completions::Flags::None) - : m_shell_script{std::move(shell_script)}, m_flags(flags) {} - - ShellCandidatesCompleter(const ShellCandidatesCompleter& other) : m_shell_script{other.m_shell_script}, m_flags(other.m_flags) {} - ShellCandidatesCompleter& operator=(const ShellCandidatesCompleter& other) { m_shell_script = other.m_shell_script; m_flags = other.m_flags; return *this; } + using AsyncShellScript::AsyncShellScript; - Completions operator()(const Context& context, CompletionFlags flags, + Completions operator()(const Context& context, CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) { - if (m_last_token != token_to_complete) + CandidateList candidates; + if (m_last_token != token_to_complete or pos_in_token != m_last_pos_in_token) { ShellContext shell_context{ params, - { { "token_to_complete", to_string(token_to_complete) } } + { { "token_to_complete", to_string(token_to_complete) }, + { "pos_in_token", to_string(pos_in_token) } } }; - m_running_script.emplace(ShellManager::instance().spawn(m_shell_script, context, false, shell_context)); - m_watcher.emplace((int)m_running_script->out, FdEvents::Read, EventMode::Urgent, - [this, &input_handler=context.input_handler()](auto&&... args) { read_candidates(input_handler); }); + spawn_script(context, shell_context, [this](StringView line) { m_candidates.push_back(line.str()); }); + + candidates = std::move(m_candidates); // avoid completion menu flicker by keeping the previous result visible m_candidates.clear(); m_last_token = token_to_complete; + m_last_pos_in_token = pos_in_token; } - return rank_candidates(params[token_to_complete].substr(0, pos_in_token)); + else + candidates = m_candidates; + + return {0_byte, pos_in_token, std::move(candidates), m_flags}; } private: - void read_candidates(InputHandler& input_handler) + CandidateList m_candidates; + int m_last_token = -1; + ByteCount m_last_pos_in_token = -1; +}; + +struct ShellCandidatesCompleter : AsyncShellScript +{ + using AsyncShellScript::AsyncShellScript; + + Completions operator()(const Context& context, + CommandParameters params, size_t token_to_complete, + ByteCount pos_in_token) { - char buffer[2048]; - bool closed = false; - int fd = (int)m_running_script->out; - while (fd_readable(fd)) + if (m_last_token != token_to_complete) { - int size = read(fd, buffer, sizeof(buffer)); - if (size == 0) - { - closed = true; - break; - } - m_stdout_buffer.insert(m_stdout_buffer.end(), buffer, buffer + size); + ShellContext shell_context{ + params, + { { "token_to_complete", to_string(token_to_complete) } } + }; + spawn_script(context, shell_context, [this](StringView line) { m_candidates.emplace_back(line.str(), used_letters(line)); }); + m_candidates.clear(); + m_last_token = token_to_complete; } - - auto end = closed ? m_stdout_buffer.end() : find(m_stdout_buffer | reverse(), '\n').base(); - for (auto c : ArrayView(m_stdout_buffer.begin(), end) | split<StringView>('\n') - | filter([](auto s) { return not s.empty(); })) - m_candidates.emplace_back(c.str(), used_letters(c)); - m_stdout_buffer.erase(m_stdout_buffer.begin(), end); - - input_handler.refresh_ifn(); - if (not closed) - return; - - m_running_script.reset(); - m_watcher.reset(); + return rank_candidates(params[token_to_complete].substr(0, pos_in_token)); } +private: Completions rank_candidates(StringView query) { UsedLetters query_letters = used_letters(query); @@ -377,13 +398,8 @@ private: return Completions{0_byte, query.length(), std::move(res), m_flags}; } - String m_shell_script; - Vector<char, MemoryDomain::Completion> m_stdout_buffer; - Optional<Shell> m_running_script; - Optional<FDWatcher> m_watcher; Vector<std::pair<String, UsedLetters>, MemoryDomain::Completion> m_candidates; int m_last_token = -1; - Completions::Flags m_flags; }; template<typename Completer> @@ -395,9 +411,9 @@ struct PromptCompleterAdapter { if (not m_completer) return {}; - return [completer=std::move(m_completer)](const Context& context, CompletionFlags flags, + return [completer=std::move(m_completer)](const Context& context, StringView prefix, ByteCount cursor_pos) { - return completer(context, flags, {String{String::NoCopy{}, prefix}}, 0, cursor_pos); + return completer(context, {String{String::NoCopy{}, prefix}}, 0, cursor_pos); }; } @@ -602,7 +618,7 @@ void do_write_buffer(Context& context, Optional<String> filename, WriteFlags fla auto method = write_method.value_or_compute([&] { return context.options()["writemethod"].get<WriteMethod>(); }); context.hooks().run_hook(Hook::BufWritePre, effective_filename, context); - write_buffer_to_file(buffer, effective_filename, method, flags); + write_buffer_to_file(context, buffer, effective_filename, method, flags); context.hooks().run_hook(Hook::BufWritePost, effective_filename, context); } @@ -657,7 +673,7 @@ void write_all_buffers(const Context& context, bool sync = false, Optional<Write auto method = write_method.value_or_compute([&] { return context.options()["writemethod"].get<WriteMethod>(); }); auto flags = sync ? WriteFlags::Sync : WriteFlags::None; buffer->run_hook_in_own_context(Hook::BufWritePre, buffer->name(), context.name()); - write_buffer_to_file(*buffer, buffer->name(), method, flags); + write_buffer_to_file(context, *buffer, buffer->name(), method, flags); buffer->run_hook_in_own_context(Hook::BufWritePost, buffer->name(), context.name()); } } @@ -994,7 +1010,7 @@ static constexpr auto highlighter_scopes = { "global/", "buffer/", "window/", "s template<bool add> Completions highlighter_cmd_completer( - const Context& context, CompletionFlags flags, CommandParameters params, + const Context& context, CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) { if (token_to_complete == 0) @@ -1075,9 +1091,9 @@ const CommandDesc arrange_buffers_cmd = { ParameterDesc{{}, ParameterDesc::Flags::None, 1}, CommandFlags::None, CommandHelper{}, - [](const Context& context, CompletionFlags flags, CommandParameters params, size_t, ByteCount cursor_pos) + [](const Context& context, CommandParameters params, size_t, ByteCount cursor_pos) { - return menu(complete_buffer_name<false>)(context, flags, params.back(), cursor_pos); + return menu(complete_buffer_name<false>)(context, params.back(), cursor_pos); }, [](const ParametersParser& parser, Context&, const ShellContext&) { @@ -1175,8 +1191,7 @@ const CommandDesc remove_highlighter_cmd = { } }; -static Completions complete_hooks(const Context&, CompletionFlags, - StringView prefix, ByteCount cursor_pos) +static Completions complete_hooks(const Context&, StringView prefix, ByteCount cursor_pos) { return { 0_byte, cursor_pos, complete(prefix, cursor_pos, enum_desc(Meta::Type<Hook>{}) | transform(&EnumDesc<Hook>::name)) }; } @@ -1229,12 +1244,12 @@ const CommandDesc remove_hook_cmd = { double_params, CommandFlags::None, CommandHelper{}, - [](const Context& context, CompletionFlags flags, + [](const Context& context, CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) -> Completions { if (token_to_complete == 0) - return menu(complete_scope)(context, flags, params[0], pos_in_token); + return menu(complete_scope)(context, params[0], pos_in_token); else if (token_to_complete == 1) { if (auto scope = get_scope_ifp(params[0], context)) @@ -1272,8 +1287,7 @@ Vector<String> params_to_shell(const ParametersParser& parser) return vars; } -Completions complete_completer_type(const Context&, CompletionFlags, - StringView prefix, ByteCount cursor_pos) +Completions complete_completer_type(const Context&, StringView prefix, ByteCount cursor_pos) { static constexpr StringView completers[] = {"file", "client", "buffer", "shell-script", "shell-script-candidates", "command", "shell"}; return { 0_byte, cursor_pos, complete(prefix, cursor_pos, completers) }; @@ -1284,8 +1298,7 @@ CommandCompleter make_command_completer(StringView type, StringView param, Compl { if (type == "file") { - return [=](const Context& context, CompletionFlags flags, - CommandParameters params, + return [=](const Context& context, CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) { const String& prefix = params[token_to_complete]; const auto& ignored_files = context.options()["ignored_files"].get<Regex>(); @@ -1297,8 +1310,7 @@ CommandCompleter make_command_completer(StringView type, StringView param, Compl } else if (type == "client") { - return [=](const Context& context, CompletionFlags flags, - CommandParameters params, + return [=](const Context& context, CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) { const String& prefix = params[token_to_complete]; @@ -1310,12 +1322,11 @@ CommandCompleter make_command_completer(StringView type, StringView param, Compl } else if (type == "buffer") { - return [=](const Context& context, CompletionFlags flags, - CommandParameters params, + return [=](const Context& context, CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) { return add_flags(complete_buffer_name<false>, completions_flags)( - context, flags, params[token_to_complete], pos_in_token); + context, params[token_to_complete], pos_in_token); }; } else if (type == "shell-script") @@ -1336,12 +1347,11 @@ CommandCompleter make_command_completer(StringView type, StringView param, Compl return CommandManager::NestedCompleter{}; else if (type == "shell") { - return [=](const Context& context, CompletionFlags flags, - CommandParameters params, + return [=](const Context& context, CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) { return add_flags(shell_complete, completions_flags)( - context, flags, params[token_to_complete], pos_in_token); + context, params[token_to_complete], pos_in_token); }; } else @@ -1453,8 +1463,7 @@ const CommandDesc define_command_cmd = { define_command }; -static Completions complete_alias_name(const Context& context, CompletionFlags, - StringView prefix, ByteCount cursor_pos) +static Completions complete_alias_name(const Context& context, StringView prefix, ByteCount cursor_pos) { return { 0_byte, cursor_pos, complete(prefix, cursor_pos, context.aliases().flatten_aliases() @@ -1546,7 +1555,7 @@ const CommandDesc echo_cmd = { message.push_back('\n'); if (auto filename = parser.get_switch("to-file")) - write_to_file(*filename, message); + write_to_file(context, *filename, message); else if (auto command = parser.get_switch("to-shell-script")) ShellManager::instance().eval(*command, context, message, ShellManager::Flags::None, shell_context); else if (parser.get_switch("debug")) @@ -1587,8 +1596,7 @@ const CommandDesc debug_cmd = { CommandFlags::None, CommandHelper{}, make_completer( - [](const Context& context, CompletionFlags flags, - StringView prefix, ByteCount cursor_pos) -> Completions { + [](const Context& context, StringView prefix, ByteCount cursor_pos) -> Completions { auto c = {"info", "buffers", "options", "memory", "shared-strings", "profile-hash-maps", "faces", "mappings", "regex", "registers"}; return { 0_byte, cursor_pos, complete(prefix, cursor_pos, c), Completions::Flags::Menu }; @@ -1772,12 +1780,11 @@ const CommandDesc set_option_cmd = { }, CommandFlags::None, option_doc_helper, - [](const Context& context, CompletionFlags flags, - CommandParameters params, size_t token_to_complete, - ByteCount pos_in_token) -> Completions + [](const Context& context, CommandParameters params, + size_t token_to_complete, ByteCount pos_in_token) -> Completions { if (token_to_complete == 0) - return menu(complete_scope_including_current)(context, flags, params[0], pos_in_token); + return menu(complete_scope_including_current)(context, params[0], pos_in_token); else if (token_to_complete == 1) return { 0_byte, params[1].length(), GlobalScope::instance().option_registry().complete_option_name(params[1], pos_in_token), @@ -1809,12 +1816,11 @@ const CommandDesc set_option_cmd = { } }; -Completions complete_option(const Context& context, CompletionFlags flags, - CommandParameters params, size_t token_to_complete, - ByteCount pos_in_token) +Completions complete_option(const Context& context, CommandParameters params, + size_t token_to_complete, ByteCount pos_in_token) { if (token_to_complete == 0) - return menu(complete_scope_no_global)(context, flags, params[0], pos_in_token); + return menu(complete_scope_no_global)(context, params[0], pos_in_token); else if (token_to_complete == 1) return { 0_byte, params[1].length(), GlobalScope::instance().option_registry().complete_option_name(params[1], pos_in_token), @@ -1883,7 +1889,7 @@ const CommandDesc declare_option_cmd = { CommandFlags::None, CommandHelper{}, make_completer( - [](const Context& context, CompletionFlags flags, + [](const Context& context, StringView prefix, ByteCount cursor_pos) -> Completions { auto c = {"int", "bool", "str", "regex", "int-list", "str-list", "completions", "line-specs", "range-specs", "str-to-str-map"}; return { 0_byte, cursor_pos, complete(prefix, cursor_pos, c), Completions::Flags::Menu }; @@ -1929,12 +1935,11 @@ const CommandDesc declare_option_cmd = { }; template<bool unmap> -static Completions map_key_completer(const Context& context, CompletionFlags flags, - CommandParameters params, size_t token_to_complete, - ByteCount pos_in_token) +static Completions map_key_completer(const Context& context, CommandParameters params, + size_t token_to_complete, ByteCount pos_in_token) { if (token_to_complete == 0) - return menu(complete_scope)(context, flags, params[0], pos_in_token); + return menu(complete_scope)(context, params[0], pos_in_token); if (token_to_complete == 1) { auto& user_modes = get_scope(params[0], context).keymaps().user_modes(); @@ -2450,7 +2455,7 @@ const CommandDesc try_catch_cmd = { } }; -static Completions complete_face(const Context& context, CompletionFlags flags, +static Completions complete_face(const Context& context, StringView prefix, ByteCount cursor_pos) { return {0_byte, cursor_pos, @@ -2482,8 +2487,9 @@ const CommandDesc set_face_cmd = { " <fg color>[,<bg color>[,<underline color>]][+<attributes>][@<base>]\n" "colors are either a color name, rgb:######, or rgba:######## values.\n" "attributes is a combination of:\n" - " u: underline, c: curly underline, i: italic, b: bold,\n" - " r: reverse, s: strikethrough, B: blink, d: dim,\n" + " u: underline, c: curly underline, U: double underline,\n" + " i: italic, b: bold, r: reverse,\n" + " s: strikethrough, B: blink, d: dim,\n" " f: final foreground, g: final background,\n" " a: final attributes, F: same as +fga\n" "facespec can as well just be the name of another face.\n" @@ -2544,7 +2550,7 @@ const CommandDesc set_register_cmd = { CommandFlags::None, CommandHelper{}, make_completer( - [](const Context& context, CompletionFlags flags, + [](const Context& context, StringView prefix, ByteCount cursor_pos) -> Completions { return { 0_byte, cursor_pos, RegisterManager::instance().complete_register_name(prefix, cursor_pos) }; @@ -2594,7 +2600,7 @@ const CommandDesc change_directory_cmd = { CommandFlags::None, CommandHelper{}, make_completer( - [](const Context& context, CompletionFlags flags, + [](const Context& context, StringView prefix, ByteCount cursor_pos) -> Completions { return { 0_byte, cursor_pos, complete_filename(prefix, @@ -2695,7 +2701,7 @@ const CommandDesc enter_user_mode_cmd = { }, CommandFlags::None, CommandHelper{}, - [](const Context& context, CompletionFlags flags, + [](const Context& context, CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) -> Completions { @@ -2749,7 +2755,7 @@ const CommandDesc require_module_cmd = { CommandFlags::None, CommandHelper{}, make_completer(menu( - [](const Context&, CompletionFlags, StringView prefix, ByteCount cursor_pos) { + [](const Context&, StringView prefix, ByteCount cursor_pos) { return CommandManager::instance().complete_module_name(prefix.substr(0, cursor_pos)); })), [](const ParametersParser& parser, Context& context, const ShellContext&) diff --git a/src/completion.cc b/src/completion.cc index 762a0413..c44319d8 100644 --- a/src/completion.cc +++ b/src/completion.cc @@ -2,13 +2,13 @@ #include "file.hh" #include "context.hh" #include "option_types.hh" +#include "option_manager.hh" #include "regex.hh" namespace Kakoune { -Completions shell_complete(const Context& context, CompletionFlags flags, - StringView prefix, ByteCount cursor_pos) +Completions shell_complete(const Context& context, StringView prefix, ByteCount cursor_pos) { ByteCount word_start = 0; ByteCount word_end = 0; diff --git a/src/completion.hh b/src/completion.hh index e9321d5a..cef703a2 100644 --- a/src/completion.hh +++ b/src/completion.hh @@ -1,7 +1,6 @@ #ifndef completion_hh_INCLUDED #define completion_hh_INCLUDED -#include <functional> #include <algorithm> #include "units.hh" @@ -43,22 +42,12 @@ struct Completions : candidates(std::move(candidates)), start(start), end(end), flags{flags} {} }; -enum class CompletionFlags -{ - None = 0, - Fast = 1 << 0, -}; - -constexpr bool with_bit_ops(Meta::Type<CompletionFlags>) { return true; } - -inline Completions complete_nothing(const Context&, CompletionFlags, - StringView, ByteCount cursor_pos) +inline Completions complete_nothing(const Context&, StringView, ByteCount cursor_pos) { return {cursor_pos, cursor_pos}; } -Completions shell_complete(const Context& context, CompletionFlags, - StringView, ByteCount cursor_pos); +Completions shell_complete(const Context& context, StringView, ByteCount cursor_pos); inline Completions offset_pos(Completions completion, ByteCount offset) { diff --git a/src/constexpr_utils.hh b/src/constexpr_utils.hh deleted file mode 100644 index f83faacb..00000000 --- a/src/constexpr_utils.hh +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef constexpr_utils_hh_INCLUDED -#define constexpr_utils_hh_INCLUDED - -#include <utility> -#include <initializer_list> -#include <stddef.h> - -#include "array_view.hh" - -namespace Kakoune -{ - -template<typename T, size_t N> -struct Array -{ - constexpr size_t size() const { return N; } - constexpr const T& operator[](int i) const { return m_data[i]; } - constexpr const T* begin() const { return m_data; } - constexpr const T* end() const { return m_data+N; } - - constexpr T& operator[](int i) { return m_data[i]; } - constexpr T* begin() { return m_data; } - constexpr T* end() { return m_data+N; } - - constexpr operator ArrayView<T>() { return {m_data, N}; } - constexpr operator ConstArrayView<T>() const { return {m_data, N}; } - - T m_data[N]; -}; - -template<typename T, typename... U> requires (std::is_same_v<T, U> and ...) -Array(T, U...) -> Array<T, 1 + sizeof...(U)>; - -template<typename T, size_t N, size_t... Indices> -constexpr Array<T, N> make_array(const T (&data)[N], std::index_sequence<Indices...>) -{ - static_assert(sizeof...(Indices) == N, "size mismatch"); - return {{data[Indices]...}}; -} - -template<typename T, size_t N> -constexpr Array<T, N> make_array(const T (&data)[N]) -{ - return make_array(data, std::make_index_sequence<N>()); -} - -template<typename T, size_t capacity> -struct ConstexprVector -{ - using iterator = T*; - using const_iterator = const T*; - - constexpr ConstexprVector() : m_size{0} {} - constexpr ConstexprVector(std::initializer_list<T> items) - : m_size{items.size()} - { - T* ptr = m_data; - for (auto& item : items) - *ptr++ = std::move(item); - } - - constexpr bool empty() const { return m_size == 0; } - constexpr size_t size() const { return m_size; } - - constexpr void resize(size_t n, const T& val = {}) - { - if (n >= capacity) - throw "capacity exceeded"; - for (int i = m_size; i < n; ++i) - m_data[i] = val; - m_size = n; - kak_assert(this->size() == m_size); // check for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79520 - } - - constexpr T& operator[](size_t i) { return m_data[i]; } - constexpr const T& operator[](size_t i) const { return m_data[i]; } - - constexpr iterator begin() { return m_data; } - constexpr iterator end() { return m_data + m_size; } - - constexpr const_iterator begin() const { return m_data; } - constexpr const_iterator end() const { return m_data + m_size; } - - size_t m_size; - T m_data[capacity] = {}; -}; - -} - -#endif // constexpr_utils_hh_INCLUDED diff --git a/src/context.cc b/src/context.cc index 46d9cd58..048da784 100644 --- a/src/context.cc +++ b/src/context.cc @@ -1,10 +1,8 @@ #include "context.hh" -#include "alias_registry.hh" #include "client.hh" -#include "face_registry.hh" +#include "scope.hh" #include "buffer_manager.hh" -#include "hook_manager.hh" #include "register_manager.hh" #include "window.hh" @@ -62,6 +60,12 @@ Scope& Context::scope(bool allow_local) const return GlobalScope::instance(); } +OptionManager& Context::options() const { return scope().options(); } +HookManager& Context::hooks() const { return scope().hooks(); } +KeymapManager& Context::keymaps() const { return scope().keymaps(); } +AliasRegistry& Context::aliases() const { return scope().aliases(); } +FaceRegistry& Context::faces(bool allow_local) const { return scope(allow_local).faces(); } + void Context::set_client(Client& client) { kak_assert(not has_client()); @@ -82,6 +86,12 @@ void Context::print_status(DisplayLine status) const client().print_status(std::move(status)); } +void Context::push_jump(bool force) +{ + if (force or not (m_flags & Flags::Draft)) + m_jump_list.push(selections()); +} + void JumpList::push(SelectionList jump, Optional<size_t> index) { if (index) @@ -428,4 +438,22 @@ void Context::set_name(String name) { String old_name = std::exchange(m_name, std::move(name)); hooks().run_hook(Hook::ClientRenamed, format("{}:{}", old_name, m_name), *this); } + +ScopedEdition::ScopedEdition(Context& context) + : m_context{context}, + m_buffer{context.has_buffer() ? &context.buffer() : nullptr} +{ if (m_buffer) m_context.begin_edition(); } + +ScopedEdition::~ScopedEdition() { if (m_buffer) m_context.end_edition(); } + +ScopedSelectionEdition::ScopedSelectionEdition(Context& context) + : m_context{context}, + m_buffer{not (m_context.flags() & Context::Flags::Draft) and context.has_buffer() ? &context.buffer() : nullptr} +{ if (m_buffer) m_context.m_selection_history.begin_edition(); } + +ScopedSelectionEdition::ScopedSelectionEdition(ScopedSelectionEdition&& other) : m_context{other.m_context}, m_buffer{other.m_buffer} +{ other.m_buffer = nullptr; } + +ScopedSelectionEdition::~ScopedSelectionEdition() { if (m_buffer) m_context.m_selection_history.end_edition(); } + } diff --git a/src/context.hh b/src/context.hh index fb45fc2d..ce17c3f7 100644 --- a/src/context.hh +++ b/src/context.hh @@ -10,14 +10,19 @@ namespace Kakoune { +class Context; class Window; class Buffer; class Client; class Scope; class InputHandler; class DisplayLine; -class KeymapManager; + class AliasRegistry; +class FaceRegistry; +class OptionManager; +class KeymapManager; +class HookManager; enum Direction { Backward = -1, Forward = 1 }; @@ -104,11 +109,11 @@ public: Scope& scope(bool allow_local = true) const; Scope* local_scope() const { return m_local_scopes.empty() ? nullptr : m_local_scopes.back(); } - OptionManager& options() const { return scope().options(); } - HookManager& hooks() const { return scope().hooks(); } - KeymapManager& keymaps() const { return scope().keymaps(); } - AliasRegistry& aliases() const { return scope().aliases(); } - FaceRegistry& faces(bool allow_local = true) const { return scope(allow_local).faces(); } + OptionManager& options() const; + HookManager& hooks() const; + KeymapManager& keymaps() const; + AliasRegistry& aliases() const; + FaceRegistry& faces(bool allow_local = true) const; void print_status(DisplayLine status) const; @@ -132,11 +137,7 @@ public: Flags flags() const { return m_flags; } JumpList& jump_list() { return m_jump_list; } - void push_jump(bool force = false) - { - if (force or not (m_flags & Flags::Draft)) - m_jump_list.push(selections()); - } + void push_jump(bool force = false); template<typename Func> void set_last_select(Func&& last_select) { m_last_select = std::forward<Func>(last_select); } @@ -215,12 +216,9 @@ private: struct ScopedEdition { - ScopedEdition(Context& context) - : m_context{context}, - m_buffer{context.has_buffer() ? &context.buffer() : nullptr} - { if (m_buffer) m_context.begin_edition(); } - - ~ScopedEdition() { if (m_buffer) m_context.end_edition(); } + ScopedEdition(Context& context); + ~ScopedEdition(); + ScopedEdition(const ScopedEdition&) = delete; Context& context() const { return m_context; } private: @@ -230,14 +228,10 @@ private: struct ScopedSelectionEdition { - ScopedSelectionEdition(Context& context) - : m_context{context}, - m_buffer{not (m_context.flags() & Context::Flags::Draft) and context.has_buffer() ? &context.buffer() : nullptr} - { if (m_buffer) m_context.m_selection_history.begin_edition(); } - ScopedSelectionEdition(ScopedSelectionEdition&& other) : m_context{other.m_context}, m_buffer{other.m_buffer} - { other.m_buffer = nullptr; } - - ~ScopedSelectionEdition() { if (m_buffer) m_context.m_selection_history.end_edition(); } + ScopedSelectionEdition(Context& context); + ScopedSelectionEdition(ScopedSelectionEdition&& other); + ~ScopedSelectionEdition(); + private: Context& m_context; SafePtr<Buffer> m_buffer; diff --git a/src/coord.hh b/src/coord.hh index 8420dcee..30c4ce74 100644 --- a/src/coord.hh +++ b/src/coord.hh @@ -41,18 +41,8 @@ struct LineAndColumn return *static_cast<EffectiveType*>(this); } - [[gnu::always_inline]] - constexpr friend auto operator<=>(const EffectiveType& lhs, const EffectiveType& rhs) - { - return (lhs.line != rhs.line) ? lhs.line <=> rhs.line - : lhs.column <=> rhs.column; - } - - [[gnu::always_inline]] - constexpr friend bool operator==(const EffectiveType& lhs, const EffectiveType& rhs) - { - return lhs.line == rhs.line and lhs.column == rhs.column; - } + constexpr friend auto operator<=>(const LineAndColumn& lhs, const LineAndColumn& rhs) = default; + constexpr friend bool operator==(const LineAndColumn& lhs, const LineAndColumn& rhs) = default; friend constexpr size_t hash_value(const EffectiveType& val) { @@ -65,6 +55,9 @@ struct BufferCoord : LineAndColumn<BufferCoord, LineCount, ByteCount> [[gnu::always_inline]] constexpr BufferCoord(LineCount line = 0, ByteCount column = 0) : LineAndColumn{line, column} {} + + constexpr friend auto operator<=>(const BufferCoord& lhs, const BufferCoord& rhs) = default; + constexpr friend bool operator==(const BufferCoord& lhs, const BufferCoord& rhs) = default; }; struct DisplayCoord : LineAndColumn<DisplayCoord, LineCount, ColumnCount> @@ -73,6 +66,9 @@ struct DisplayCoord : LineAndColumn<DisplayCoord, LineCount, ColumnCount> constexpr DisplayCoord(LineCount line = 0, ColumnCount column = 0) : LineAndColumn{line, column} {} + constexpr friend auto operator<=>(const DisplayCoord& lhs, const DisplayCoord& rhs) = default; + constexpr friend bool operator==(const DisplayCoord& lhs, const DisplayCoord& rhs) = default; + static constexpr const char* option_type_name = "coord"; }; diff --git a/src/debug.cc b/src/debug.cc new file mode 100644 index 00000000..55f7e4d3 --- /dev/null +++ b/src/debug.cc @@ -0,0 +1,39 @@ +#include "debug.hh" + +#include "buffer_manager.hh" +#include "buffer_utils.hh" +#include "utils.hh" + +namespace Kakoune +{ + +void write_to_debug_buffer(StringView str) +{ + if (not BufferManager::has_instance()) + { + write(2, str); + write(2, "\n"); + return; + } + + constexpr StringView debug_buffer_name = "*debug*"; + // Try to ensure we keep an empty line at the end of the debug buffer + // where the user can put its cursor to scroll with new messages + const bool eol_back = not str.empty() and str.back() == '\n'; + if (Buffer* buffer = BufferManager::instance().get_buffer_ifp(debug_buffer_name)) + { + buffer->flags() &= ~Buffer::Flags::ReadOnly; + auto restore = on_scope_end([buffer] { buffer->flags() |= Buffer::Flags::ReadOnly; }); + + buffer->insert(buffer->back_coord(), eol_back ? str : str + "\n"); + } + else + { + String line = str + (eol_back ? "\n" : "\n\n"); + create_buffer_from_string( + debug_buffer_name.str(), Buffer::Flags::NoUndo | Buffer::Flags::Debug | Buffer::Flags::ReadOnly, + line); + } +} + +} diff --git a/src/debug.hh b/src/debug.hh new file mode 100644 index 00000000..774d5710 --- /dev/null +++ b/src/debug.hh @@ -0,0 +1,39 @@ +#ifndef debug_hh_INCLUDED +#define debug_hh_INCLUDED + +#include "array.hh" +#include "enum.hh" + +namespace Kakoune +{ + +class StringView; + +enum class DebugFlags +{ + None = 0, + Hooks = 1 << 0, + Shell = 1 << 1, + Profile = 1 << 2, + Keys = 1 << 3, + Commands = 1 << 4, +}; + +constexpr bool with_bit_ops(Meta::Type<DebugFlags>) { return true; } + +constexpr auto enum_desc(Meta::Type<DebugFlags>) +{ + return make_array<EnumDesc<DebugFlags>>({ + { DebugFlags::Hooks, "hooks" }, + { DebugFlags::Shell, "shell" }, + { DebugFlags::Profile, "profile" }, + { DebugFlags::Keys, "keys" }, + { DebugFlags::Commands, "commands" }, + }); +} + +void write_to_debug_buffer(StringView str); + +} + +#endif // debug_hh_INCLUDED diff --git a/src/display_buffer.cc b/src/display_buffer.cc index 26c58754..bc934202 100644 --- a/src/display_buffer.cc +++ b/src/display_buffer.cc @@ -2,7 +2,6 @@ #include "assert.hh" #include "buffer.hh" -#include "buffer_utils.hh" #include "face_registry.hh" #include "utf8.hh" diff --git a/src/display_buffer.hh b/src/display_buffer.hh index c4ea6fb5..3a6ad0c2 100644 --- a/src/display_buffer.hh +++ b/src/display_buffer.hh @@ -7,7 +7,8 @@ #include "string.hh" #include "vector.hh" #include "hash_map.hh" -#include <functional> + +#include <algorithm> namespace Kakoune { @@ -137,7 +138,7 @@ public: template<typename It> iterator insert(iterator pos, It beg, It end) { - auto has_buffer_range = std::mem_fn(&DisplayAtom::has_buffer_range); + auto has_buffer_range = [](const DisplayAtom& atom) { return atom.has_buffer_range(); }; auto had_range = any_of(*this, has_buffer_range); if (auto first = std::find_if(beg, end, has_buffer_range); first != end) { diff --git a/src/event_manager.cc b/src/event_manager.cc index ed608fcc..01838de6 100644 --- a/src/event_manager.cc +++ b/src/event_manager.cc @@ -73,7 +73,7 @@ EventManager::~EventManager() kak_assert(m_timers.empty()); } -bool EventManager::handle_next_events(EventMode mode, sigset_t* sigmask, bool block) +bool EventManager::handle_next_events(EventMode mode, sigset_t* sigmask, Optional<Nanoseconds> timeout) { int max_fd = 0; fd_set rfds, wfds, efds; @@ -97,12 +97,10 @@ bool EventManager::handle_next_events(EventMode mode, sigset_t* sigmask, bool bl FD_SET(fd, &efds); } - bool with_timeout = false; if (m_has_forced_fd) - block = false; + timeout.reset(); - timespec ts{}; - if (block and not m_timers.empty()) + if (not m_timers.empty()) { auto next_date = (*std::min_element( m_timers.begin(), m_timers.end(), [](Timer* lhs, Timer* rhs) { @@ -111,15 +109,16 @@ bool EventManager::handle_next_events(EventMode mode, sigset_t* sigmask, bool bl if (next_date != TimePoint::max()) { - with_timeout = true; - using namespace std::chrono; using ns = std::chrono::nanoseconds; - auto nsecs = std::max(ns(0), duration_cast<ns>(next_date - Clock::now())); - auto secs = duration_cast<seconds>(nsecs); - ts = timespec{ (time_t)secs.count(), (long)(nsecs - secs).count() }; + auto remaining = std::max(Nanoseconds(0), + std::chrono::duration_cast<Nanoseconds>(next_date - Clock::now())); + timeout = timeout ? std::min(*timeout, remaining) : remaining; } } - int res = pselect(max_fd + 1, &rfds, &wfds, &efds, - not block or with_timeout ? &ts : nullptr, sigmask); + auto ts = timeout.map([](auto nsecs) { + auto secs = std::chrono::duration_cast<std::chrono::seconds>(nsecs); + return timespec{(time_t)secs.count(), (long)(nsecs - secs).count()}; + }); + int res = pselect(max_fd + 1, &rfds, &wfds, &efds, ts ? &*ts : nullptr, sigmask); // copy forced fds *after* select, so that signal handlers can write to // m_forced_fd, interupt select, and directly be serviced. @@ -164,7 +163,7 @@ void EventManager::force_signal(int fd) void EventManager::handle_urgent_events() { if (has_instance()) - instance().handle_next_events(EventMode::Urgent, nullptr, false); + instance().handle_next_events(EventMode::Urgent, nullptr, Nanoseconds{}); } diff --git a/src/event_manager.hh b/src/event_manager.hh index 68f6dee3..92d90105 100644 --- a/src/event_manager.hh +++ b/src/event_manager.hh @@ -4,6 +4,7 @@ #include "clock.hh" #include "meta.hh" #include "utils.hh" +#include "optional.hh" #include "vector.hh" #include <functional> @@ -87,10 +88,14 @@ private: class EventManager : public Singleton<EventManager> { public: + using Nanoseconds = std::chrono::nanoseconds; + EventManager(); ~EventManager(); - bool handle_next_events(EventMode mode, sigset_t* sigmask = nullptr, bool block = true); + // blocks until next event if no timeout given + bool handle_next_events(EventMode mode, sigset_t* sigmask = nullptr, + Optional<Nanoseconds> timeout = {}); // force the watchers associated with fd to be executed // on next handle_next_events call. diff --git a/src/face.hh b/src/face.hh index 1ed986ef..7e887705 100644 --- a/src/face.hh +++ b/src/face.hh @@ -9,19 +9,20 @@ namespace Kakoune enum class Attribute : int { - Normal = 0, - Underline = 1 << 1, - CurlyUnderline = 1 << 2, - Reverse = 1 << 3, - Blink = 1 << 4, - Bold = 1 << 5, - Dim = 1 << 6, - Italic = 1 << 7, - Strikethrough = 1 << 8, - FinalFg = 1 << 9, - FinalBg = 1 << 10, - FinalAttr = 1 << 11, - Final = FinalFg | FinalBg | FinalAttr + Normal = 0, + Underline = 1 << 1, + CurlyUnderline = 1 << 2, + DoubleUnderline = 1 << 3, + Reverse = 1 << 4, + Blink = 1 << 5, + Bold = 1 << 6, + Dim = 1 << 7, + Italic = 1 << 8, + Strikethrough = 1 << 9, + FinalFg = 1 << 10, + FinalBg = 1 << 11, + FinalAttr = 1 << 12, + Final = FinalFg | FinalBg | FinalAttr }; constexpr bool with_bit_ops(Meta::Type<Attribute>) { return true; } diff --git a/src/face_registry.cc b/src/face_registry.cc index d6c5cd53..aa997dde 100644 --- a/src/face_registry.cc +++ b/src/face_registry.cc @@ -2,7 +2,7 @@ #include "exception.hh" #include "ranges.hh" -#include "string_utils.hh" +#include "format.hh" namespace Kakoune { @@ -50,6 +50,7 @@ FaceSpec parse_face(StringView facedesc) { case 'u': face.attributes |= Attribute::Underline; break; case 'c': face.attributes |= Attribute::CurlyUnderline; break; + case 'U': face.attributes |= Attribute::DoubleUnderline; break; case 'r': face.attributes |= Attribute::Reverse; break; case 'b': face.attributes |= Attribute::Bold; break; case 'B': face.attributes |= Attribute::Blink; break; @@ -78,6 +79,7 @@ String to_string(Attribute attributes) attrs[] { { Attribute::Underline, "u" }, { Attribute::CurlyUnderline, "c" }, + { Attribute::DoubleUnderline, "U" }, { Attribute::Reverse, "r" }, { Attribute::Blink, "B" }, { Attribute::Bold, "b" }, diff --git a/src/face_registry.hh b/src/face_registry.hh index 88e52980..110e9769 100644 --- a/src/face_registry.hh +++ b/src/face_registry.hh @@ -2,7 +2,6 @@ #define face_registry_hh_INCLUDED #include "face.hh" -#include "utils.hh" #include "hash_map.hh" #include "ranges.hh" #include "string.hh" diff --git a/src/file.cc b/src/file.cc index ca2d2834..2fbf2e78 100644 --- a/src/file.cc +++ b/src/file.cc @@ -2,14 +2,13 @@ #include "assert.hh" #include "buffer.hh" +#include "client.hh" #include "exception.hh" #include "flags.hh" -#include "option_types.hh" #include "event_manager.hh" #include "ranked_match.hh" #include "regex.hh" #include "string.hh" -#include "unicode.hh" #include <limits> #include <cerrno> @@ -271,7 +270,7 @@ void write(int fd, StringView data) count -= written; } else if (errno == EAGAIN and not atomic and EventManager::has_instance()) - EventManager::instance().handle_next_events(EventMode::Urgent, nullptr, false); + EventManager::instance().handle_next_events(EventMode::Urgent, nullptr, std::chrono::nanoseconds{}); else throw file_access_error(format("fd: {}", fd), strerror(errno)); } @@ -279,10 +278,28 @@ void write(int fd, StringView data) template void write<true>(int fd, StringView data); template void write<false>(int fd, StringView data); +static int create_file(const Context& context, const char* filename) +{ + int fd; + const int flags = O_CREAT | O_WRONLY | O_TRUNC | (EventManager::has_instance() ? O_NONBLOCK : 0); + using namespace std::chrono; + BusyIndicator busy_indicator{context, [&](seconds elapsed) { + return DisplayLine{format("waiting to open file ({}s)", elapsed.count()), + context.faces()["Information"]}; + }}; + while ((fd = open(filename, flags, 0644)) == -1) + { + if (errno == ENXIO and EventManager::has_instance()) // trying to open a FIFO with no readers yet + EventManager::instance().handle_next_events(EventMode::Urgent, nullptr, nanoseconds{1'000'000}); + else + return -1; + } + return fd; +} -void write_to_file(StringView filename, StringView data) +void write_to_file(const Context& context, StringView filename, StringView data) { - const int fd = open(filename.zstr(), O_CREAT | O_WRONLY | O_TRUNC, 0644); + int fd = create_file(context, filename.zstr()); if (fd == -1) throw file_access_error(filename, strerror(errno)); auto close_fd = on_scope_end([fd]{ close(fd); }); @@ -332,7 +349,7 @@ int open_temp_file(StringView filename) return open_temp_file(filename, buffer); } -void write_buffer_to_file(Buffer& buffer, StringView filename, +void write_buffer_to_file(const Context& context, Buffer& buffer, StringView filename, WriteMethod method, WriteFlags flags) { auto zfilename = filename.zstr(); @@ -353,7 +370,7 @@ void write_buffer_to_file(Buffer& buffer, StringView filename, char temp_filename[PATH_MAX]; const int fd = replace ? open_temp_file(filename, temp_filename) - : open(zfilename, O_CREAT | O_WRONLY | O_TRUNC, 0644); + : create_file(context, zfilename); if (fd == -1) { auto saved_errno = errno; diff --git a/src/file.hh b/src/file.hh index cfc68ac2..a0b55e56 100644 --- a/src/file.hh +++ b/src/file.hh @@ -6,6 +6,7 @@ #include "meta.hh" #include "string.hh" #include "units.hh" +#include "array.hh" #include "vector.hh" #include <sys/types.h> @@ -41,7 +42,8 @@ String read_fd(int fd, bool text = false); String read_file(StringView filename, bool text = false); template<bool force_blocking = false> void write(int fd, StringView data); -void write_to_file(StringView filename, StringView data); +class Context; +void write_to_file(const Context&, StringView filename, StringView data); struct MappedFile { @@ -75,7 +77,7 @@ enum class WriteFlags }; constexpr bool with_bit_ops(Meta::Type<WriteFlags>) { return true; } -void write_buffer_to_file(Buffer& buffer, StringView filename, +void write_buffer_to_file(const Context& context, Buffer& buffer, StringView filename, WriteMethod method, WriteFlags flags); void write_buffer_to_fd(Buffer& buffer, int fd); void write_buffer_to_backup_file(Buffer& buffer); diff --git a/src/format.cc b/src/format.cc new file mode 100644 index 00000000..24f30681 --- /dev/null +++ b/src/format.cc @@ -0,0 +1,182 @@ +#include "format.hh" + +#include "exception.hh" +#include "string_utils.hh" + +#include <algorithm> +#include <charconv> +#include <cstdio> + +namespace Kakoune +{ + + +template<size_t N> +InplaceString<N> to_string_impl(auto val, auto format) +{ + InplaceString<N> res; + auto [end, errc] = std::to_chars(res.m_data, res.m_data + N, val, format); + if (errc != std::errc{}) + throw runtime_error("to_string error"); + res.m_length = end - res.m_data; + *end = '\0'; + return res; +} + +template<size_t N> +InplaceString<N> to_string_impl(auto val) +{ + return to_string_impl<N>(val, 10); +} + +InplaceString<15> to_string(int val) +{ + return to_string_impl<15>(val); +} + +InplaceString<15> to_string(unsigned val) +{ + return to_string_impl<15>(val); +} + +InplaceString<23> to_string(long int val) +{ + return to_string_impl<23>(val); +} + +InplaceString<23> to_string(long long int val) +{ + return to_string_impl<23>(val); +} + +InplaceString<23> to_string(unsigned long val) +{ + return to_string_impl<23>(val); +} + +InplaceString<23> to_string(Hex val) +{ + return to_string_impl<23>(val.val, 16); +} + +InplaceString<23> to_string(Grouped val) +{ + auto ungrouped = to_string_impl<23>(val.val); + + InplaceString<23> res; + for (int pos = 0, len = ungrouped.m_length; pos != len; ++pos) + { + if (res.m_length and ((len - pos) % 3) == 0) + res.m_data[res.m_length++] = ','; + res.m_data[res.m_length++] = ungrouped.m_data[pos]; + } + return res; +} + +InplaceString<23> to_string(float val) +{ +#if defined(__cpp_lib_to_chars) + return to_string_impl<23>(val, std::chars_format::general); +#else + InplaceString<23> res; + res.m_length = snprintf(res.m_data, 23, "%f", val); + return res; +#endif +} + +InplaceString<7> to_string(Codepoint c) +{ + InplaceString<7> res; + char* ptr = res.m_data; + utf8::dump(ptr, c); + res.m_length = (int)(ptr - res.m_data); + return res; +} + +template<typename AppendFunc> +void format_impl(StringView fmt, ArrayView<const StringView> params, AppendFunc append) +{ + int implicitIndex = 0; + for (auto it = fmt.begin(), end = fmt.end(); it != end;) + { + auto opening = std::find(it, end, '{'); + if (opening == end) + { + append(StringView{it, opening}); + break; + } + else if (opening != it and *(opening-1) == '\\') + { + append(StringView{it, opening-1}); + append('{'); + it = opening + 1; + } + else + { + append(StringView{it, opening}); + auto closing = std::find(opening, end, '}'); + if (closing == end) + throw runtime_error("format string error, unclosed '{'"); + + auto format = std::find(opening+1, closing, ':'); + const int index = opening+1 == format ? implicitIndex : str_to_int({opening+1, format}); + + if (index >= params.size()) + throw runtime_error("format string parameter index too big"); + + if (format != closing) + { + char padding = ' '; + if (*(++format) == '0') + { + padding = '0'; + ++format; + } + for (ColumnCount width = str_to_int({format, closing}), len = params[index].column_length(); + width > len; --width) + append(padding); + } + + append(params[index]); + implicitIndex = index+1; + it = closing+1; + } + } +} + +StringView format_to(ArrayView<char> buffer, StringView fmt, ArrayView<const StringView> params) +{ + char* ptr = buffer.begin(); + const char* end = buffer.end(); + format_impl(fmt, params, [&](StringView s) mutable { + for (auto c : s) + { + if (ptr == end) + throw runtime_error("buffer is too small"); + *ptr++ = c; + } + }); + if (ptr == end) + throw runtime_error("buffer is too small"); + *ptr = 0; + + return { buffer.begin(), ptr }; +} + +void format_with(FunctionRef<void (StringView)> append, StringView fmt, ArrayView<const StringView> params) +{ + format_impl(fmt, params, append); +} + +String format(StringView fmt, ArrayView<const StringView> params) +{ + ByteCount size = fmt.length(); + for (auto& s : params) size += s.length(); + String res; + res.reserve(size); + + format_impl(fmt, params, [&](StringView s) { res += s; }); + return res; +} + +} diff --git a/src/format.hh b/src/format.hh new file mode 100644 index 00000000..652bc477 --- /dev/null +++ b/src/format.hh @@ -0,0 +1,81 @@ +#ifndef format_hh_INCLUDED +#define format_hh_INCLUDED + +#include "string.hh" +#include "utils.hh" + +namespace Kakoune +{ + +template<size_t N> +struct InplaceString +{ + static_assert(N < 256, "InplaceString cannot handle sizes >= 256"); + + constexpr operator StringView() const { return {m_data, ByteCount{m_length}}; } + operator String() const { return {m_data, ByteCount{m_length}}; } + + unsigned char m_length{}; + char m_data[N]; +}; + +struct Hex { size_t val; }; +constexpr Hex hex(size_t val) { return {val}; } + +struct Grouped { size_t val; }; +constexpr Grouped grouped(size_t val) { return {val}; } + +InplaceString<15> to_string(int val); +InplaceString<15> to_string(unsigned val); +InplaceString<23> to_string(long int val); +InplaceString<23> to_string(unsigned long val); +InplaceString<23> to_string(long long int val); +InplaceString<23> to_string(Hex val); +InplaceString<23> to_string(Grouped val); +InplaceString<23> to_string(float val); +InplaceString<7> to_string(Codepoint c); + +template<typename RealType, typename ValueType> +decltype(auto) to_string(const StronglyTypedNumber<RealType, ValueType>& val) +{ + return to_string((ValueType)val); +} + +namespace detail +{ + +template<typename T> requires std::is_convertible_v<T, StringView> +StringView format_param(const T& val) { return val; } + +template<typename T> requires (not std::is_convertible_v<T, StringView>) +decltype(auto) format_param(const T& val) { return to_string(val); } + +} + +String format(StringView fmt, ArrayView<const StringView> params); + +template<typename... Types> +String format(StringView fmt, Types&&... params) +{ + return format(fmt, ArrayView<const StringView>{detail::format_param(std::forward<Types>(params))...}); +} + +StringView format_to(ArrayView<char> buffer, StringView fmt, ArrayView<const StringView> params); + +template<typename... Types> +StringView format_to(ArrayView<char> buffer, StringView fmt, Types&&... params) +{ + return format_to(buffer, fmt, ArrayView<const StringView>{detail::format_param(std::forward<Types>(params))...}); +} + +void format_with(FunctionRef<void (StringView)> append, StringView fmt, ArrayView<const StringView> params); + +template<typename... Types> +void format_with(FunctionRef<void (StringView)> append, StringView fmt, Types&&... params) +{ + return format_with(append, fmt, ArrayView<const StringView>{detail::format_param(std::forward<Types>(params))...}); +} + +} + +#endif // format_hh_INCLUDED diff --git a/src/hash_map.cc b/src/hash_map.cc index ae486490..c0afa4a2 100644 --- a/src/hash_map.cc +++ b/src/hash_map.cc @@ -2,10 +2,12 @@ #include "clock.hh" #include "string.hh" -#include "buffer_utils.hh" #include "unit_tests.hh" +#include "format.hh" +#include "debug.hh" #include <random> +#include <algorithm> #include <unordered_map> namespace Kakoune diff --git a/src/highlighter.cc b/src/highlighter.cc index 86da8ac6..7a6b2155 100644 --- a/src/highlighter.cc +++ b/src/highlighter.cc @@ -1,6 +1,7 @@ #include "highlighter.hh" -#include "buffer_utils.hh" +#include "debug.hh" +#include "flags.hh" namespace Kakoune { diff --git a/src/highlighter.hh b/src/highlighter.hh index 925dcaa7..73520c53 100644 --- a/src/highlighter.hh +++ b/src/highlighter.hh @@ -3,9 +3,7 @@ #include "coord.hh" #include "completion.hh" -#include "display_buffer.hh" -#include "exception.hh" -#include "flags.hh" +#include "range.hh" #include "hash_map.hh" #include "array_view.hh" #include "string.hh" @@ -18,6 +16,9 @@ namespace Kakoune { class Context; +class DisplayBuffer; + +using BufferRange = Range<BufferCoord>; enum class HighlightPass { diff --git a/src/highlighter_group.cc b/src/highlighter_group.cc index 813262c0..57bbd7d2 100644 --- a/src/highlighter_group.cc +++ b/src/highlighter_group.cc @@ -1,7 +1,9 @@ #include "highlighter_group.hh" +#include "flags.hh" +#include "format.hh" #include "ranges.hh" -#include "string_utils.hh" + namespace Kakoune { diff --git a/src/highlighters.cc b/src/highlighters.cc index 86985084..25f93245 100644 --- a/src/highlighters.cc +++ b/src/highlighters.cc @@ -2,10 +2,10 @@ #include "assert.hh" #include "buffer_utils.hh" +#include "debug.hh" #include "changes.hh" #include "command_manager.hh" #include "context.hh" -#include "clock.hh" #include "display_buffer.hh" #include "face_registry.hh" #include "highlighter_group.hh" @@ -506,10 +506,8 @@ std::unique_ptr<Highlighter> create_line_highlighter(HighlighterParameters param for (auto& atom : *it) { column += atom.length(); - if (!atom.has_buffer_range()) - continue; - - kak_assert(atom.begin().line == line); + if (atom.has_buffer_range() and atom.begin().line != line) + break; apply_face(face)(atom); } const ColumnCount remaining = context.context.window().dimensions().column - column; diff --git a/src/hook_manager.cc b/src/hook_manager.cc index a1b29434..ae3acf65 100644 --- a/src/hook_manager.cc +++ b/src/hook_manager.cc @@ -1,7 +1,6 @@ #include "hook_manager.hh" -#include "buffer_utils.hh" -#include "clock.hh" +#include "debug.hh" #include "command_manager.hh" #include "context.hh" #include "display_buffer.hh" diff --git a/src/hook_manager.hh b/src/hook_manager.hh index bee06064..97c768a3 100644 --- a/src/hook_manager.hh +++ b/src/hook_manager.hh @@ -1,11 +1,11 @@ #ifndef hook_manager_hh_INCLUDED #define hook_manager_hh_INCLUDED -#include "hash_map.hh" #include "completion.hh" #include "safe_ptr.hh" #include "meta.hh" #include "enum.hh" +#include "array.hh" #include <memory> diff --git a/src/input_handler.cc b/src/input_handler.cc index 2703b51a..0f2c8987 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -1,7 +1,7 @@ #include "input_handler.hh" -#include "buffer_manager.hh" -#include "buffer_utils.hh" +#include "buffer.hh" +#include "debug.hh" #include "command_manager.hh" #include "client.hh" #include "event_manager.hh" @@ -11,25 +11,17 @@ #include "option_types.hh" #include "regex.hh" #include "register_manager.hh" -#include "hash_map.hh" #include "user_interface.hh" -#include "utf8.hh" #include "window.hh" #include "word_db.hh" +#include <concepts> #include <utility> #include <limits> namespace Kakoune { -static void clear_info_and_echo(Context& context) -{ - context.print_status({}); - if (context.has_client()) - context.client().info_hide(); -} - class InputMode : public RefCountable { public: @@ -58,7 +50,7 @@ public: virtual std::pair<CursorMode, DisplayCoord> get_cursor_info() const { const auto cursor = context().selections().main().cursor(); - auto coord = context().window().display_position(cursor).value_or(DisplayCoord{}); + auto coord = context().window().display_coord(cursor).value_or(DisplayCoord{}); return {CursorMode::Buffer, coord}; } @@ -101,8 +93,6 @@ void InputMode::paste(StringView content) context().print_status({error.what().str(), context().faces()["Error"] }); context().hooks().run_hook(Hook::RuntimeError, error.what(), context()); } - - clear_info_and_echo(context()); } namespace InputModes @@ -126,8 +116,10 @@ struct MouseHandler return false; Buffer& buffer = context.buffer(); - BufferCoord cursor; - constexpr auto modifiers = Key::Modifiers::Control | Key::Modifiers::Alt | Key::Modifiers::Shift | Key::Modifiers::MouseButtonMask; + // bits above these potentially store additional information + constexpr auto mask = (Key::Modifiers) 0x7FF; + constexpr auto modifiers = Key::Modifiers::Control | Key::Modifiers::Alt | Key::Modifiers::Shift | Key::Modifiers::MouseButtonMask | ~mask; + switch ((key.modifiers & ~modifiers).value) { case Key::Modifiers::MousePress: @@ -135,20 +127,31 @@ struct MouseHandler { case Key::MouseButton::Right: { m_dragging.reset(); - cursor = context.window().buffer_coord(key.coord()); + auto cursor = context.window().buffer_coord(key.coord()); + if (not cursor) + { + context.ensure_cursor_visible = false; + return true; + } ScopedSelectionEdition selection_edition{context}; auto& selections = context.selections(); if (key.modifiers & Key::Modifiers::Control) - selections = {{selections.begin()->anchor(), cursor}}; + selections = {{selections.begin()->anchor(), *cursor}}; else - selections.main() = {selections.main().anchor(), cursor}; + selections.main() = {selections.main().anchor(), *cursor}; selections.sort_and_merge_overlapping(); return true; } case Key::MouseButton::Left: { m_dragging.reset(new ScopedSelectionEdition{context}); - m_anchor = context.window().buffer_coord(key.coord()); + auto anchor = context.window().buffer_coord(key.coord()); + if (not anchor) + { + context.ensure_cursor_visible = false; + return true; + } + m_anchor = *anchor; if (not (key.modifiers & Key::Modifiers::Control)) context.selections_write_only() = { buffer, m_anchor}; else @@ -166,34 +169,34 @@ struct MouseHandler } case Key::Modifiers::MouseRelease: { - if (not m_dragging) + auto cursor = context.window().buffer_coord(key.coord()); + if (not m_dragging or not cursor) { context.ensure_cursor_visible = false; return true; } auto& selections = context.selections(); - cursor = context.window().buffer_coord(key.coord()); - selections.main() = {buffer.clamp(m_anchor), cursor}; + selections.main() = {buffer.clamp(m_anchor), *cursor}; selections.sort_and_merge_overlapping(); m_dragging.reset(); return true; } case Key::Modifiers::MousePos: { - if (not m_dragging) + auto cursor = context.window().buffer_coord(key.coord()); + if (not m_dragging or not cursor) { context.ensure_cursor_visible = false; return true; } - cursor = context.window().buffer_coord(key.coord()); auto& selections = context.selections(); - selections.main() = {buffer.clamp(m_anchor), cursor}; + selections.main() = {buffer.clamp(m_anchor), *cursor}; selections.sort_and_merge_overlapping(); return true; } case Key::Modifiers::Scroll: - scroll_window(context, static_cast<int32_t>(key.key), m_dragging ? OnHiddenCursor::MoveCursor : OnHiddenCursor::PreserveSelections); + scroll_window(context, key.scroll_amount(), m_dragging ? OnHiddenCursor::MoveCursor : OnHiddenCursor::PreserveSelections); return true; default: return false; @@ -228,6 +231,9 @@ public: context().flags() & Context::Flags::Draft ? Timer::Callback{} : [this](Timer&) { RefPtr<InputMode> keep_alive{this}; // hook could trigger pop_mode() + if (context().has_client()) + context().client().clear_pending(); + context().hooks().run_hook(Hook::NormalIdle, "", context()); }}, m_fs_check_timer{TimePoint::max(), @@ -275,6 +281,8 @@ public: void on_key(Key key, bool) override { + bool should_clear = false; + kak_assert(m_state != State::PopOnEnabled); ScopedSetBool set_in_on_key{m_in_on_key}; @@ -291,7 +299,7 @@ public: if (m_mouse_handler.handle_key(key, context())) { - clear_info_and_echo(context()); + should_clear = true; if (not transient) m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context())); @@ -338,7 +346,7 @@ public: m_state = State::PopOnEnabled; }); - clear_info_and_echo(context()); + should_clear = true; // Hack to parse keys sent by terminals using the 8th bit to mark the // meta key. In normal mode, give priority to a potential alt-key than @@ -369,7 +377,11 @@ public: context().hooks().run_hook(Hook::NormalKey, to_string(key), context()); if (enabled() and not transient) // The hook might have changed mode + { + if (should_clear and context().has_client()) + context().client().schedule_clear(); m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context())); + } } ModeInfo mode_info() const override @@ -395,6 +407,17 @@ public: return {atoms, m_params}; } + void paste(StringView content) override + { + InputMode::paste(content); + if (not (context().flags() & Context::Flags::Draft)) + { + if (context().has_client()) + context().client().schedule_clear(); + m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context())); + } + } + KeymapMode keymap_mode() const override { return KeymapMode::Normal; } StringView name() const override { return "normal"; } @@ -669,7 +692,7 @@ public: Timer::Callback{} : [this](Timer&) { RefPtr<InputMode> keep_alive{this}; // hook or m_callback could trigger pop_mode() if (m_auto_complete and m_refresh_completion_pending) - refresh_completions(CompletionFlags::Fast); + refresh_completions(); if (m_line_changed) { m_callback(m_line_editor.line(), PromptEvent::Change, context()); @@ -806,10 +829,10 @@ public: CandidateList& candidates = m_completions.candidates; if (m_auto_complete and m_refresh_completion_pending) - refresh_completions(CompletionFlags::Fast); + refresh_completions(); if (candidates.empty()) // manual completion, we need to ask our completer for completions { - refresh_completions(CompletionFlags::None); + refresh_completions(); if ((not m_prefix_in_completions and candidates.size() > 1) or candidates.size() > 2) return; @@ -867,7 +890,7 @@ public: }); if (m_explicit_completer) - refresh_completions(CompletionFlags::None); + refresh_completions(); }, "enter completion type", "f: filename\n" "w: buffer word\n"); @@ -878,7 +901,7 @@ public: m_auto_complete = not m_auto_complete; if (m_auto_complete) - refresh_completions(CompletionFlags::Fast); + refresh_completions(); else if (context().has_client()) { clear_completions(); @@ -973,7 +996,7 @@ private: template<typename Completer> void use_explicit_completer(Completer&& completer) { - m_explicit_completer = [completer](const Context& context, CompletionFlags flags, StringView content, ByteCount cursor_pos) { + m_explicit_completer = [completer](const Context& context, StringView content, ByteCount cursor_pos) { Optional<Token> last_token; CommandParser parser{content.substr(0_byte, cursor_pos)}; while (auto token = parser.read_token(false)) @@ -989,7 +1012,7 @@ private: }; } - void refresh_completions(CompletionFlags flags) + void refresh_completions() { try { @@ -999,7 +1022,7 @@ private: return; m_current_completion = -1; const String& line = m_line_editor.line(); - m_completions = completer(context(), flags, line, + m_completions = completer(context(), line, line.byte_count_to(m_line_editor.cursor_pos())); if (not context().has_client()) return; @@ -1290,9 +1313,13 @@ public: selections.sort_and_merge_overlapping(); } else if (auto cp = key.codepoint()) + { + m_completer.try_accept(); insert(*cp); + } else if (key == ctrl('r')) { + m_completer.try_accept(); on_next_key_with_autoinfo(context(), "register", KeymapMode::None, [this](Key key, Context&) { auto cp = key.codepoint(); @@ -1346,6 +1373,7 @@ public: } else if (key == ctrl('v')) { + m_completer.try_accept(); on_next_key_with_autoinfo(context(), "raw-insert", KeymapMode::None, [this, transient](Key key, Context&) { if (auto cp = get_raw_codepoint(key)) @@ -1374,6 +1402,7 @@ public: void paste(StringView content) override { + m_completer.try_accept(); insert(ConstArrayView<StringView>{content}); m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context())); } @@ -1411,7 +1440,7 @@ private: template<typename S> void insert(ConstArrayView<S> strings) { - m_completer.try_accept(); + kak_assert(not m_completer.has_candidate_selected()); context().selections().for_each([strings, &buffer=context().buffer()] (size_t index, Selection& sel) { Kakoune::insert(buffer, sel, sel.cursor(), strings[std::min(strings.size()-1, index)]); diff --git a/src/input_handler.hh b/src/input_handler.hh index dd475c92..32e9bfca 100644 --- a/src/input_handler.hh +++ b/src/input_handler.hh @@ -2,9 +2,10 @@ #define input_handler_hh_INCLUDED #include "completion.hh" -#include "constexpr_utils.hh" +#include "array.hh" #include "context.hh" #include "env_vars.hh" +#include "enum.hh" #include "face.hh" #include "normal.hh" #include "optional.hh" @@ -41,8 +42,7 @@ class InputMode; enum class KeymapMode : char; enum class CursorMode; -using PromptCompleter = std::function<Completions (const Context&, CompletionFlags, - StringView, ByteCount)>; +using PromptCompleter = std::function<Completions (const Context&, StringView, ByteCount)>; enum class InsertMode : unsigned { Insert, diff --git a/src/insert_completer.cc b/src/insert_completer.cc index 73c08a43..49bbd4ca 100644 --- a/src/insert_completer.cc +++ b/src/insert_completer.cc @@ -2,6 +2,7 @@ #include "buffer_manager.hh" #include "buffer_utils.hh" +#include "debug.hh" #include "client.hh" #include "command_manager.hh" #include "changes.hh" @@ -16,7 +17,6 @@ #include "utf8_iterator.hh" #include "user_interface.hh" -#include <numeric> #include <utility> namespace Kakoune @@ -481,12 +481,12 @@ auto& get_last(BufferRange& range) { return range.end; } bool InsertCompleter::has_candidate_selected() const { - return m_current_candidate >= 0 and m_current_candidate < m_completions.candidates.size() - 1; + return m_completions.is_valid() and m_current_candidate >= 0 and m_current_candidate < m_completions.candidates.size() - 1; } void InsertCompleter::try_accept() { - if (m_completions.is_valid() and has_candidate_selected()) + if (has_candidate_selected()) reset(); } diff --git a/src/insert_completer.hh b/src/insert_completer.hh index c624639d..713cdedb 100644 --- a/src/insert_completer.hh +++ b/src/insert_completer.hh @@ -90,6 +90,8 @@ public: void explicit_line_buffer_complete(); void explicit_line_all_complete(); + bool has_candidate_selected() const; + private: bool setup_ifn(); @@ -98,7 +100,6 @@ private: void on_option_changed(const Option& opt) override; void menu_show(); - bool has_candidate_selected() const; Context& m_context; OptionManager& m_options; diff --git a/src/json.cc b/src/json.cc index 7323e2fe..6b297dff 100644 --- a/src/json.cc +++ b/src/json.cc @@ -4,7 +4,9 @@ #include "string_utils.hh" #include "unit_tests.hh" #include "utils.hh" +#include "ranges.hh" +#include <algorithm> #include <cstdio> namespace Kakoune diff --git a/src/json.hh b/src/json.hh index 84c14d36..6765c793 100644 --- a/src/json.hh +++ b/src/json.hh @@ -3,6 +3,7 @@ #include "hash_map.hh" #include "string.hh" +#include "string_utils.hh" #include "value.hh" namespace Kakoune diff --git a/src/json_ui.cc b/src/json_ui.cc index 01768897..7f645605 100644 --- a/src/json_ui.cc +++ b/src/json_ui.cc @@ -8,6 +8,7 @@ #include "keys.hh" #include "ranges.hh" #include "string_utils.hh" +#include "format.hh" #include <cstdio> #include <utility> @@ -39,6 +40,7 @@ String to_json(Attribute attributes) attrs[] { { Attribute::Underline, "underline" }, { Attribute::CurlyUnderline, "curly_underline" }, + { Attribute::DoubleUnderline, "double_underline" }, { Attribute::Reverse, "reverse" }, { Attribute::Blink, "blink" }, { Attribute::Bold, "bold" }, @@ -282,12 +284,12 @@ void JsonUI::eval_json(const Value& json) } else if (method == "scroll") { - if (params.size() != 1) - throw invalid_rpc_request("scroll needs an amount"); - else if (not params[0].is_a<int>()) - throw invalid_rpc_request("scroll amount is not an integer"); - m_on_key({Key::Modifiers::Scroll, (Codepoint)params[0].as<int>()}); - + if (params.size() != 3) + throw invalid_rpc_request("scroll needs an amount and coordinates"); + else if (not params[0].is_a<int>() or not params[1].is_a<int>() or not params[2].is_a<int>()) + throw invalid_rpc_request("scroll parameters are not integers"); + m_on_key({Key::Modifiers::Scroll | (Key::Modifiers)(params[0].as<int>() << 16), + encode_coord({params[1].as<int>(), params[2].as<int>()})}); } else if (method == "menu_select") { diff --git a/src/keymap_manager.cc b/src/keymap_manager.cc index 582a271d..1d05d50c 100644 --- a/src/keymap_manager.cc +++ b/src/keymap_manager.cc @@ -1,11 +1,9 @@ #include "keymap_manager.hh" -#include "array_view.hh" #include "assert.hh" #include "exception.hh" -#include "string_utils.hh" - -#include <algorithm> +#include "format.hh" +#include "ranges.hh" namespace Kakoune { diff --git a/src/keymap_manager.hh b/src/keymap_manager.hh index 4819be05..233ed08b 100644 --- a/src/keymap_manager.hh +++ b/src/keymap_manager.hh @@ -1,12 +1,9 @@ #ifndef keymap_manager_hh_INCLUDED #define keymap_manager_hh_INCLUDED -#include "array_view.hh" #include "keys.hh" -#include "hash.hh" #include "string.hh" #include "hash_map.hh" -#include "utils.hh" #include "vector.hh" namespace Kakoune diff --git a/src/keys.cc b/src/keys.cc index 5ba44181..4170ce98 100644 --- a/src/keys.cc +++ b/src/keys.cc @@ -5,7 +5,7 @@ #include "string.hh" #include "unit_tests.hh" #include "utf8_iterator.hh" -#include "utils.hh" +#include "format.hh" #include "string_utils.hh" namespace Kakoune @@ -196,7 +196,7 @@ String to_string(Key key) else if (key.modifiers & Key::Modifiers::MouseRelease) res = format("mouse:release:{}:{}.{}", key.mouse_button(), coord.line, coord.column); else if (key.modifiers & Key::Modifiers::Scroll) - res = format("scroll:{}", static_cast<int>(key.key)); + res = format("scroll:{}:{}.{}", key.scroll_amount(), coord.line, coord.column); else if (key.modifiers & Key::Modifiers::Resize) res = format("resize:{}.{}", coord.line, coord.column); else diff --git a/src/keys.hh b/src/keys.hh index ccafe336..3c5d7f4b 100644 --- a/src/keys.hh +++ b/src/keys.hh @@ -89,8 +89,9 @@ struct Key constexpr bool operator==(Key other) const { return val() == other.val(); } constexpr auto operator<=>(Key other) const { return val() <=> other.val(); } - constexpr DisplayCoord coord() const { return {(int)((key & 0xFFFF0000) >> 16), (int)(key & 0x0000FFFF)}; } + constexpr DisplayCoord coord() const { return {(int)((int32_t) (key & 0xFFFF0000) >> 16), (int)(key & 0x0000FFFF)}; } constexpr MouseButton mouse_button() { return MouseButton{((int)modifiers & (int)Modifiers::MouseButtonMask) >> 6}; } + constexpr int scroll_amount() { return (int32_t)modifiers >> 16; } static Modifiers to_modifier(MouseButton button) { return Key::Modifiers{((int)button << 6) & (int)Modifiers::MouseButtonMask}; } Optional<Codepoint> codepoint() const; diff --git a/src/main.cc b/src/main.cc index a10ec8dd..f0c6aad1 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,12 +1,13 @@ #include "assert.hh" #include "backtrace.hh" #include "buffer.hh" -#include "buffer_manager.hh" #include "buffer_utils.hh" +#include "buffer_manager.hh" #include "client_manager.hh" #include "command_manager.hh" #include "commands.hh" #include "context.hh" +#include "debug.hh" #include "event_manager.hh" #include "face_registry.hh" #include "file.hh" @@ -27,7 +28,6 @@ #include "string.hh" #include "unit_tests.hh" #include "window.hh" -#include "clock.hh" #include <fcntl.h> #include <locale.h> @@ -47,6 +47,8 @@ struct { } constexpr version_notes[] = { { 0, "» kak_* appearing in shell arguments will be added to the environment\n" + "» {+U}double underline{} support\n" + "» {+u}git apply{} can stage/revert selected changes to current buffer\n" }, { 20240518, "» Fix tests failing on some platforms\n" @@ -594,6 +596,7 @@ void register_options() " terminal_assistant clippy|cat|dilbert|none|off\n" " terminal_status_on_top bool\n" " terminal_set_title bool\n" + " terminal_title str\n" " terminal_enable_mouse bool\n" " terminal_synchronized bool\n" " terminal_wheel_scroll_amount int\n" @@ -686,11 +689,22 @@ pid_t fork_server_to_background() std::unique_ptr<UserInterface> create_local_ui(UIType ui_type) { + if (ui_type == UIType::Terminal and not isatty(0)) + { + // move stdin to another fd, and restore tty as stdin + int fd = dup(0); + int tty = open("/dev/tty", O_RDONLY); + dup2(tty, 0); + close(tty); + create_fifo_buffer("*stdin*", fd, Buffer::Flags::None, AutoScroll::NotInitially); + } + auto ui = make_ui(ui_type); static SignalHandler old_handler = set_signal_handler(SIGTSTP, [](int sig) { if (ClientManager::instance().count() == 1 and - *ClientManager::instance().begin() == local_client) + *ClientManager::instance().begin() == local_client and + not Server::instance().is_daemon()) old_handler(sig); else { @@ -818,11 +832,23 @@ int run_server(StringView session, StringView server_init, " {}", error.what())); } + { + Context empty_context{Context::EmptyContextFlag{}}; + global_scope.hooks().run_hook(Hook::EnterDirectory, real_path("."), empty_context); + global_scope.hooks().run_hook(Hook::KakBegin, session, empty_context); + } + if (not server_init.empty()) try { Context init_context{Context::EmptyContextFlag{}}; command_manager.execute(server_init, init_context); } + catch (const kill_session& kill) + { + Context empty_context{Context::EmptyContextFlag{}}; + global_scope.hooks().run_hook(Hook::KakEnd, "", empty_context); + return kill.exit_status; + } catch (runtime_error& error) { startup_error = true; @@ -830,12 +856,6 @@ int run_server(StringView session, StringView server_init, " {}", error.what())); } - { - Context empty_context{Context::EmptyContextFlag{}}; - global_scope.hooks().run_hook(Hook::EnterDirectory, real_path("."), empty_context); - global_scope.hooks().run_hook(Hook::KakBegin, session, empty_context); - } - if (not files.empty()) try { for (auto& file : files) @@ -899,14 +919,16 @@ int run_server(StringView session, StringView server_init, // Loop so that eventual inputs happening during the processing are handled as // well, avoiding unneeded redraws. - bool allow_blocking = not client_manager.has_pending_inputs(); + Optional<std::chrono::nanoseconds> timeout; + if (client_manager.has_pending_inputs()) + timeout = std::chrono::nanoseconds{}; try { - while (event_manager.handle_next_events(EventMode::Normal, nullptr, allow_blocking)) + while (event_manager.handle_next_events(EventMode::Normal, nullptr, timeout)) { if (client_manager.process_pending_inputs()) break; - allow_blocking = false; + timeout = std::chrono::nanoseconds{}; } } catch (const cancel&) {} @@ -996,14 +1018,15 @@ int run_filter(StringView keystr, ConstArrayView<StringView> files, bool quiet, } }; + Context empty_context{Context::EmptyContextFlag{}}; for (auto& file : files) { Buffer* buffer = open_file_buffer(file, Buffer::Flags::NoHooks); if (not suffix_backup.empty()) - write_buffer_to_file(*buffer, buffer->name() + suffix_backup, + write_buffer_to_file(empty_context, *buffer, buffer->name() + suffix_backup, WriteMethod::Overwrite, WriteFlags::None); apply_to_buffer(*buffer); - write_buffer_to_file(*buffer, buffer->name(), + write_buffer_to_file(empty_context, *buffer, buffer->name(), WriteMethod::Overwrite, WriteFlags::None); buffer_manager.delete_buffer(*buffer); } diff --git a/src/memory.hh b/src/memory.hh index 8eff2d54..da1ca597 100644 --- a/src/memory.hh +++ b/src/memory.hh @@ -3,7 +3,6 @@ #include <cstddef> #include <new> -#include <utility> #include "assert.hh" #include "meta.hh" diff --git a/src/meta.hh b/src/meta.hh index 15429ca8..151f1020 100644 --- a/src/meta.hh +++ b/src/meta.hh @@ -1,17 +1,12 @@ #ifndef meta_hh_INCLUDED #define meta_hh_INCLUDED -namespace Kakoune -{ -inline namespace Meta +namespace Kakoune::inline Meta { struct AnyType{}; template<typename T> struct Type : AnyType {}; -template<typename T> using void_t = void; - -} } #endif // meta_hh_INCLUDED diff --git a/src/normal.cc b/src/normal.cc index ea183bc4..6aa5b703 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -4,9 +4,7 @@ #include "buffer_manager.hh" #include "buffer_utils.hh" #include "changes.hh" -#include "client_manager.hh" #include "command_manager.hh" -#include "commands.hh" #include "context.hh" #include "diff.hh" #include "enum.hh" @@ -493,9 +491,9 @@ void command(const Context& context, EnvVarMap env_vars, char reg = 0) ":", {}, default_command, context.faces()["Prompt"], PromptFlags::DropHistoryEntriesWithBlankPrefix, ':', - [completer=CommandManager::Completer{}](const Context& context, CompletionFlags flags, + [completer=CommandManager::Completer{}](const Context& context, StringView cmd_line, ByteCount pos) mutable { - return completer(context, flags, cmd_line, pos); + return completer(context, cmd_line, pos); }, [env_vars = std::move(env_vars), default_command](StringView cmdline, PromptEvent event, Context& context) { if (context.has_client()) @@ -537,9 +535,8 @@ void command(Context& context, NormalParams params) command(context, std::move(env_vars), params.reg); } -BufferCoord apply_diff(Buffer& buffer, BufferCoord pos, StringView before, StringView after) +BufferCoord apply_diff(Buffer& buffer, BufferCoord pos, ArrayView<StringView> lines_before, StringView after) { - const auto lines_before = before | split_after<StringView>('\n') | gather<Vector<StringView>>(); const auto lines_after = after | split_after<StringView>('\n') | gather<Vector<StringView>>(); auto byte_count = [](auto&& lines, int first, int count) { @@ -603,26 +600,38 @@ void pipe(Context& context, NormalParams params) SelectionList selections = context.selections(); for (auto& sel : selections) { - const auto beg = changes_tracker.get_new_coord_tolerant(sel.min()); - const auto end = changes_tracker.get_new_coord_tolerant(sel.max()); + const auto first = changes_tracker.get_new_coord_tolerant(sel.min()); + const auto last = changes_tracker.get_new_coord_tolerant(sel.max()); + + Vector<StringView> in_lines; + for (auto line = first.line; line <= last.line; ++line) + { + auto content = buffer[line]; + if (line == last.line) + content = content.substr(0, last.column + utf8::codepoint_size(content[last.column])); + if (line == first.line) + content = content.substr(first.column); + in_lines.push_back(content); + } - String in = buffer.string(beg, buffer.char_next(end)); // Needed in case we read selections inside the cmdline - context.selections_write_only().set({keep_direction(Selection{beg, end}, sel)}, 0); + context.selections_write_only().set({keep_direction(Selection{first, last}, sel)}, 0); String out = ShellManager::instance().eval( - cmdline, context, in, - ShellManager::Flags::WaitForStdout).first; + cmdline, context, + [it = in_lines.begin(), end = in_lines.end()]() mutable { + return (it != end) ? *it++ : StringView{}; + }, ShellManager::Flags::WaitForStdout).first; - if (in.back() != '\n' and not out.empty() and out.back() == '\n') + if (in_lines.back().back() != '\n' and not out.empty() and out.back() == '\n') out.resize(out.length()-1, 0); - auto new_end = apply_diff(buffer, beg, in, out); - if (new_end != beg) + auto new_end = apply_diff(buffer, first, in_lines, out); + if (new_end != first) { auto& min = sel.min(); auto& max = sel.max(); - min = beg; + min = first; max = buffer.char_prev(new_end); } else @@ -833,7 +842,7 @@ void regex_prompt(Context& context, String prompt, char reg, RegexMode mode, Fun context.input_handler().prompt( std::move(prompt), {}, default_regex, context.faces()["Prompt"], PromptFlags::Search, reg, - [](const Context& context, CompletionFlags, StringView regex, ByteCount pos) -> Completions { + [](const Context& context, StringView regex, ByteCount pos) -> Completions { auto current_word = [](StringView s) { auto it = s.end(); while (it != s.begin() and is_word(*(it-1))) @@ -1841,7 +1850,7 @@ SelectionList read_selections_from_register(char reg, const Context& context) const auto buffer_name = StringView{ content[0].begin (), end_content[1].begin () - 1 }; Buffer& buffer = BufferManager::instance().get_buffer(buffer_name); - return selection_list_from_strings(buffer, ColumnType::Byte, content | skip(1), timestamp, main); + return selection_list_from_strings(buffer, ColumnType::Byte, content.subrange(1), timestamp, main); } enum class CombineOp diff --git a/src/option.hh b/src/option.hh index 732fefca..a27fc256 100644 --- a/src/option.hh +++ b/src/option.hh @@ -1,10 +1,10 @@ #ifndef option_hh_INCLUDED #define option_hh_INCLUDED -#include "enum.hh" +#include "exception.hh" #include "meta.hh" +#include "string.hh" #include "vector.hh" -#include "constexpr_utils.hh" namespace Kakoune { @@ -64,29 +64,6 @@ struct PrefixedList template<typename T> using TimestampedList = PrefixedList<size_t, T>; -enum class DebugFlags -{ - None = 0, - Hooks = 1 << 0, - Shell = 1 << 1, - Profile = 1 << 2, - Keys = 1 << 3, - Commands = 1 << 4, -}; - -constexpr bool with_bit_ops(Meta::Type<DebugFlags>) { return true; } - -constexpr auto enum_desc(Meta::Type<DebugFlags>) -{ - return make_array<EnumDesc<DebugFlags>>({ - { DebugFlags::Hooks, "hooks" }, - { DebugFlags::Shell, "shell" }, - { DebugFlags::Profile, "profile" }, - { DebugFlags::Keys, "keys" }, - { DebugFlags::Commands, "commands" }, - }); -} - } #endif // option_hh_INCLUDED diff --git a/src/option_manager.hh b/src/option_manager.hh index 3a4bb5a5..c36aeb6d 100644 --- a/src/option_manager.hh +++ b/src/option_manager.hh @@ -5,9 +5,10 @@ #include "exception.hh" #include "hash_map.hh" #include "option.hh" +#include "option_types.hh" #include "ranges.hh" -#include "utils.hh" #include "vector.hh" +#include "format.hh" #include "string_utils.hh" #include <memory> diff --git a/src/option_types.cc b/src/option_types.cc index a1eeb418..f005c1db 100644 --- a/src/option_types.cc +++ b/src/option_types.cc @@ -1,4 +1,5 @@ #include "option_types.hh" +#include "debug.hh" #include "unit_tests.hh" namespace Kakoune diff --git a/src/option_types.hh b/src/option_types.hh index 40d367df..f6f89e8a 100644 --- a/src/option_types.hh +++ b/src/option_types.hh @@ -7,10 +7,11 @@ #include "flags.hh" #include "hash_map.hh" #include "option.hh" -#include "ranges.hh" #include "string.hh" #include "string_utils.hh" +#include "format.hh" #include "units.hh" +#include "ranges.hh" #include <tuple> #include <vector> diff --git a/src/parameters_parser.cc b/src/parameters_parser.cc index 75575323..5d2858ed 100644 --- a/src/parameters_parser.cc +++ b/src/parameters_parser.cc @@ -1,5 +1,6 @@ #include "parameters_parser.hh" +#include "ranges.hh" #include "flags.hh" namespace Kakoune diff --git a/src/parameters_parser.hh b/src/parameters_parser.hh index c11dc501..1b91b6fc 100644 --- a/src/parameters_parser.hh +++ b/src/parameters_parser.hh @@ -6,9 +6,8 @@ #include "meta.hh" #include "array_view.hh" #include "optional.hh" -#include "flags.hh" #include "string.hh" -#include "string_utils.hh" +#include "format.hh" #include <functional> @@ -41,9 +40,7 @@ struct wrong_argument_count : public parameter_error class Context; struct Completions; -enum class CompletionFlags; -using ArgCompleter = std::function<Completions (const Context&, CompletionFlags, - StringView, ByteCount)>; +using ArgCompleter = std::function<Completions (const Context&, StringView, ByteCount)>; struct SwitchDesc { diff --git a/src/profile.hh b/src/profile.hh index 7feff017..6c121731 100644 --- a/src/profile.hh +++ b/src/profile.hh @@ -1,8 +1,10 @@ #ifndef profile_hh_INCLUDED #define profile_hh_INCLUDED -#include "option.hh" #include "clock.hh" +#include "context.hh" +#include "debug.hh" +#include "option_manager.hh" namespace Kakoune { diff --git a/src/range.hh b/src/range.hh index a36386f2..6e72ffba 100644 --- a/src/range.hh +++ b/src/range.hh @@ -1,6 +1,8 @@ #ifndef range_hh_INCLUDED #define range_hh_INCLUDED +#include <cstddef> + namespace Kakoune { diff --git a/src/ranges.hh b/src/ranges.hh index 7b568cd2..af13d6f8 100644 --- a/src/ranges.hh +++ b/src/ranges.hh @@ -5,9 +5,8 @@ #include <utility> #include <iterator> #include <numeric> -#include <tuple> -#include "constexpr_utils.hh" +#include "array.hh" namespace Kakoune { @@ -191,7 +190,13 @@ struct EnumerateView Iterator(size_t index, RangeIt it) : m_index{index}, m_it{std::move(it)} {} - decltype(auto) operator*() { return std::tuple<size_t, decltype(*m_it)>(m_index, *m_it); } + struct ValueType + { + size_t index; + decltype(*std::declval<RangeIt>()) element; + }; + + ValueType operator*() { return {m_index, *m_it}; } Iterator& operator++() { ++m_index; ++m_it; return *this; } Iterator operator++(int) { auto copy = *this; ++(*this); return copy; } diff --git a/src/ranked_match.cc b/src/ranked_match.cc index a13decdf..87bcece7 100644 --- a/src/ranked_match.cc +++ b/src/ranked_match.cc @@ -4,6 +4,7 @@ #include "unit_tests.hh" #include "utf8_iterator.hh" #include "optional.hh" +#include "ranges.hh" #include <algorithm> diff --git a/src/ref_ptr.hh b/src/ref_ptr.hh index 4b576039..fcbd6a71 100644 --- a/src/ref_ptr.hh +++ b/src/ref_ptr.hh @@ -1,8 +1,6 @@ #ifndef ref_ptr_hh_INCLUDED #define ref_ptr_hh_INCLUDED -#include <utility> - namespace Kakoune { @@ -34,7 +32,7 @@ struct RefPtr ~RefPtr() noexcept { release(); } RefPtr(const RefPtr& other) : m_ptr(other.m_ptr) { acquire(); } RefPtr(RefPtr&& other) - noexcept(noexcept(std::declval<RefPtr>().moved(nullptr))) + noexcept(noexcept(moved(nullptr))) : m_ptr(other.m_ptr) { other.m_ptr = nullptr; moved(&other); } RefPtr& operator=(const RefPtr& other) diff --git a/src/regex_impl.cc b/src/regex_impl.cc index 9b307f23..0789ef6d 100644 --- a/src/regex_impl.cc +++ b/src/regex_impl.cc @@ -1,14 +1,14 @@ #include "regex_impl.hh" -#include "exception.hh" #include "string.hh" #include "unicode.hh" #include "unit_tests.hh" #include "utf8.hh" #include "utf8_iterator.hh" -#include "string_utils.hh" +#include "format.hh" #include "vector.hh" #include "utils.hh" +#include "ranges.hh" #include <cstdio> #include <cstring> @@ -19,6 +19,9 @@ namespace Kakoune constexpr Codepoint CompiledRegex::StartDesc::count; +namespace +{ + struct ParsedRegex { enum Op : char @@ -73,9 +76,6 @@ struct ParsedRegex uint32_t capture_count; }; -namespace -{ - template<RegexMode mode = RegexMode::Forward> struct Children { @@ -123,12 +123,14 @@ struct Children const Index m_index; }; -} // Recursive descent parser based on naming used in the ECMAScript // standard, although the syntax is not fully compatible. struct RegexParser { + static ParsedRegex parse(StringView re) { return RegexParser{re}.m_parsed_regex; } + +private: RegexParser(StringView re) : m_regex{re}, m_pos{re.begin(), re} { @@ -138,11 +140,6 @@ struct RegexParser kak_assert(root == 0); } - ParsedRegex get_parsed_regex() { return std::move(m_parsed_regex); } - - static ParsedRegex parse(StringView re) { return RegexParser{re}.get_parsed_regex(); } - -private: struct InvalidPolicy { Codepoint operator()(Codepoint cp) const { throw regex_error{"Invalid utf8 in regex"}; } @@ -1034,6 +1031,9 @@ private: not contains(start_desc.map, false)) return nullptr; + if (std::count(std::begin(start_desc.map), std::end(start_desc.map), true) == 1) + start_desc.start_byte = find(start_desc.map, true) - std::begin(start_desc.map); + return std::make_unique<CompiledRegex::StartDesc>(start_desc); } @@ -1084,6 +1084,8 @@ private: ParsedRegex& m_parsed_regex; }; +} + String dump_regex(const CompiledRegex& program) { String res; @@ -1595,6 +1597,17 @@ auto test_regex = UnitTest{[]{ } { + TestVM<RegexMode::Backward | RegexMode::Search> vm{"oo(.{3,4}|f)"}; + kak_assert(vm.backward_start_desc and vm.backward_start_desc->offset == 4); + for (int c = 0; c < CompiledRegex::StartDesc::count; ++c) + kak_assert(vm.backward_start_desc->map[c] == (c == 'f' or c == 'o')); + + kak_assert(vm.exec("ooxxx", RegexExecFlags::None)); + kak_assert(vm.exec("oofx", RegexExecFlags::None)); + kak_assert(not vm.exec("oox😄", RegexExecFlags::None)); + } + + { auto eq = [](const CompiledRegex::NamedCapture& lhs, const CompiledRegex::NamedCapture& rhs) { return lhs.name == rhs.name and diff --git a/src/regex_impl.hh b/src/regex_impl.hh index 07936027..2f7fad4e 100644 --- a/src/regex_impl.hh +++ b/src/regex_impl.hh @@ -9,6 +9,7 @@ #include "utils.hh" #include <bit> +#include <algorithm> namespace Kakoune { @@ -155,6 +156,7 @@ struct CompiledRegex : UseMemoryDomain<MemoryDomain::Regex> { static constexpr Codepoint count = 256; using OffsetLimits = std::numeric_limits<uint8_t>; + char start_byte = 0; uint8_t offset = 0; bool map[count]; }; @@ -210,11 +212,12 @@ constexpr bool is_direction(RegexMode mode) (mode & ~(RegexMode::Forward | RegexMode::Backward)) == RegexMode{0}; } -template<typename It, typename=void> +template<typename It> struct SentinelType { using Type = It; }; template<typename It> -struct SentinelType<It, void_t<typename It::Sentinel>> { using Type = typename It::Sentinel; }; + requires requires { typename It::Sentinel; } +struct SentinelType<It> { using Type = typename It::Sentinel; }; template<typename Iterator, RegexMode mode> requires (has_direction(mode)) @@ -257,8 +260,6 @@ public: if (flags & RegexExecFlags::NotInitialNull and begin == end) return false; - constexpr bool search = (mode & RegexMode::Search); - const ExecConfig config{ Sentinel{forward ? begin : end}, Sentinel{forward ? end : begin}, @@ -267,24 +268,11 @@ public: flags }; - Iterator start = forward ? begin : end; - if (const auto& start_desc = forward ? m_program.forward_start_desc : m_program.backward_start_desc) - { - if (search) - { - start = find_next_start(start, config, *start_desc); - if (start == config.end) // If start_desc is not null, it means we consume at least one char - return false; - } - else if (start != config.end) - { - const unsigned char c = forward ? *start : *utf8::previous(start, config.end); - if (not start_desc->map[c]) - return false; - } - } + exec_program(forward ? begin : end, config, idle_func); - return exec_program(std::move(start), config, idle_func); + while (not m_threads.next_is_empty()) + release_saves(m_threads.pop_next().saves); + return m_found_match; } ArrayView<const Iterator> captures() const @@ -370,8 +358,9 @@ private: // Steps a thread until it consumes the current character, matches or fail [[gnu::always_inline]] - void step_thread(const Iterator& pos, Codepoint cp, uint16_t current_step, Thread thread, const ExecConfig& config) + void step_next_thread(const Iterator& pos, Codepoint cp, uint16_t current_step, const ExecConfig& config) { + Thread thread = m_threads.pop_current(); auto failed = [this, &thread]() { release_saves(thread.saves); }; @@ -471,33 +460,48 @@ private: return failed(); } - bool exec_program(Iterator pos, const ExecConfig& config, auto&& idle_func) + void exec_program(const Iterator& start, const ExecConfig& config, auto&& idle_func) { kak_assert(m_threads.current_is_empty() and m_threads.next_is_empty()); + m_threads.ensure_initial_capacity(); release_saves(m_captures); m_captures = -1; - m_threads.ensure_initial_capacity(); - - ConstArrayView<CompiledRegex::Instruction> insts{m_program.instructions}; - const auto* first_inst = insts.begin() + (forward ? 0 : m_program.first_backward_inst); - m_threads.push_current({first_inst, -1}); + m_found_match = false; const auto* start_desc = (forward ? m_program.forward_start_desc : m_program.backward_start_desc).get(); - auto next_start = pos; + Iterator next_start = start; + if (start_desc) + { + if (mode & RegexMode::Search) + { + next_start = find_next_start(start, config.end, *start_desc); + if (next_start == config.end) // If start_desc is not null, it means we consume at least one char + return; + } + else if (start != config.end) + { + const unsigned char c = forward ? *start : *utf8::previous(start, config.end); + if (not start_desc->map[c]) + return; + } + } + + const auto insts = forward ? ArrayView(m_program.instructions).subrange(0, m_program.first_backward_inst) + : ArrayView(m_program.instructions).subrange(m_program.first_backward_inst); + m_threads.push_current({insts.begin(), -1}); - constexpr bool search = mode & RegexMode::Search; - constexpr bool any_match = mode & RegexMode::AnyMatch; uint16_t current_step = -1; - m_found_match = false; - while (true) // Iterate on all codepoints and once at the end + uint8_t idle_count = 0; // Run idle loop every 256 * 65536 == 16M codepoints + auto pos = next_start; + while (pos != config.end) { if (++current_step == 0) { - idle_func(); + if (++idle_count == 0) + idle_func(); // We wrapped, avoid potential collision on inst.last_step by resetting them - for (auto& inst : forward ? insts.subrange(0, m_program.first_backward_inst) - : insts.subrange(m_program.first_backward_inst)) + for (auto& inst : insts) inst.last_step = 0; current_step = 1; // step 0 is never valid } @@ -506,38 +510,61 @@ private: Codepoint cp = codepoint(next, config); while (not m_threads.current_is_empty()) - step_thread(pos, cp, current_step, m_threads.pop_current(), config); + step_next_thread(pos, cp, current_step, config); - if (pos == config.end or - (m_threads.next_is_empty() and (not search or m_found_match)) or - (m_found_match and any_match)) - { - while (not m_threads.next_is_empty()) - release_saves(m_threads.pop_next().saves); - return m_found_match; - } - - if (search and not m_found_match) + if ((mode & RegexMode::Search) and not m_found_match) { if (start_desc) { if (pos == next_start) - next_start = find_next_start(next, config, *start_desc); + next_start = find_next_start(next, config.end, *start_desc); if (m_threads.next_is_empty()) next = next_start; } if (not start_desc or next == next_start) - m_threads.push_next({first_inst, -1}); + m_threads.push_next({insts.begin(), -1}); } + else if (m_threads.next_is_empty() or (m_found_match and (mode & RegexMode::AnyMatch))) + return; + pos = next; m_threads.swap_next(); } + + if (++current_step == 0) + { + for (auto& inst : insts) + inst.last_step = 0; + current_step = 1; // step 0 is never valid + } + while (not m_threads.current_is_empty()) + step_next_thread(pos, -1, current_step, config); } - static Iterator find_next_start(Iterator start, const ExecConfig& config, const StartDesc& start_desc) + static Iterator find_next_start(const Iterator& start, const Sentinel& end, const StartDesc& start_desc) { auto pos = start; - while (pos != config.end) + if (char start_byte = start_desc.start_byte) + { + while (pos != end) + { + if constexpr (forward) + { + if (*pos == start_byte) + return utf8::advance(pos, start, -CharCount(start_desc.offset)); + ++pos; + } + else + { + auto prev = utf8::previous(pos, end); + if (*prev == start_byte) + return utf8::advance(pos, start, CharCount(start_desc.offset)); + pos = prev; + } + } + } + + while (pos != end) { static_assert(StartDesc::count <= 256, "start desc should be ascii only"); if constexpr (forward) @@ -548,9 +575,9 @@ private: } else { - auto prev = utf8::previous(pos, config.end); + auto prev = utf8::previous(pos, end); if (start_desc.map[static_cast<unsigned char>(*prev)]) - return pos; + return utf8::advance(pos, start, CharCount(start_desc.offset)); pos = prev; } } @@ -652,10 +679,14 @@ private: bool current_is_empty() const { return m_current == m_next_begin; } bool next_is_empty() const { return m_next_end == m_next_begin; } + [[gnu::always_inline]] void push_current(Thread thread) { m_data[decrement(m_current)] = thread; grow_ifn(true); } + [[gnu::always_inline]] Thread pop_current() { return m_data[post_increment(m_current)]; } + [[gnu::always_inline]] void push_next(Thread thread) { m_data[post_increment(m_next_end)] = thread; grow_ifn(false); } + [[gnu::always_inline]] Thread pop_next() { return m_data[decrement(m_next_end)]; } void swap_next() @@ -675,11 +706,16 @@ private: m_capacity = initial_capacity; } + [[gnu::always_inline]] void grow_ifn(bool pushed_current) { - if (m_current != m_next_end) - return; + if (m_current == m_next_end) + grow(pushed_current); + } + [[gnu::noinline]] + void grow(bool pushed_current) + { const auto new_capacity = m_capacity * 2; Thread* new_data = new Thread[new_capacity]; Thread* old_data = m_data.get(); diff --git a/src/register_manager.cc b/src/register_manager.cc index a189c5b6..b9d09a52 100644 --- a/src/register_manager.cc +++ b/src/register_manager.cc @@ -3,11 +3,23 @@ #include "assert.hh" #include "context.hh" #include "hash_map.hh" -#include "string_utils.hh" +#include "format.hh" +#include "ranges.hh" +#include "hook_manager.hh" namespace Kakoune { +Register::RestoreInfo Register::save(const Context& context) +{ + return get(context) | gather<RestoreInfo>(); +} + +void Register::restore(Context& context, const RestoreInfo& info) +{ + set(context, info, true); +} + void StaticRegister::set(Context& context, ConstArrayView<String> values, bool) { m_content.assign(values.begin(), values.end()); diff --git a/src/register_manager.hh b/src/register_manager.hh index 2e760e6f..9d843eff 100644 --- a/src/register_manager.hh +++ b/src/register_manager.hh @@ -24,8 +24,8 @@ public: virtual const String& get_main(const Context& context, size_t main_index) = 0; using RestoreInfo = Vector<String, MemoryDomain::Registers>; - RestoreInfo save(const Context& context) { return get(context) | gather<RestoreInfo>(); } - void restore(Context& context, const RestoreInfo& info) { set(context, info, true); } + RestoreInfo save(const Context& context); + void restore(Context& context, const RestoreInfo& info); NestedBool& modified_hook_disabled() { return m_disable_modified_hook; } diff --git a/src/remote.cc b/src/remote.cc index 025a4458..f4e62419 100644 --- a/src/remote.cc +++ b/src/remote.cc @@ -1,7 +1,7 @@ #include "remote.hh" -#include "buffer_manager.hh" #include "buffer_utils.hh" +#include "debug.hh" #include "client_manager.hh" #include "command_manager.hh" #include "display_buffer.hh" @@ -619,21 +619,21 @@ const String& session_directory() return session_dir; } -String session_path(StringView session) +String session_path(StringView session, bool assume_valid) { - if (not all_of(session, is_identifier)) + if (not assume_valid and not all_of(session, is_identifier)) throw runtime_error{format("invalid session name: '{}'", session)}; - return format("{}/{}", session_directory(), session); + String path = format("{}/{}", session_directory(), session); + if (not assume_valid and path.length() + 1 > sizeof sockaddr_un{}.sun_path) + throw runtime_error{format("socket path too long: '{}'", path)}; + return path; } static sockaddr_un session_addr(StringView session) { sockaddr_un addr; addr.sun_family = AF_UNIX; - String path = session_path(session); - if (path.length() + 1 > sizeof addr.sun_path) - throw runtime_error{format("socket path too long: '{}'", path)}; - strcpy(addr.sun_path, path.c_str()); + strcpy(addr.sun_path, session_path(session).c_str()); return addr; } @@ -806,8 +806,8 @@ private: auto env_vars = m_reader.read<HashMap<String, String, MemoryDomain::EnvVars>>(); if (auto stdin_fd = m_reader.ancillary_fd()) - create_fifo_buffer(generate_buffer_name("*stdin-{}*"), *stdin_fd, Buffer::Flags::None); - + create_fifo_buffer(generate_buffer_name("*stdin-{}*"), *stdin_fd, Buffer::Flags::None, + AutoScroll::NotInitially); auto* ui = new RemoteUI{sock, dimensions}; ClientManager::instance().create_client( std::unique_ptr<UserInterface>(ui), pid, std::move(name), @@ -889,7 +889,7 @@ Server::Server(String session_name, bool is_daemon) bool Server::rename_session(StringView name) { - String old_socket_file = session_path(m_session); + String old_socket_file = session_path(m_session, true); String new_socket_file = session_path(name); if (file_exists(new_socket_file)) @@ -906,7 +906,7 @@ void Server::close_session(bool do_unlink) { if (do_unlink) { - String socket_file = session_path(m_session); + String socket_file = session_path(m_session, true); unlink(socket_file.c_str()); } m_listener->close_fd(); diff --git a/src/remote.hh b/src/remote.hh index 99e60cbd..dc6bc41f 100644 --- a/src/remote.hh +++ b/src/remote.hh @@ -46,7 +46,7 @@ private: void send_command(StringView session, StringView command); String get_user_name(); const String& session_directory(); -String session_path(StringView session); +String session_path(StringView session, bool assume_valid = false); struct Server : public Singleton<Server> { diff --git a/src/selection.cc b/src/selection.cc index f0f0270c..99b21c3e 100644 --- a/src/selection.cc +++ b/src/selection.cc @@ -2,11 +2,12 @@ #include "buffer_utils.hh" #include "changes.hh" -#include "utf8.hh" namespace Kakoune { +SelectionList::~SelectionList() = default; + SelectionList::SelectionList(Buffer& buffer, Selection s, size_t timestamp) : m_selections({ std::move(s) }), m_buffer(&buffer), m_timestamp(timestamp) { @@ -27,6 +28,11 @@ SelectionList::SelectionList(Buffer& buffer, Vector<Selection> list, size_t time SelectionList::SelectionList(Buffer& buffer, Vector<Selection> list) : SelectionList(buffer, std::move(list), buffer.timestamp()) {} +SelectionList::SelectionList(const SelectionList&) = default; +SelectionList::SelectionList(SelectionList&&) = default; +SelectionList& SelectionList::operator=(const SelectionList&) = default; +SelectionList& SelectionList::operator=(SelectionList&&) = default; + void SelectionList::remove(size_t index) { m_selections.erase(begin() + index); @@ -538,4 +544,31 @@ Selection selection_from_string(ColumnType column_type, const Buffer& buffer, St return Selection{anchor, cursor}; } +SelectionList selection_list_from_strings(Buffer& buffer, ColumnType column_type, ConstArrayView<String> descs, size_t timestamp, size_t main, ColumnCount tabstop) +{ + if ((column_type != ColumnType::Byte and timestamp != buffer.timestamp()) or timestamp > buffer.timestamp()) + throw runtime_error{format("invalid timestamp '{}'", timestamp)}; + + auto from_string = [&](StringView desc) { + return selection_from_string(column_type, buffer, desc, tabstop); + }; + + auto sels = descs | transform(from_string) | gather<Vector<Selection>>(); + if (sels.empty()) + throw runtime_error{"empty selection description"}; + if (main >= sels.size()) + throw runtime_error{"invalid main selection index"}; + + sort_selections(sels, main); + merge_overlapping_selections(sels, main); + if (timestamp < buffer.timestamp()) + update_selections(sels, main, buffer, timestamp); + else + clamp_selections(sels, buffer); + + SelectionList res{buffer, std::move(sels)}; + res.set_main_index(main); + return res; +} + } diff --git a/src/selection.hh b/src/selection.hh index 509a527d..ede4fa7b 100644 --- a/src/selection.hh +++ b/src/selection.hh @@ -1,13 +1,21 @@ #ifndef selection_hh_INCLUDED #define selection_hh_INCLUDED -#include "buffer.hh" +#include "coord.hh" +#include "range.hh" +#include "safe_ptr.hh" +#include "utils.hh" +#include "string.hh" +#include "vector.hh" #include <limits> namespace Kakoune { +class Buffer; +using BufferRange = Range<BufferCoord>; + using CaptureList = Vector<String, MemoryDomain::Selections>; constexpr ColumnCount max_column{std::numeric_limits<int>::max()}; @@ -84,11 +92,18 @@ struct SelectionList { static constexpr MemoryDomain Domain = MemoryDomain::Selections; + ~SelectionList(); SelectionList(Buffer& buffer, Selection s); SelectionList(Buffer& buffer, Selection s, size_t timestamp); SelectionList(Buffer& buffer, Vector<Selection> s); SelectionList(Buffer& buffer, Vector<Selection> s, size_t timestamp); + SelectionList(const SelectionList&); + SelectionList(SelectionList&&); + + SelectionList& operator=(const SelectionList&); + SelectionList& operator=(SelectionList&&); + void update(bool merge = true); void check_invariant() const; @@ -166,33 +181,7 @@ String selection_to_string(ColumnType column_type, const Buffer& buffer, const S String selection_list_to_string(ColumnType column_type, const SelectionList& selections, ColumnCount tabstop = -1); -template<typename StringArray> -SelectionList selection_list_from_strings(Buffer& buffer, ColumnType column_type, StringArray&& descs, size_t timestamp, size_t main, ColumnCount tabstop = -1) -{ - if ((column_type != ColumnType::Byte and timestamp != buffer.timestamp()) or timestamp > buffer.timestamp()) - throw runtime_error{format("invalid timestamp '{}'", timestamp)}; - - auto from_string = [&](StringView desc) { - return selection_from_string(column_type, buffer, desc, tabstop); - }; - - auto sels = descs | transform(from_string) | gather<Vector<Selection>>(); - if (sels.empty()) - throw runtime_error{"empty selection description"}; - if (main >= sels.size()) - throw runtime_error{"invalid main selection index"}; - - sort_selections(sels, main); - merge_overlapping_selections(sels, main); - if (timestamp < buffer.timestamp()) - update_selections(sels, main, buffer, timestamp); - else - clamp_selections(sels, buffer); - - SelectionList res{buffer, std::move(sels)}; - res.set_main_index(main); - return res; -} +SelectionList selection_list_from_strings(Buffer& buffer, ColumnType column_type, ConstArrayView<String> descs, size_t timestamp, size_t main, ColumnCount tabstop = -1); } diff --git a/src/selectors.hh b/src/selectors.hh index a9a61314..e132d897 100644 --- a/src/selectors.hh +++ b/src/selectors.hh @@ -6,6 +6,7 @@ #include "meta.hh" #include "unicode.hh" #include "vector.hh" +#include "array.hh" namespace Kakoune { diff --git a/src/shared_string.cc b/src/shared_string.cc index f88008b1..8b4f6e6e 100644 --- a/src/shared_string.cc +++ b/src/shared_string.cc @@ -1,5 +1,6 @@ #include "shared_string.hh" -#include "buffer_utils.hh" +#include "debug.hh" +#include "format.hh" namespace Kakoune { diff --git a/src/shared_string.hh b/src/shared_string.hh index 78e9fef4..79c34830 100644 --- a/src/shared_string.hh +++ b/src/shared_string.hh @@ -6,7 +6,6 @@ #include "utils.hh" #include "hash_map.hh" -#include <numeric> #include <cstring> namespace Kakoune diff --git a/src/shell_manager.cc b/src/shell_manager.cc index 47432df5..0baf77c8 100644 --- a/src/shell_manager.cc +++ b/src/shell_manager.cc @@ -1,6 +1,6 @@ #include "shell_manager.hh" -#include "buffer_utils.hh" +#include "debug.hh" #include "client.hh" #include "clock.hh" #include "context.hh" @@ -10,11 +10,11 @@ #include "face_registry.hh" #include "file.hh" #include "flags.hh" -#include "option.hh" #include "option_types.hh" #include "regex.hh" #include <array> +#include <chrono> #include <cstring> #include <sys/types.h> #include <sys/wait.h> @@ -118,7 +118,7 @@ Shell spawn_shell(const char* shell, StringView cmdline, if (pid_t pid = vfork()) return {pid, std::move(stdin_pipe[1]), std::move(stdout_pipe[0]), std::move(stderr_pipe[0])}; - auto renamefd = [](int oldfd, int newfd) { + constexpr auto renamefd = [](int oldfd, int newfd) { if (oldfd == newfd) return; dup2(oldfd, newfd); @@ -198,18 +198,22 @@ FDWatcher make_reader(int fd, String& contents, OnClose&& on_close) }}; } -FDWatcher make_pipe_writer(UniqueFd& fd, StringView contents) +FDWatcher make_pipe_writer(UniqueFd& fd, const FunctionRef<StringView ()>& generator) { int flags = fcntl((int)fd, F_GETFL, 0); fcntl((int)fd, F_SETFL, flags | O_NONBLOCK); return {(int)fd, FdEvents::Write, EventMode::Urgent, - [contents, &fd](FDWatcher& watcher, FdEvents, EventMode) mutable { + [&generator, &fd, contents=generator()](FDWatcher& watcher, FdEvents, EventMode) mutable { while (fd_writable((int)fd)) { ssize_t size = ::write((int)fd, contents.begin(), (size_t)contents.length()); if (size > 0) + { contents = contents.substr(ByteCount{(int)size}); + if (contents.empty()) + contents = generator(); + } if (size == -1 and (errno == EAGAIN or errno == EWOULDBLOCK)) return; if (size < 0 or contents.empty()) @@ -264,7 +268,7 @@ struct CommandFifos } std::pair<String, int> ShellManager::eval( - StringView cmdline, const Context& context, StringView input, + StringView cmdline, const Context& context, FunctionRef<StringView ()> input_generator, Flags flags, const ShellContext& shell_context) { const DebugFlags debug_flags = context.options()["debug"].get<DebugFlags>(); @@ -291,13 +295,13 @@ std::pair<String, int> ShellManager::eval( }); auto spawn_time = profile ? Clock::now() : Clock::time_point{}; - auto shell = spawn_shell(m_shell.c_str(), cmdline, shell_context.params, kak_env, not input.empty()); + auto shell = spawn_shell(m_shell.c_str(), cmdline, shell_context.params, kak_env, true); auto wait_time = Clock::now(); String stdout_contents, stderr_contents; auto stdout_reader = make_reader((int)shell.out, stdout_contents, [&](bool){ shell.out.close(); }); auto stderr_reader = make_reader((int)shell.err, stderr_contents, [&](bool){ shell.err.close(); }); - auto stdin_writer = make_pipe_writer(shell.in, input); + auto stdin_writer = make_pipe_writer(shell.in, input_generator); // block SIGCHLD to make sure we wont receive it before // our call to pselect, that will end up blocking indefinitly. @@ -313,24 +317,11 @@ std::pair<String, int> ShellManager::eval( bool failed = false; using namespace std::chrono; - static constexpr seconds wait_timeout{1}; - Optional<DisplayLine> previous_status; - Timer wait_timer{wait_time + wait_timeout, [&](Timer& timer) { - if (not context.has_client()) - return; - - const auto now = Clock::now(); - timer.set_next_date(now + wait_timeout); - auto& client = context.client(); - if (not previous_status) - previous_status = client.current_status(); - - client.print_status({format("waiting for shell command to finish{} ({}s)", - terminated ? " (shell terminated)" : "", - duration_cast<seconds>(now - wait_time).count()), - context.faces()[failed ? "Error" : "Information"]}); - client.redraw_ifn(); - }, EventMode::Urgent}; + BusyIndicator busy_indicator{context, [&](seconds elapsed) { + return DisplayLine{format("waiting for shell command to finish{} ({}s)", + terminated ? " (shell terminated)" : "", elapsed.count()), + context.faces()[failed ? "Error" : "Information"]}; + }}; bool cancelling = false; while (not terminated or shell.in or @@ -370,12 +361,6 @@ std::pair<String, int> ShellManager::eval( if (cancelling) throw cancel{}; - if (previous_status) // restore the status line - { - context.print_status(std::move(*previous_status)); - context.client().redraw_ifn(); - } - return { std::move(stdout_contents), WIFEXITED(status) ? WEXITSTATUS(status) : -1 }; } diff --git a/src/shell_manager.hh b/src/shell_manager.hh index f6ef38a4..5b42d644 100644 --- a/src/shell_manager.hh +++ b/src/shell_manager.hh @@ -58,10 +58,20 @@ public: friend constexpr bool with_bit_ops(Meta::Type<Flags>) { return true; } std::pair<String, int> eval(StringView cmdline, const Context& context, - StringView input = {}, + FunctionRef<StringView ()> stdin_generator, Flags flags = Flags::WaitForStdout, const ShellContext& shell_context = {}); + std::pair<String, int> eval(StringView cmdline, const Context& context, + StringView in, + Flags flags = Flags::WaitForStdout, + const ShellContext& shell_context = {}) + { + return eval(cmdline, context, + [in]() mutable { return std::exchange(in, StringView{}); }, + flags, shell_context); + } + Shell spawn(StringView cmdline, const Context& context, bool open_stdin, diff --git a/src/string.hh b/src/string.hh index 0ed1dc75..677f01f5 100644 --- a/src/string.hh +++ b/src/string.hh @@ -8,6 +8,7 @@ #include "units.hh" #include "utf8.hh" +#include <algorithm> namespace Kakoune { diff --git a/src/string_utils.cc b/src/string_utils.cc index c614d485..d533a12b 100644 --- a/src/string_utils.cc +++ b/src/string_utils.cc @@ -3,9 +3,7 @@ #include "exception.hh" #include "utf8_iterator.hh" #include "unit_tests.hh" - -#include <charconv> -#include <cstdio> +#include "ranges.hh" namespace Kakoune { @@ -147,88 +145,6 @@ int str_to_int(StringView str) throw runtime_error{str + " is not a number"}; } -template<size_t N> -InplaceString<N> to_string_impl(auto val, auto format) -{ - InplaceString<N> res; - auto [end, errc] = std::to_chars(res.m_data, res.m_data + N, val, format); - if (errc != std::errc{}) - throw runtime_error("to_string error"); - res.m_length = end - res.m_data; - *end = '\0'; - return res; -} - -template<size_t N> -InplaceString<N> to_string_impl(auto val) -{ - return to_string_impl<N>(val, 10); -} - -InplaceString<15> to_string(int val) -{ - return to_string_impl<15>(val); -} - -InplaceString<15> to_string(unsigned val) -{ - return to_string_impl<15>(val); -} - -InplaceString<23> to_string(long int val) -{ - return to_string_impl<23>(val); -} - -InplaceString<23> to_string(long long int val) -{ - return to_string_impl<23>(val); -} - -InplaceString<23> to_string(unsigned long val) -{ - return to_string_impl<23>(val); -} - -InplaceString<23> to_string(Hex val) -{ - return to_string_impl<23>(val.val, 16); -} - -InplaceString<23> to_string(Grouped val) -{ - auto ungrouped = to_string_impl<23>(val.val); - - InplaceString<23> res; - for (int pos = 0, len = ungrouped.m_length; pos != len; ++pos) - { - if (res.m_length and ((len - pos) % 3) == 0) - res.m_data[res.m_length++] = ','; - res.m_data[res.m_length++] = ungrouped.m_data[pos]; - } - return res; -} - -InplaceString<23> to_string(float val) -{ -#if defined(__cpp_lib_to_chars) - return to_string_impl<23>(val, std::chars_format::general); -#else - InplaceString<23> res; - res.m_length = snprintf(res.m_data, 23, "%f", val); - return res; -#endif -} - -InplaceString<7> to_string(Codepoint c) -{ - InplaceString<7> res; - char* ptr = res.m_data; - utf8::dump(ptr, c); - res.m_length = (int)(ptr - res.m_data); - return res; -} - bool subsequence_match(StringView str, StringView subseq) { auto it = str.begin(); @@ -270,149 +186,6 @@ String expand_tabs(StringView line, ColumnCount tabstop, ColumnCount col) return res; } -WrapView::Iterator::Iterator(StringView text, ColumnCount max_width) - : m_remaining{text}, m_max_width{max_width} -{ - if (max_width <= 0) - throw runtime_error("Invalid max width"); - ++*this; -} - -WrapView::Iterator& WrapView::Iterator::operator++() -{ - using Utf8It = utf8::iterator<const char*>; - Utf8It it{m_remaining.begin(), m_remaining}; - Utf8It last_word_end = it; - - while (it != m_remaining.end()) - { - const CharCategories cat = categorize(*it, {'_'}); - if (cat == CharCategories::EndOfLine) - { - m_current = StringView{m_remaining.begin(), it.base()}; - m_remaining = StringView{(it+1).base(), m_remaining.end()}; - return *this; - } - - Utf8It word_end = it+1; - while (word_end != m_remaining.end() and categorize(*word_end, {'_'}) == cat) - ++word_end; - - if (word_end > m_remaining.begin() and - utf8::column_distance(m_remaining.begin(), word_end.base()) >= m_max_width) - { - auto line_end = last_word_end <= m_remaining.begin() ? - Utf8It{utf8::advance(m_remaining.begin(), m_remaining.end(), m_max_width), m_remaining} - : last_word_end; - - m_current = StringView{m_remaining.begin(), line_end.base()}; - - while (line_end != m_remaining.end() and is_horizontal_blank(*line_end)) - ++line_end; - - if (line_end != m_remaining.end() and *line_end == '\n') - ++line_end; - - m_remaining = StringView{line_end.base(), m_remaining.end()}; - return *this; - } - if (cat == CharCategories::Word or cat == CharCategories::Punctuation) - last_word_end = word_end; - - if (word_end > m_remaining.begin()) - it = word_end; - } - m_current = m_remaining; - m_remaining = StringView{}; - return *this; -} - -template<typename AppendFunc> -void format_impl(StringView fmt, ArrayView<const StringView> params, AppendFunc append) -{ - int implicitIndex = 0; - for (auto it = fmt.begin(), end = fmt.end(); it != end;) - { - auto opening = std::find(it, end, '{'); - if (opening == end) - { - append(StringView{it, opening}); - break; - } - else if (opening != it and *(opening-1) == '\\') - { - append(StringView{it, opening-1}); - append('{'); - it = opening + 1; - } - else - { - append(StringView{it, opening}); - auto closing = std::find(opening, end, '}'); - if (closing == end) - throw runtime_error("format string error, unclosed '{'"); - - auto format = std::find(opening+1, closing, ':'); - const int index = opening+1 == format ? implicitIndex : str_to_int({opening+1, format}); - - if (index >= params.size()) - throw runtime_error("format string parameter index too big"); - - if (format != closing) - { - char padding = ' '; - if (*(++format) == '0') - { - padding = '0'; - ++format; - } - for (ColumnCount width = str_to_int({format, closing}), len = params[index].column_length(); - width > len; --width) - append(padding); - } - - append(params[index]); - implicitIndex = index+1; - it = closing+1; - } - } -} - -StringView format_to(ArrayView<char> buffer, StringView fmt, ArrayView<const StringView> params) -{ - char* ptr = buffer.begin(); - const char* end = buffer.end(); - format_impl(fmt, params, [&](StringView s) mutable { - for (auto c : s) - { - if (ptr == end) - throw runtime_error("buffer is too small"); - *ptr++ = c; - } - }); - if (ptr == end) - throw runtime_error("buffer is too small"); - *ptr = 0; - - return { buffer.begin(), ptr }; -} - -void format_with(FunctionRef<void (StringView)> append, StringView fmt, ArrayView<const StringView> params) -{ - format_impl(fmt, params, append); -} - -String format(StringView fmt, ArrayView<const StringView> params) -{ - ByteCount size = fmt.length(); - for (auto& s : params) size += s.length(); - String res; - res.reserve(size); - - format_impl(fmt, params, [&](StringView s) { res += s; }); - return res; -} - String double_up(StringView s, StringView characters) { String res; @@ -444,21 +217,6 @@ UnitTest test_string{[]() kak_assert(StringView{"youpi"}.ends_with("youpi")); kak_assert(not StringView{"youpi"}.ends_with("oup")); - auto wrapped = "wrap this paragraph\n respecting whitespaces and much_too_long_words" | wrap_at(16) | gather<Vector<String>>(); - kak_assert(wrapped.size() == 6); - kak_assert(wrapped[0] == "wrap this"); - kak_assert(wrapped[1] == "paragraph"); - kak_assert(wrapped[2] == " respecting"); - kak_assert(wrapped[3] == "whitespaces and"); - kak_assert(wrapped[4] == "much_too_long_wo"); - kak_assert(wrapped[5] == "rds"); - - auto wrapped2 = "error: unknown type" | wrap_at(7) | gather<Vector<String>>(); - kak_assert(wrapped2.size() == 3); - kak_assert(wrapped2[0] == "error:"); - kak_assert(wrapped2[1] == "unknown"); - kak_assert(wrapped2[2] == "type"); - kak_assert(trim_indent(" ") == ""); kak_assert(trim_indent("no-indent") == "no-indent"); kak_assert(trim_indent("\nno-indent") == "no-indent"); diff --git a/src/string_utils.hh b/src/string_utils.hh index 3034dfd5..9e245bcc 100644 --- a/src/string_utils.hh +++ b/src/string_utils.hh @@ -4,9 +4,9 @@ #include "string.hh" #include "enum.hh" #include "vector.hh" -#include "ranges.hh" #include "optional.hh" -#include "utils.hh" +#include "format.hh" +#include "array.hh" namespace Kakoune { @@ -66,116 +66,9 @@ bool subsequence_match(StringView str, StringView subseq); String expand_tabs(StringView line, ColumnCount tabstop, ColumnCount col = 0); -struct WrapView -{ - struct Iterator - { - using difference_type = ptrdiff_t; - using value_type = StringView; - using pointer = StringView*; - using reference = StringView&; - using iterator_category = std::forward_iterator_tag; - - Iterator(StringView text, ColumnCount max_width); - - Iterator& operator++(); - Iterator operator++(int) { auto copy = *this; ++(*this); return copy; } - - bool operator==(Iterator other) const { return m_remaining == other.m_remaining and m_current == other.m_current; } - StringView operator*() { return m_current; } - - private: - StringView m_current; - StringView m_remaining; - ColumnCount m_max_width; - }; - - Iterator begin() const { return {text, max_width}; } - Iterator end() const { return {{}, 1}; } - - StringView text; - ColumnCount max_width; -}; - -inline auto wrap_at(ColumnCount max_width) -{ - return ViewFactory{[=](StringView text) { - return WrapView{text, max_width}; - }}; -} - int str_to_int(StringView str); // throws on error Optional<int> str_to_int_ifp(StringView str); -template<size_t N> -struct InplaceString -{ - static_assert(N < 256, "InplaceString cannot handle sizes >= 256"); - - constexpr operator StringView() const { return {m_data, ByteCount{m_length}}; } - operator String() const { return {m_data, ByteCount{m_length}}; } - - unsigned char m_length{}; - char m_data[N]; -}; - -struct Hex { size_t val; }; -constexpr Hex hex(size_t val) { return {val}; } - -struct Grouped { size_t val; }; -constexpr Grouped grouped(size_t val) { return {val}; } - -InplaceString<15> to_string(int val); -InplaceString<15> to_string(unsigned val); -InplaceString<23> to_string(long int val); -InplaceString<23> to_string(unsigned long val); -InplaceString<23> to_string(long long int val); -InplaceString<23> to_string(Hex val); -InplaceString<23> to_string(Grouped val); -InplaceString<23> to_string(float val); -InplaceString<7> to_string(Codepoint c); - -template<typename RealType, typename ValueType> -decltype(auto) to_string(const StronglyTypedNumber<RealType, ValueType>& val) -{ - return to_string((ValueType)val); -} - -namespace detail -{ - -template<typename T> requires std::is_convertible_v<T, StringView> -StringView format_param(const T& val) { return val; } - -template<typename T> requires (not std::is_convertible_v<T, StringView>) -decltype(auto) format_param(const T& val) { return to_string(val); } - -} - -String format(StringView fmt, ArrayView<const StringView> params); - -template<typename... Types> -String format(StringView fmt, Types&&... params) -{ - return format(fmt, ArrayView<const StringView>{detail::format_param(std::forward<Types>(params))...}); -} - -StringView format_to(ArrayView<char> buffer, StringView fmt, ArrayView<const StringView> params); - -template<typename... Types> -StringView format_to(ArrayView<char> buffer, StringView fmt, Types&&... params) -{ - return format_to(buffer, fmt, ArrayView<const StringView>{detail::format_param(std::forward<Types>(params))...}); -} - -void format_with(FunctionRef<void (StringView)> append, StringView fmt, ArrayView<const StringView> params); - -template<typename... Types> -void format_with(FunctionRef<void (StringView)> append, StringView fmt, Types&&... params) -{ - return format_with(append, fmt, ArrayView<const StringView>{detail::format_param(std::forward<Types>(params))...}); -} - String double_up(StringView s, StringView characters); inline String quote(StringView s) diff --git a/src/terminal_ui.cc b/src/terminal_ui.cc index 33574e90..672626e4 100644 --- a/src/terminal_ui.cc +++ b/src/terminal_ui.cc @@ -1,14 +1,14 @@ #include "terminal_ui.hh" -#include "buffer_utils.hh" #include "display_buffer.hh" #include "event_manager.hh" #include "exception.hh" #include "file.hh" #include "keys.hh" #include "ranges.hh" -#include "string_utils.hh" +#include "format.hh" #include "diff.hh" +#include "string_utils.hh" #include <algorithm> @@ -224,7 +224,7 @@ void TerminalUI::Screen::set_face(const Face& face, Writer& writer) static constexpr int fg_table[]{ 39, 30, 31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97 }; static constexpr int bg_table[]{ 49, 40, 41, 42, 43, 44, 45, 46, 47, 100, 101, 102, 103, 104, 105, 106, 107 }; static constexpr int ul_table[]{ 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - static constexpr const char* attr_table[]{ "0", "4", "4:3", "7", "5", "1", "2", "3", "9" }; + static constexpr const char* attr_table[]{ "0", "4", "4:3", "21", "7", "5", "1", "2", "3", "9" }; auto set_color = [&](bool fg, const Color& color, bool join) { if (join) @@ -609,16 +609,19 @@ void TerminalUI::draw_status(const DisplayLine& status_line, if (m_set_title) { Writer writer{STDOUT_FILENO}; - constexpr char suffix[] = " - Kakoune\007"; writer.write("\033]2;"); - // Fill title escape sequence buffer, removing non ascii characters - for (auto& atom : mode_line) - { - const auto str = atom.content(); + auto write_escaped = [&](StringView str) { for (auto it = str.begin(), end = str.end(); it != end; utf8::to_next(it, end)) writer.write((*it >= 0x20 and *it <= 0x7e) ? *it : '?'); + }; + if (not m_title) + { + for (auto& atom : mode_line) + write_escaped(atom.content()); } - writer.write(suffix); + else + write_escaped(*m_title); + writer.write(" - Kakoune\007"); } m_dirty = true; @@ -703,7 +706,7 @@ Optional<Key> TerminalUI::get_next_key() static constexpr auto control = [](char c) { return c & 037; }; auto convert = [this](Codepoint c) -> Codepoint { - if (c == control('m') or c == control('j')) + if (c == control('m')) return Key::Return; if (c == control('i')) return Key::Tab; @@ -798,9 +801,8 @@ Optional<Key> TerminalUI::get_next_key() return Key{mod | Key::to_modifier(button), coord}; }; - auto mouse_scroll = [this](Key::Modifiers mod, bool down) -> Key { - return {mod | Key::Modifiers::Scroll, - (Codepoint)((down ? 1 : -1) * m_wheel_scroll_amount)}; + auto mouse_scroll = [this](Key::Modifiers mod, Codepoint coord, bool down) -> Key { + return {mod | Key::Modifiers::Scroll | (Key::Modifiers)((down ? m_wheel_scroll_amount : -1 * m_wheel_scroll_amount) << 16), coord}; }; auto masked_key = [&](Codepoint key, Codepoint shifted_key = 0) { @@ -859,6 +861,8 @@ Optional<Key> TerminalUI::get_next_key() return masked_key(Key::F11 + params[0][0] - 23); case 25: case 26: return Key{Key::Modifiers::Shift, Key::F3 + params[0][0] - 25}; // rxvt style + case 27: + return masked_key(convert(static_cast<Codepoint>(params[2][0]))); case 28: case 29: return Key{Key::Modifiers::Shift, Key::F5 + params[0][0] - 28}; // rxvt style case 31: case 32: @@ -883,7 +887,23 @@ Optional<Key> TerminalUI::get_next_key() switch (params[0][0]) { // Treat numpad keys the same as their non-numpad counterparts. Could add a numpad modifier here. + case 57399: key = '0'; break; + case 57400: key = '1'; break; + case 57401: key = '2'; break; + case 57402: key = '3'; break; + case 57403: key = '4'; break; + case 57404: key = '5'; break; + case 57405: key = '6'; break; + case 57406: key = '7'; break; + case 57407: key = '8'; break; + case 57408: key = '9'; break; + case 57409: key = '.'; break; + case 57410: key = '/'; break; + case 57411: key = '*'; break; + case 57412: key = '-'; break; + case 57413: key = '+'; break; case 57414: key = Key::Return; break; + case 57415: key = '='; break; case 57417: key = Key::Left; break; case 57418: key = Key::Right; break; case 57419: key = Key::Up; break; @@ -921,8 +941,8 @@ Optional<Key> TerminalUI::get_next_key() else if (int guess = ffs(m_mouse_state) - 1; 0 <= guess and guess < 3) return mouse_button(mod, Key::MouseButton{guess}, coord, true); break; - case 64: return mouse_scroll(mod, false); - case 65: return mouse_scroll(mod, true); + case 64: return mouse_scroll(mod, coord, false); + case 65: return mouse_scroll(mod, coord, true); } return Key{Key::Modifiers::MousePos, coord}; } @@ -1537,6 +1557,7 @@ void TerminalUI::set_ui_options(const Options& options) m_status_on_top = find("terminal_status_on_top").map(to_bool).value_or(false); m_set_title = find("terminal_set_title").map(to_bool).value_or(true); + m_title = find("terminal_title").map([](StringView s) { return String{s}; }); auto synchronized = find("terminal_synchronized").map(to_bool); m_synchronized.set = (bool)synchronized; diff --git a/src/terminal_ui.hh b/src/terminal_ui.hh index 85583a1d..02085532 100644 --- a/src/terminal_ui.hh +++ b/src/terminal_ui.hh @@ -6,7 +6,6 @@ #include "display_buffer.hh" #include "event_manager.hh" #include "face.hh" -#include "hash_map.hh" #include "optional.hh" #include "string.hh" #include "user_interface.hh" @@ -154,6 +153,7 @@ private: int m_shift_function_key = default_shift_function_key; bool m_set_title = true; + Optional<String> m_title; struct Synchronized { diff --git a/src/unicode.hh b/src/unicode.hh index 0acf4005..5ce63f4b 100644 --- a/src/unicode.hh +++ b/src/unicode.hh @@ -5,7 +5,6 @@ #include <cwchar> #include "array_view.hh" -#include "ranges.hh" #include "units.hh" namespace Kakoune @@ -75,8 +74,12 @@ enum WordType { Word, WORD }; template<WordType word_type = Word> inline bool is_word(Codepoint c, ConstArrayView<Codepoint> extra_word_chars = {'_'}) noexcept { - return (c < 128 ? is_basic_alpha(c) or is_basic_digit(c) : iswalnum((wchar_t)c)) or - contains(extra_word_chars, c); + if (c < 128 ? is_basic_alpha(c) or is_basic_digit(c) : iswalnum((wchar_t)c)) + return true; + for (auto cp : extra_word_chars) + if (c == cp) + return true; + return false; } template<> diff --git a/src/unique_descriptor.hh b/src/unique_descriptor.hh index c099c17e..0d925ef5 100644 --- a/src/unique_descriptor.hh +++ b/src/unique_descriptor.hh @@ -1,6 +1,8 @@ #ifndef fd_hh_INCLUDED #define fd_hh_INCLUDED +#include <utility> + namespace Kakoune { diff --git a/src/units.hh b/src/units.hh index 3bdfe253..9d2511b9 100644 --- a/src/units.hh +++ b/src/units.hh @@ -5,7 +5,6 @@ #include "hash.hh" #include <type_traits> -#include <compare> namespace Kakoune { @@ -17,7 +16,7 @@ public: StronglyTypedNumber() = default; [[gnu::always_inline]] - explicit constexpr StronglyTypedNumber(ValueType value) + constexpr StronglyTypedNumber(ValueType value) : m_value(value) { static_assert(std::is_base_of<StronglyTypedNumber, RealType>::value, @@ -83,13 +82,8 @@ public: RealType& operator%=(RealType other) { m_value %= other.m_value; return static_cast<RealType&>(*this); } - [[gnu::always_inline]] - constexpr friend bool operator==(RealType lhs, RealType rhs) - { return lhs.m_value == rhs.m_value; } - - [[gnu::always_inline]] - constexpr friend auto operator<=>(RealType lhs, RealType rhs) - { return lhs.m_value <=> rhs.m_value; } + constexpr friend bool operator==(StronglyTypedNumber lhs, StronglyTypedNumber rhs) = default; + constexpr friend auto operator<=>(StronglyTypedNumber lhs, StronglyTypedNumber rhs) = default; [[gnu::always_inline]] constexpr bool operator!() const @@ -111,10 +105,7 @@ protected: struct LineCount : public StronglyTypedNumber<LineCount, int> { - LineCount() = default; - - [[gnu::always_inline]] - constexpr LineCount(int value) : StronglyTypedNumber<LineCount>(value) {} + using StronglyTypedNumber::StronglyTypedNumber; }; [[gnu::always_inline]] @@ -125,10 +116,7 @@ inline constexpr LineCount operator"" _line(unsigned long long int value) struct ByteCount : public StronglyTypedNumber<ByteCount, int> { - ByteCount() = default; - - [[gnu::always_inline]] - constexpr ByteCount(int value) : StronglyTypedNumber<ByteCount>(value) {} + using StronglyTypedNumber::StronglyTypedNumber; }; [[gnu::always_inline]] @@ -143,10 +131,7 @@ Byte* operator+(Byte* ptr, ByteCount count) { return ptr + (int)count; } struct CharCount : public StronglyTypedNumber<CharCount, int> { - CharCount() = default; - - [[gnu::always_inline]] - constexpr CharCount(int value) : StronglyTypedNumber<CharCount>(value) {} + using StronglyTypedNumber::StronglyTypedNumber; }; [[gnu::always_inline]] @@ -157,10 +142,7 @@ inline constexpr CharCount operator"" _char(unsigned long long int value) struct ColumnCount : public StronglyTypedNumber<ColumnCount, int> { - ColumnCount() = default; - - [[gnu::always_inline]] - constexpr ColumnCount(int value) : StronglyTypedNumber<ColumnCount>(value) {} + using StronglyTypedNumber::StronglyTypedNumber; }; [[gnu::always_inline]] diff --git a/src/utf8.hh b/src/utf8.hh index 6ef2f63d..e5643214 100644 --- a/src/utf8.hh +++ b/src/utf8.hh @@ -6,8 +6,6 @@ #include "units.hh" #include "optional.hh" -#include <cstddef> - namespace Kakoune { @@ -41,22 +39,13 @@ struct Pass } -// returns the codepoint of the character whose first byte -// is pointed by it template<typename InvalidPolicy = utf8::InvalidPolicy::Pass, typename Iterator, typename Sentinel> -Codepoint read_codepoint(Iterator& it, const Sentinel& end) +[[gnu::noinline]] +Codepoint read_codepoint_multibyte(Iterator& it, const Sentinel& end, char byte) noexcept(noexcept(InvalidPolicy{}(0))) { if (it == end) - return InvalidPolicy{}(-1); - // According to rfc3629, UTF-8 allows only up to 4 bytes. - // (21 bits codepoint) - unsigned char byte = read(it); - if ((byte & 0x80) == 0) // 0xxxxxxx - return byte; - - if (it == end) return InvalidPolicy{}(byte); if ((byte & 0xE0) == 0xC0) // 110xxxxx @@ -83,6 +72,21 @@ Codepoint read_codepoint(Iterator& it, const Sentinel& end) return InvalidPolicy{}(byte); } +// returns the codepoint of the character whose first byte +// is pointed by it +template<typename InvalidPolicy = utf8::InvalidPolicy::Pass, + typename Iterator, typename Sentinel> +Codepoint read_codepoint(Iterator& it, const Sentinel& end) + noexcept(noexcept(InvalidPolicy{}(0))) +{ + if (it == end) + return InvalidPolicy{}(-1); + unsigned char byte = read(it); + if ((byte & 0x80) == 0) [[likely]] // 0xxxxxxx + return byte; + return read_codepoint_multibyte(it, end, byte); +} + template<typename InvalidPolicy = utf8::InvalidPolicy::Pass, typename Iterator, typename Sentinel> Codepoint codepoint(Iterator it, const Sentinel& end) diff --git a/src/value.hh b/src/value.hh index 2b9e0d22..f38e4fa2 100644 --- a/src/value.hh +++ b/src/value.hh @@ -2,7 +2,6 @@ #define value_hh_INCLUDED #include "hash_map.hh" -#include "units.hh" #include "meta.hh" #include <type_traits> diff --git a/src/window.cc b/src/window.cc index e296a62d..520d77e1 100644 --- a/src/window.cc +++ b/src/window.cc @@ -1,13 +1,14 @@ #include "window.hh" #include "assert.hh" -#include "clock.hh" +#include "buffer.hh" +#include "buffer_utils.hh" #include "context.hh" #include "highlighter.hh" #include "hook_manager.hh" #include "input_handler.hh" #include "client.hh" -#include "buffer_utils.hh" +#include "debug.hh" #include "option.hh" #include "option_types.hh" #include "profile.hh" @@ -295,7 +296,7 @@ BufferCoord find_buffer_coord(const DisplayLine& line, const Buffer& buffer, } } -Optional<DisplayCoord> Window::display_position(BufferCoord coord) const +Optional<DisplayCoord> Window::display_coord(BufferCoord coord) const { if (m_display_buffer.timestamp() != buffer().timestamp()) return {}; @@ -311,13 +312,13 @@ Optional<DisplayCoord> Window::display_position(BufferCoord coord) const return {}; } -BufferCoord Window::buffer_coord(DisplayCoord coord) const +Optional<BufferCoord> Window::buffer_coord(DisplayCoord coord) const { if (m_display_buffer.timestamp() != buffer().timestamp() or m_display_buffer.lines().empty()) - return {0, 0}; + return {}; if (coord <= 0_line) - coord = {0,0}; + coord = {}; if ((size_t)coord.line >= m_display_buffer.lines().size()) coord = DisplayCoord{(int)m_display_buffer.lines().size()-1, INT_MAX}; diff --git a/src/window.hh b/src/window.hh index 65253c87..3e1e5f95 100644 --- a/src/window.hh +++ b/src/window.hh @@ -37,8 +37,8 @@ public: const DisplayBuffer& update_display_buffer(const Context& context); - Optional<DisplayCoord> display_position(BufferCoord coord) const; - BufferCoord buffer_coord(DisplayCoord coord) const; + Optional<DisplayCoord> display_coord(BufferCoord coord) const; + Optional<BufferCoord> buffer_coord(DisplayCoord coord) const; Buffer& buffer() const { return *m_buffer; } diff --git a/src/word_db.cc b/src/word_db.cc index 755304ca..0a1873e7 100644 --- a/src/word_db.cc +++ b/src/word_db.cc @@ -2,9 +2,7 @@ #include "buffer.hh" #include "line_modification.hh" -#include "option_types.hh" #include "unit_tests.hh" -#include "utils.hh" #include "value.hh" namespace Kakoune |
