summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEnrico Borba <enricozb@users.noreply.github.com>2024-12-23 09:23:58 +0100
committerGitHub <noreply@github.com>2024-12-23 09:23:58 +0100
commit52125e6336d596aebdd4da91080b3178ddca7449 (patch)
tree27d3e5c01660d567f22fee621c97753f294256b0 /src
parent14cb35f62b36b2f1aa530adb5e31c05ff1347bfc (diff)
parent9c458c50661446fc6e7295787b06422137af099d (diff)
Merge branch 'master' into enricozb/daemon-stdin
Diffstat (limited to 'src')
-rw-r--r--src/alias_registry.cc1
-rw-r--r--src/alias_registry.hh1
-rw-r--r--src/array.hh48
-rw-r--r--src/array_view.hh3
-rw-r--r--src/assert.cc5
-rw-r--r--src/backtrace.cc2
-rw-r--r--src/buffer.cc12
-rw-r--r--src/buffer.hh20
-rw-r--r--src/buffer.inl.hh48
-rw-r--r--src/buffer_utils.cc65
-rw-r--r--src/buffer_utils.hh3
-rw-r--r--src/client.cc61
-rw-r--r--src/client.hh31
-rw-r--r--src/client_manager.cc1
-rw-r--r--src/color.cc2
-rw-r--r--src/command_manager.cc43
-rw-r--r--src/command_manager.hh9
-rw-r--r--src/commands.cc272
-rw-r--r--src/completion.cc4
-rw-r--r--src/completion.hh15
-rw-r--r--src/constexpr_utils.hh90
-rw-r--r--src/context.cc34
-rw-r--r--src/context.hh44
-rw-r--r--src/coord.hh20
-rw-r--r--src/debug.cc39
-rw-r--r--src/debug.hh39
-rw-r--r--src/display_buffer.cc1
-rw-r--r--src/display_buffer.hh5
-rw-r--r--src/event_manager.cc25
-rw-r--r--src/event_manager.hh7
-rw-r--r--src/face.hh27
-rw-r--r--src/face_registry.cc4
-rw-r--r--src/face_registry.hh1
-rw-r--r--src/file.cc31
-rw-r--r--src/file.hh6
-rw-r--r--src/format.cc182
-rw-r--r--src/format.hh81
-rw-r--r--src/hash_map.cc4
-rw-r--r--src/highlighter.cc3
-rw-r--r--src/highlighter.hh7
-rw-r--r--src/highlighter_group.cc4
-rw-r--r--src/highlighters.cc8
-rw-r--r--src/hook_manager.cc3
-rw-r--r--src/hook_manager.hh2
-rw-r--r--src/input_handler.cc105
-rw-r--r--src/input_handler.hh6
-rw-r--r--src/insert_completer.cc6
-rw-r--r--src/insert_completer.hh3
-rw-r--r--src/json.cc2
-rw-r--r--src/json.hh1
-rw-r--r--src/json_ui.cc14
-rw-r--r--src/keymap_manager.cc6
-rw-r--r--src/keymap_manager.hh3
-rw-r--r--src/keys.cc4
-rw-r--r--src/keys.hh3
-rw-r--r--src/main.cc51
-rw-r--r--src/memory.hh1
-rw-r--r--src/meta.hh7
-rw-r--r--src/normal.cc45
-rw-r--r--src/option.hh27
-rw-r--r--src/option_manager.hh3
-rw-r--r--src/option_types.cc1
-rw-r--r--src/option_types.hh3
-rw-r--r--src/parameters_parser.cc1
-rw-r--r--src/parameters_parser.hh7
-rw-r--r--src/profile.hh4
-rw-r--r--src/range.hh2
-rw-r--r--src/ranges.hh11
-rw-r--r--src/ranked_match.cc1
-rw-r--r--src/ref_ptr.hh4
-rw-r--r--src/regex_impl.cc35
-rw-r--r--src/regex_impl.hh146
-rw-r--r--src/register_manager.cc14
-rw-r--r--src/register_manager.hh4
-rw-r--r--src/remote.cc24
-rw-r--r--src/remote.hh2
-rw-r--r--src/selection.cc35
-rw-r--r--src/selection.hh45
-rw-r--r--src/selectors.hh1
-rw-r--r--src/shared_string.cc3
-rw-r--r--src/shared_string.hh1
-rw-r--r--src/shell_manager.cc49
-rw-r--r--src/shell_manager.hh12
-rw-r--r--src/string.hh1
-rw-r--r--src/string_utils.cc244
-rw-r--r--src/string_utils.hh111
-rw-r--r--src/terminal_ui.cc51
-rw-r--r--src/terminal_ui.hh2
-rw-r--r--src/unicode.hh9
-rw-r--r--src/unique_descriptor.hh2
-rw-r--r--src/units.hh32
-rw-r--r--src/utf8.hh30
-rw-r--r--src/value.hh1
-rw-r--r--src/window.cc13
-rw-r--r--src/window.hh4
-rw-r--r--src/word_db.cc2
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