summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIgor Ramazanov <igor.ramazanov@protonmail.com>2024-06-04 23:19:47 +0200
committerGitHub <noreply@github.com>2024-06-04 23:19:47 +0200
commit4d21fbb3b0b66ef48270e5655bf2687a1f78becf (patch)
tree479c9ca7cd6e9fcf16d7abc7ebea8c8fe0b502e9 /src
parent7e8c430ad0706604c0b919207f322a5c14150575 (diff)
parent727d2391c7695056ce6bb170b127c6e6ca9e1ab4 (diff)
Merge branch 'mawww:master' into contrib/gendocs.sh
Diffstat (limited to 'src')
-rw-r--r--src/Makefile191
-rw-r--r--src/alias_registry.hh3
-rw-r--r--src/buffer.cc57
-rw-r--r--src/buffer_manager.cc32
-rw-r--r--src/buffer_manager.hh4
-rw-r--r--src/buffer_utils.cc43
-rw-r--r--src/client.cc11
-rw-r--r--src/commands.cc72
-rw-r--r--src/context.cc13
-rw-r--r--src/context.hh12
-rw-r--r--src/diff.hh8
-rw-r--r--src/display_buffer.hh5
-rw-r--r--src/event_manager.cc22
-rw-r--r--src/face_registry.cc1
-rw-r--r--src/face_registry.hh2
-rw-r--r--src/file.cc52
-rw-r--r--src/hash.cc10
-rw-r--r--src/hash.hh14
-rw-r--r--src/hash_map.hh6
-rw-r--r--src/highlighter_group.cc5
-rw-r--r--src/highlighter_group.hh2
-rw-r--r--src/highlighters.cc107
-rw-r--r--src/hook_manager.hh6
-rw-r--r--src/input_handler.cc110
-rw-r--r--src/input_handler.hh10
-rw-r--r--src/insert_completer.cc22
-rw-r--r--src/insert_completer.hh2
-rw-r--r--src/keymap_manager.hh2
-rw-r--r--src/line_modification.cc2
-rw-r--r--src/main.cc12
-rw-r--r--src/normal.cc16
-rw-r--r--src/option_manager.cc9
-rw-r--r--src/option_manager.hh2
-rw-r--r--src/ranked_match.cc8
-rw-r--r--src/regex.cc4
-rw-r--r--src/regex.hh5
-rw-r--r--src/regex_impl.cc118
-rw-r--r--src/regex_impl.hh173
-rw-r--r--src/scope.cc10
-rw-r--r--src/scope.hh2
-rw-r--r--src/shared_string.cc28
-rw-r--r--src/shared_string.hh32
-rw-r--r--src/shell_manager.hh2
-rw-r--r--src/string.hh2
-rw-r--r--src/terminal_ui.cc8
-rw-r--r--src/unicode.hh35
-rw-r--r--src/window.cc2
-rw-r--r--src/word_db.cc2
48 files changed, 690 insertions, 606 deletions
diff --git a/src/Makefile b/src/Makefile
deleted file mode 100644
index bb0f32e6..00000000
--- a/src/Makefile
+++ /dev/null
@@ -1,191 +0,0 @@
-debug ?= no
-static ?= no
-gzip_man ?= yes
-# to get format compatible with GitHub archive use "gzip -S .gz" here
-compress_bin ?= bzip2
-
-ifneq ($(gzip_man),yes)
- ifneq ($(gzip_man),no)
- $(error gzip_man should be either yes or no)
- endif
-endif
-
-ifeq ($(debug),yes)
- CPPFLAGS += -DKAK_DEBUG
- CXXFLAGS += -O0
- suffix := .debug
-else
- ifeq ($(debug),no)
- CXXFLAGS += -O3
- suffix := .opt
- else
- $(error debug should be either yes or no)
- endif
-endif
-
-ifneq (,$(findstring address,$(sanitize)))
- CXXFLAGS += -fsanitize=address
- LDFLAGS += -lasan
- sanitize_suffix := $(sanitize_suffix)a
-endif
-ifneq (,$(findstring undefined,$(sanitize)))
- CXXFLAGS += -fsanitize=undefined
- LDFLAGS += -lubsan
- sanitize_suffix := $(sanitize_suffix)u
-endif
-
-ifneq (,$(sanitize_suffix))
- suffix := $(suffix).san_$(sanitize_suffix)
-endif
-
-version ?= $(shell if [ -f .version ]; then cat .version; elif [ -d ../.git ]; then git describe --tags HEAD; else echo "unknown"; fi)
-
-sources := $(sort $(wildcard *.cc))
-objects := $(addprefix ., $(sources:.cc=$(suffix).o))
-deps := $(addprefix ., $(sources:.cc=$(suffix).d))
-
-ifeq ($(static),yes)
- LDFLAGS += -static -pthread
-endif
-
-PREFIX ?= /usr/local
-DESTDIR ?= # root dir
-
-bindir := $(DESTDIR)$(PREFIX)/bin
-libexecdir := $(DESTDIR)$(PREFIX)/libexec/kak
-sharedir := $(DESTDIR)$(PREFIX)/share/kak
-docdir := $(DESTDIR)$(PREFIX)/share/doc/kak
-mandir := $(DESTDIR)$(PREFIX)/share/man/man1
-
-os := $(shell uname)
-
-ifeq ($(os),Darwin)
- CPPFLAGS += -I/opt/local/include
- LDFLAGS += -L/opt/local/lib
-else ifeq ($(os),FreeBSD)
- CPPFLAGS += -I/usr/local/include
- LDFLAGS += -L/usr/local/lib
-else ifeq ($(os),Haiku)
- LIBS += -lnetwork -lbe
-else ifeq ($(os),OpenBSD)
- CPPFLAGS += -D'KAK_BIN_PATH="$(bindir)/kak"' -I/usr/local/include
- LDFLAGS += -L/usr/local/lib
- mandir := $(DESTDIR)$(PREFIX)/man/man1
-else ifneq (,$(findstring _NT,$(os)))
- # Both Cygwin and MSYS2 have "_NT" in their uname.
- CPPFLAGS += -D_XOPEN_SOURCE=700
- LIBS += -ldbghelp
-else ifeq ($(os),SunOS)
- LDFLAGS += -lsocket -rdynamic
-else
- LDFLAGS += -rdynamic
-endif
-
-CXXFLAGS += -pedantic -std=c++2a -g -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare -Wno-address
-
-compiler := $(shell $(CXX) --version)
-ifneq (,$(findstring clang,$(compiler)))
- CXXFLAGS += -frelaxed-template-template-args -Wno-ambiguous-reversed-operator
-else ifneq (,$(findstring g++,$(compiler)))
- CXXFLAGS += -Wno-init-list-lifetime -Wno-stringop-overflow
-endif
-
-all : kak
-
-kak : kak$(suffix)
- ln -sf $< $@
-
-kak$(suffix) : $(objects) .version.o
- $(CXX) $(LDFLAGS) $(CXXFLAGS) $(objects) .version.o $(LIBS) -o $@
-
--include $(deps)
-
-.%$(suffix).o: %.cc
- $(CXX) $(CPPFLAGS) $(CXXFLAGS) -MD -MP -MF $(addprefix ., $(<:.cc=$(suffix).d)) -c -o $@ $<
-
-.version.o: .version.cc
- $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
-
-.version.cc: FORCE
- @printf "%s" 'namespace Kakoune { const char* version = "$(version)"; }' > .version.cc.tmp
- @if cmp -s .version.cc.tmp .version.cc; then rm .version.cc.tmp; else mv .version.cc.tmp .version.cc; fi
-
-# Generate the man page
-ifeq ($(gzip_man),yes)
-../doc/kak.1.gz: ../doc/kak.1
- gzip -n -9 -f < $< > $@
-man: ../doc/kak.1.gz
-else
-man: ../doc/kak.1
-endif
-
-check: test
-test: kak
- cd ../test && ./run
-
-TAGS: tags
-tags:
- ctags -R
-
-clean:
- rm -f $(objects) $(deps) .version.cc .version.o
-
-dist:
- @if ! [ -d ../.git ]; then echo "make dist can only run from a git repo"; false; fi
- @if git status -s | grep -qEv '^\?\?'; then echo "working tree is not clean"; false; fi
- cd ../; \
- basename="kakoune-$$(echo "$(version)" | sed -e s/^v//)"; \
- git archive --format=tar --prefix=$${basename}/ HEAD -o $${basename}.tar; \
- echo "$(version)" > src/.version; \
- tar --transform "s,^,$${basename}/," -rf $${basename}.tar src/.version; \
- rm src/.version; \
- $(compress_bin) $${basename}.tar;
-
-distclean: clean
- rm -f kak kak$(suffix)
- find ../doc -type f \( -name \*\\.gz -o -name \*\\.1 \) -exec rm -f '{}' +
-
-installdirs:
- install -d $(bindir) \
- $(libexecdir) \
- $(sharedir)/rc \
- $(sharedir)/colors \
- $(sharedir)/doc \
- $(docdir) \
- $(mandir)
-ifeq ($(debug),yes)
- install -d $(sharedir)/gdb
-endif
-
-install: kak man installdirs
- install -m 0755 kak $(bindir)
- ln -sf ../../bin/kak $(libexecdir)/kak
- install -m 0644 ../share/kak/kakrc $(sharedir)
- install -m 0644 ../doc/pages/*.asciidoc $(sharedir)/doc
- cp -r ../rc/* $(sharedir)/rc
- find $(sharedir)/rc -type f -exec chmod 0644 {} +
- [ -e $(sharedir)/autoload ] || ln -s rc $(sharedir)/autoload
- install -m 0644 ../colors/* $(sharedir)/colors
- install -m 0644 ../README.asciidoc $(docdir)
-ifeq ($(gzip_man),yes)
- install -m 0644 ../doc/kak.1.gz $(mandir)
-else
- install -m 0644 ../doc/kak.1 $(mandir)
-endif
-ifeq ($(debug),yes)
- install -m 0644 ../gdb/kakoune.py $(sharedir)/gdb
-endif
-
-install-strip: install
- strip -s $(bindir)/kak
-
-uninstall:
- rm -rf $(bindir)/kak \
- $(libexecdir) \
- $(sharedir) \
- $(docdir) \
- $(mandir)/kak.1.gz \
- $(mandir)/kak.1
-
-.PHONY: check TAGS clean dist distclean installdirs install install-strip uninstall
-.PHONY: tags test man kak FORCE
diff --git a/src/alias_registry.hh b/src/alias_registry.hh
index c35cdf70..1b7a49eb 100644
--- a/src/alias_registry.hh
+++ b/src/alias_registry.hh
@@ -12,6 +12,9 @@ class AliasRegistry : public SafeCountable
{
public:
AliasRegistry(AliasRegistry& parent) : SafeCountable{}, m_parent(&parent) {}
+
+ void reparent(AliasRegistry& parent) { m_parent = &parent; }
+
void add_alias(String alias, String command);
void remove_alias(StringView alias);
StringView operator[](StringView alias) const;
diff --git a/src/buffer.cc b/src/buffer.cc
index 4460bab7..ada61ad8 100644
--- a/src/buffer.cc
+++ b/src/buffer.cc
@@ -47,6 +47,7 @@ Buffer::Buffer(String name, Flags flags, BufferLines lines,
options().get_local_option("eolformat").set(eolformat);
options().get_local_option("BOM").set(bom);
+ options().get_local_option("readonly").set((bool)(flags & Flags::ReadOnly));
// now we may begin to record undo data
if (not (flags & Flags::NoUndo))
@@ -219,40 +220,55 @@ void Buffer::reload(BufferLines lines, ByteOrderMark bom, EolFormat eolformat, F
[](const StringDataPtr& lhs, const StringDataPtr& rhs)
{ return lhs->strview() == rhs->strview(); });
- auto it = m_lines.begin();
+ auto read_it = m_lines.begin();
+ auto write_it = m_lines.begin();
auto new_it = lines.begin();
- for (auto& [op, len] : diff)
+ for (auto [op, len] : diff)
{
+ kak_assert(read_it >= write_it);
if (op == DiffOp::Keep)
{
- it += len;
+ if (read_it != write_it)
+ std::move(read_it, read_it + len, write_it);
+ write_it += len;
+ read_it += len;
new_it += len;
}
else if (op == DiffOp::Add)
{
- const LineCount cur_line = (int)(it - m_lines.begin());
-
+ const LineCount cur_line = (int)(write_it - m_lines.begin());
for (LineCount line = 0; line < len; ++line)
m_current_undo_group.push_back({Modification::Insert, cur_line + line, *(new_it + (int)line)});
-
m_changes.push_back({Change::Insert, cur_line, cur_line + len});
- m_lines.insert(it, new_it, new_it + len);
- it = m_lines.begin() + (int)(cur_line + len);
+
+ if (read_it != write_it)
+ {
+ auto count = std::min(len, static_cast<int>(read_it - write_it));
+ write_it = std::copy(new_it, new_it + count, write_it);
+ new_it += count;
+ if (len == count)
+ continue;
+ len -= count;
+ }
+
+ auto read_pos = read_it - m_lines.begin();
+ write_it = m_lines.insert(write_it, new_it, new_it + len) + len;
+ read_it = m_lines.begin() + read_pos + len;
new_it += len;
}
else if (op == DiffOp::Remove)
{
- const LineCount cur_line = (int)(it - m_lines.begin());
-
+ const LineCount cur_line = (int)(write_it - m_lines.begin());
for (LineCount line = len-1; line >= 0; --line)
m_current_undo_group.push_back({
Modification::Erase, cur_line + line,
- m_lines.get_storage(cur_line + line)});
+ *(read_it + (size_t)line)});
- it = m_lines.erase(it, it + len);
+ read_it += len;
m_changes.push_back({ Change::Erase, cur_line, cur_line + len });
}
}
+ m_lines.erase(write_it, m_lines.end());
}
commit_undo_group();
@@ -305,11 +321,10 @@ bool Buffer::redo(size_t count)
{
throw_if_read_only();
- if (current_history_node().redo_child == HistoryId::Invalid)
+ if (current_history_node().redo_child == HistoryId::Invalid or
+ not m_current_undo_group.empty())
return false;
- kak_assert(m_current_undo_group.empty());
-
while (count-- != 0 and current_history_node().redo_child != HistoryId::Invalid)
{
m_history_id = current_history_node().redo_child;
@@ -415,14 +430,14 @@ BufferRange Buffer::do_insert(BufferCoord pos, StringView content)
if (content[i] == '\n')
{
StringView line = content.substr(start, i + 1 - start);
- new_lines.push_back(start == 0 ? StringData::create({prefix, line}) : StringData::create({line}));
+ new_lines.push_back(start == 0 ? StringData::create(prefix, line) : StringData::create(line));
start = i + 1;
}
}
if (start == 0)
- new_lines.push_back(StringData::create({prefix, content, suffix}));
+ new_lines.push_back(StringData::create(prefix, content, suffix));
else if (start != content.length() or not suffix.empty())
- new_lines.push_back(StringData::create({content.substr(start), suffix}));
+ new_lines.push_back(StringData::create(content.substr(start), suffix));
auto line_it = m_lines.begin() + (int)pos.line;
auto new_lines_it = new_lines.begin();
@@ -451,7 +466,7 @@ BufferCoord Buffer::do_erase(BufferCoord begin, BufferCoord end)
StringView prefix = m_lines[begin.line].substr(0, begin.column);
StringView suffix = end.line == line_count() ? StringView{} : m_lines[end.line].substr(end.column);
- auto new_line = (not prefix.empty() or not suffix.empty()) ? StringData::create({prefix, suffix}) : StringDataPtr{};
+ auto new_line = (not prefix.empty() or not suffix.empty()) ? StringData::create(prefix, suffix) : StringDataPtr{};
m_lines.erase(m_lines.begin() + (int)begin.line, m_lines.begin() + (int)end.line);
m_changes.push_back({ Change::Erase, begin, end });
@@ -676,7 +691,7 @@ String Buffer::debug_description() const
UnitTest test_buffer{[]()
{
- auto make_lines = [](auto&&... lines) { return BufferLines{StringData::create({lines})...}; };
+ auto make_lines = [](auto&&... lines) { return BufferLines{StringData::create(lines)...}; };
Buffer empty_buffer("empty", Buffer::Flags::None, make_lines("\n"));
@@ -723,7 +738,7 @@ UnitTest test_buffer{[]()
UnitTest test_undo{[]()
{
- auto make_lines = [](auto&&... lines) { return BufferLines{StringData::create({lines})...}; };
+ auto make_lines = [](auto&&... lines) { return BufferLines{StringData::create(lines)...}; };
Buffer buffer("test", Buffer::Flags::None, make_lines("allo ?\n", "mais que fais la police\n", " hein ?\n", " youpi\n"));
auto pos = buffer.end_coord();
diff --git a/src/buffer_manager.cc b/src/buffer_manager.cc
index 32d03fc1..35f0fcde 100644
--- a/src/buffer_manager.cc
+++ b/src/buffer_manager.cc
@@ -7,6 +7,7 @@
#include "file.hh"
#include "ranges.hh"
#include "string.hh"
+#include "regex.hh"
namespace Kakoune
{
@@ -34,7 +35,7 @@ Buffer* BufferManager::create_buffer(String name, Buffer::Flags flags, BufferLin
throw runtime_error{"buffer name is already in use"};
}
- m_buffers.push_back(std::make_unique<Buffer>(std::move(name), flags, lines, bom, eolformat, fs_status));
+ m_buffers.push_back(std::make_unique<Buffer>(std::move(name), flags, std::move(lines), bom, eolformat, fs_status));
auto* buffer = m_buffers.back().get();
buffer->on_registered();
@@ -79,12 +80,30 @@ Buffer& BufferManager::get_buffer(StringView name)
return *res;
}
+Buffer* BufferManager::get_buffer_matching_ifp(const Regex& regex)
+{
+ for (auto& buf : m_buffers | reverse())
+ {
+ if (StringView name = buf->name(); regex_match(name.begin(), name.end(), regex))
+ return buf.get();
+ }
+ return nullptr;
+}
+
+Buffer& BufferManager::get_buffer_matching(const Regex& regex)
+{
+ Buffer* res = get_buffer_matching_ifp(regex);
+ if (not res)
+ throw runtime_error{format("no buffer matching '{}'", regex.str())};
+ return *res;
+}
+
Buffer& BufferManager::get_first_buffer()
{
if (all_of(m_buffers, [](auto& b) { return (b->flags() & Buffer::Flags::Debug); }))
create_buffer("*scratch*", Buffer::Flags::None,
- {StringData::create({"*** this is a *scratch* buffer which won't be automatically saved ***\n"}),
- StringData::create({"*** use it for notes or open a file buffer with the :edit command ***\n"})},
+ {StringData::create("*** this is a *scratch* buffer which won't be automatically saved ***\n"),
+ StringData::create("*** use it for notes or open a file buffer with the :edit command ***\n")},
ByteOrderMark::None, EolFormat::Lf, {InvalidTime, {}, {}});
return *m_buffers.back();
@@ -130,4 +149,11 @@ void BufferManager::arrange_buffers(ConstArrayView<String> first_ones)
m_buffers = std::move(res);
}
+void BufferManager::make_latest(Buffer& buffer)
+{
+ auto it = find(m_buffers, &buffer);
+ kak_assert(it != m_buffers.end());
+ std::rotate(it, it+1, m_buffers.end());
+}
+
}
diff --git a/src/buffer_manager.hh b/src/buffer_manager.hh
index 75066bd9..5b687a7f 100644
--- a/src/buffer_manager.hh
+++ b/src/buffer_manager.hh
@@ -28,6 +28,10 @@ public:
Buffer* get_buffer_ifp(StringView name);
Buffer& get_buffer(StringView name);
+ Buffer* get_buffer_matching_ifp(const Regex& regex);
+ Buffer& get_buffer_matching(const Regex& regex);
+
+ void make_latest(Buffer& buffer);
void arrange_buffers(ConstArrayView<String> first_ones);
Buffer& get_first_buffer();
diff --git a/src/buffer_utils.cc b/src/buffer_utils.cc
index 6de07c71..6a66b000 100644
--- a/src/buffer_utils.cc
+++ b/src/buffer_utils.cc
@@ -97,12 +97,12 @@ static BufferLines parse_lines(const char* pos, const char* end, EolFormat eolfo
if ((eol - pos) >= std::numeric_limits<int>::max())
throw runtime_error("line is too long");
- lines.emplace_back(StringData::create({{pos, eol - (eolformat == EolFormat::Crlf and eol != end ? 1 : 0)}, "\n"}));
+ lines.emplace_back(StringData::create(StringView{pos, eol - (eolformat == EolFormat::Crlf and eol != end ? 1 : 0)}, "\n"));
pos = eol + 1;
}
if (lines.empty())
- lines.emplace_back(StringData::create({"\n"}));
+ lines.emplace_back(StringData::create("\n"));
return lines;
}
@@ -132,15 +132,11 @@ decltype(auto) parse_file(StringView filename, Func&& func)
}
bool has_crlf = false, has_lf = false;
- for (auto it = pos; it != end; ++it)
- {
- if (*it == '\n')
- ((it != pos and *(it-1) == '\r') ? has_crlf : has_lf) = true;
- }
- const bool crlf = has_crlf and not has_lf;
- auto eolformat = crlf ? EolFormat::Crlf : EolFormat::Lf;
+ for (auto it = std::find(pos, end, '\n'); it != end; it = std::find(it+1, end, '\n'))
+ ((it != pos and *(it-1) == '\r') ? has_crlf : has_lf) = true;
+ auto eolformat = (has_crlf and not has_lf) ? EolFormat::Crlf : EolFormat::Lf;
- FsStatus fs_status{file.st.st_mtim, file.st.st_size, hash_data(file.data, file.st.st_size)};
+ FsStatus fs_status{file.st.st_mtim, file.st.st_size, murmur3(file.data, file.st.st_size)};
return func(parse_lines(pos, end, eolformat), bom, eolformat, fs_status);
}
@@ -179,12 +175,13 @@ Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, bool scroll
{
buffer->flags() |= Buffer::Flags::NoUndo | flags;
buffer->values().clear();
- buffer->reload({StringData::create({"\n"})}, ByteOrderMark::None, EolFormat::Lf, {InvalidTime, {}, {}});
+ buffer->reload({StringData::create("\n")}, ByteOrderMark::None, EolFormat::Lf, {InvalidTime, {}, {}});
+ buffer_manager.make_latest(*buffer);
}
else
buffer = buffer_manager.create_buffer(
std::move(name), flags | Buffer::Flags::Fifo | Buffer::Flags::NoUndo,
- {StringData::create({"\n"})}, ByteOrderMark::None, EolFormat::Lf, {InvalidTime, {}, {}});
+ {StringData::create("\n")}, ByteOrderMark::None, EolFormat::Lf, {InvalidTime, {}, {}});
struct FifoWatcher : FDWatcher
{
@@ -205,7 +202,7 @@ Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, bool scroll
m_buffer.flags() &= ~(Buffer::Flags::Fifo | Buffer::Flags::NoUndo);
}
- void read_fifo() const
+ void read_fifo()
{
kak_assert(m_buffer.flags() & Buffer::Flags::Fifo);
@@ -234,20 +231,21 @@ Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, bool scroll
}
auto pos = m_buffer.back_coord();
- const bool prevent_scrolling = pos == BufferCoord{0,0} and not m_scroll;
- if (prevent_scrolling)
+ const bool is_first = pos == BufferCoord{0,0};
+ if (not m_scroll and (is_first or m_had_trailing_newline))
pos = m_buffer.next(pos);
- m_buffer.insert(pos, StringView(data, data+count));
+ pos = m_buffer.insert(pos, StringView(data, data+count)).end;
- if (prevent_scrolling)
+ bool have_trailing_newline = (data[count-1] == '\n');
+ if (not m_scroll)
{
- m_buffer.erase({0,0}, m_buffer.next({0,0}));
- // in the other case, the buffer will have automatically
- // inserted a \n to guarantee its invariant.
- if (data[count-1] == '\n')
- m_buffer.insert(m_buffer.end_coord(), "\n");
+ if (is_first)
+ m_buffer.erase({0,0}, m_buffer.next({0,0}));
+ else if (not m_had_trailing_newline and have_trailing_newline)
+ m_buffer.erase(m_buffer.prev(pos), pos);
}
+ m_had_trailing_newline = have_trailing_newline;
}
while (++loop < max_loop and fd_readable(fifo));
}
@@ -263,6 +261,7 @@ Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, bool scroll
Buffer& m_buffer;
bool m_scroll;
+ bool m_had_trailing_newline = false;
};
buffer->values()[fifo_watcher_id] = Value(Meta::Type<FifoWatcher>{}, fd, *buffer, scroll);
diff --git a/src/client.cc b/src/client.cc
index ae8f83a8..6e5d082a 100644
--- a/src/client.cc
+++ b/src/client.cc
@@ -165,11 +165,16 @@ DisplayLine Client::generate_mode_line() const
DisplayLine modeline;
try
{
+ auto [mode_info_line, normal_params] = context().client().input_handler().mode_info();
const String& modelinefmt = context().options()["modelinefmt"].get<String>();
- HashMap<String, DisplayLine> atoms{{ "mode_info", context().client().input_handler().mode_line() },
+ HashMap<String, DisplayLine> atoms{{ "mode_info", mode_info_line},
{ "context_info", {generate_context_info(context()),
context().faces()["Information"]}}};
- auto expanded = expand(modelinefmt, context(), ShellContext{},
+ ShellContext shell_context{{}, {
+ {"register", normal_params ? StringView{normal_params->reg}.str() : ""},
+ {"count", normal_params ? String{to_string(normal_params->count)} : ""},
+ }};
+ auto expanded = expand(modelinefmt, context(), shell_context,
[](String s) { return escape(s, '{', '\\'); });
modeline = parse_display_line(expanded, context().faces(), atoms);
}
@@ -394,7 +399,7 @@ void Client::check_if_buffer_needs_reloading()
return;
if (MappedFile fd{filename};
- fd.st.st_size == status.file_size and hash_data(fd.data, fd.st.st_size) == status.hash)
+ fd.st.st_size == status.file_size and murmur3(fd.data, fd.st.st_size) == status.hash)
return;
if (reload == Autoreload::Ask)
diff --git a/src/commands.cc b/src/commands.cc
index 3413d095..0febf160 100644
--- a/src/commands.cc
+++ b/src/commands.cc
@@ -48,6 +48,24 @@ namespace Kakoune
extern const char* version;
+struct LocalScope : Scope
+{
+ LocalScope(Context& context)
+ : Scope(context.scope()), m_context{context}
+ {
+ m_context.m_local_scopes.push_back(this);
+ }
+
+ ~LocalScope()
+ {
+ kak_assert(not m_context.m_local_scopes.empty() and m_context.m_local_scopes.back() == this);
+ m_context.m_local_scopes.pop_back();
+ }
+
+private:
+ Context& m_context;
+};
+
namespace
{
@@ -215,21 +233,21 @@ const ParameterDesc double_params{ {}, ParameterDesc::Flags::None, 2, 2 };
static Completions complete_scope(const Context&, CompletionFlags,
StringView prefix, ByteCount cursor_pos)
{
- static constexpr StringView scopes[] = { "global", "buffer", "window", };
+ 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,
StringView prefix, ByteCount cursor_pos)
{
- static constexpr StringView scopes[] = { "global", "buffer", "window", "current" };
+ 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,
StringView prefix, ByteCount cursor_pos)
{
- static constexpr StringView scopes[] = { "buffer", "window", "current" };
+ static constexpr StringView scopes[] = { "buffer", "window", "local", "current" };
return { 0_byte, cursor_pos, complete(prefix, cursor_pos, scopes) };
}
@@ -395,6 +413,8 @@ Scope* get_scope_ifp(StringView scope, const Context& context)
return &context.buffer();
else if (prefix_match("window", scope))
return &context.window();
+ else if (prefix_match("local", scope))
+ return context.local_scope();
else if (prefix_match(scope, "buffer="))
return &BufferManager::instance().get_buffer(scope.substr(7_byte));
return nullptr;
@@ -827,13 +847,17 @@ const CommandDesc buffer_cmd = {
"buffer",
"b",
"buffer <name>: set buffer to edit in current client",
- single_param,
+ {
+ { { "matching", { {}, "treat the argument as a regex" } } },
+ ParameterDesc::Flags::None, 1, 1
+ },
CommandFlags::None,
CommandHelper{},
make_completer(menu(complete_buffer_name<true>)),
[](const ParametersParser& parser, Context& context, const ShellContext&)
{
- Buffer& buffer = BufferManager::instance().get_buffer(parser[0]);
+ Buffer& buffer = parser.get_switch("matching") ? BufferManager::instance().get_buffer_matching(Regex{parser[0]})
+ : BufferManager::instance().get_buffer(parser[0]);
if (&buffer != &context.buffer())
{
context.push_jump();
@@ -1248,6 +1272,14 @@ Vector<String> params_to_shell(const ParametersParser& parser)
return vars;
}
+Completions complete_completer_type(const Context&, CompletionFlags,
+ 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) };
+}
+
+
CommandCompleter make_command_completer(StringView type, StringView param, Completions::Flags completions_flags)
{
if (type == "file")
@@ -1347,7 +1379,8 @@ void define_command(const ParametersParser& parser, Context& context, const Shel
if (parser.get_switch("hidden"))
flags = CommandFlags::Hidden;
- const Completions::Flags completions_flags = parser.get_switch("menu") ?
+ const bool menu = (bool)parser.get_switch("menu");
+ const Completions::Flags completions_flags = menu ?
Completions::Flags::Menu : Completions::Flags::None;
const String& commands = parser[1];
@@ -1371,6 +1404,7 @@ void define_command(const ParametersParser& parser, Context& context, const Shel
desc = ParameterDesc{ {}, ParameterDesc::Flags::SwitchesAsPositional, min, max };
cmd = [=](const ParametersParser& parser, Context& context, const ShellContext& sc) {
+ LocalScope local_scope{context};
CommandManager::instance().execute(commands, context,
{ params_to_shell(parser), sc.env_vars });
};
@@ -1379,11 +1413,14 @@ void define_command(const ParametersParser& parser, Context& context, const Shel
{
desc = ParameterDesc{ {}, ParameterDesc::Flags::SwitchesAsPositional, 0, 0 };
cmd = [=](const ParametersParser& parser, Context& context, const ShellContext& sc) {
+ LocalScope local_scope{context};
CommandManager::instance().execute(commands, context, { {}, sc.env_vars });
};
}
CommandCompleter completer = parse_completion_switch(parser, completions_flags);
+ if (menu and not completer)
+ throw runtime_error("menu switch requires a completion switch");
auto docstring = trim_indent(parser.get_switch("docstring").value_or(StringView{}));
cm.register_command(cmd_name, cmd, docstring, desc, flags, CommandHelper{}, std::move(completer));
@@ -1471,7 +1508,7 @@ const CommandDesc complete_command_cmd = {
ParameterDesc::Flags::None, 2, 3},
CommandFlags::None,
CommandHelper{},
- make_completer(complete_command_name),
+ make_completer(complete_command_name, complete_completer_type),
[](const ParametersParser& parser, Context& context, const ShellContext&)
{
const Completions::Flags flags = parser.get_switch("menu") ? Completions::Flags::Menu : Completions::Flags::None;
@@ -1584,7 +1621,7 @@ const CommandDesc debug_cmd = {
}
else if (parser[0] == "memory")
{
- auto total = 0;
+ size_t total = 0;
write_to_debug_buffer("Memory usage:");
const ColumnCount column_size = 17;
write_to_debug_buffer(format("{:17} │{:17} │{:17} │{:17} ",
@@ -1634,9 +1671,14 @@ const CommandDesc debug_cmd = {
for (auto mode : concatenated(modes, user_modes))
{
KeymapMode m = parse_keymap_mode(mode, user_modes);
- for (auto& key : keymaps.get_mapped_keys(m))
- write_to_debug_buffer(format(" * {} {}: {}",
- mode, key, keymaps.get_mapping_docstring(key, m)));
+ for (auto& key : keymaps.get_mapped_keys(m)) {
+ KeyList kl = keymaps.get_mapping_keys(key, m);
+ String mapping;
+ for (const auto& k : kl)
+ mapping += to_string(k);
+ write_to_debug_buffer(format(" * {} {}: '{}' {}",
+ mode, key, mapping, keymaps.get_mapping_docstring(key, m)));
+ }
}
}
else if (parser[0] == "regex")
@@ -2165,6 +2207,7 @@ const CommandDesc execute_keys_cmd = {
}
};
+
const CommandDesc evaluate_commands_cmd = {
"evaluate-commands",
"eval",
@@ -2176,13 +2219,14 @@ const CommandDesc evaluate_commands_cmd = {
}}),
CommandFlags::None,
CommandHelper{},
- CommandCompleter{},
+ CommandManager::NestedCompleter{},
[](const ParametersParser& parser, Context& context, const ShellContext& shell_context)
{
context_wrap(parser, context, {}, [&](const ParametersParser& parser, Context& context) {
const bool no_hooks = context.hooks_disabled() or parser.get_switch("no-hooks");
ScopedSetBool disable_hooks(context.hooks_disabled(), no_hooks);
+ LocalScope local_scope{context};
if (parser.get_switch("verbatim"))
CommandManager::instance().execute_single_command(parser | gather<Vector<String>>(), context, shell_context);
else
@@ -2574,10 +2618,12 @@ const CommandDesc rename_session_cmd = {
CommandFlags::None,
CommandHelper{},
make_single_word_completer([](const Context&){ return Server::instance().session(); }),
- [](const ParametersParser& parser, Context&, const ShellContext&)
+ [](const ParametersParser& parser, Context& ctx, const ShellContext&)
{
+ String old_name = Server::instance().session();
if (not Server::instance().rename_session(parser[0]))
throw runtime_error(format("unable to rename current session: '{}' may be already in use", parser[0]));
+ ctx.hooks().run_hook(Hook::SessionRenamed, format("{}:{}", old_name, Server::instance().session()), ctx);
}
};
diff --git a/src/context.cc b/src/context.cc
index 0c1b8aad..62171674 100644
--- a/src/context.cc
+++ b/src/context.cc
@@ -4,6 +4,7 @@
#include "client.hh"
#include "face_registry.hh"
#include "buffer_manager.hh"
+#include "hook_manager.hh"
#include "register_manager.hh"
#include "window.hh"
@@ -50,8 +51,10 @@ Client& Context::client() const
return *m_client;
}
-Scope& Context::scope() const
+Scope& Context::scope(bool allow_local) const
{
+ if (allow_local and not m_local_scopes.empty())
+ return *m_local_scopes.back();
if (has_window())
return window();
if (has_buffer())
@@ -69,6 +72,8 @@ void Context::set_window(Window& window)
{
kak_assert(&window.buffer() == &buffer());
m_window.reset(&window);
+ if (not m_local_scopes.empty())
+ m_local_scopes.front()->reparent(window);
}
void Context::print_status(DisplayLine status) const
@@ -313,6 +318,8 @@ void Context::change_buffer(Buffer& buffer, Optional<FunctionRef<void()>> set_se
ScopedSelectionEdition selection_edition{*this};
selections_write_only() = SelectionList{buffer, Selection{}};
}
+ if (not m_local_scopes.empty())
+ m_local_scopes.front()->reparent(buffer);
}
if (has_input_handler())
@@ -417,4 +424,8 @@ StringView Context::main_sel_register_value(StringView reg) const
return RegisterManager::instance()[reg].get_main(*this, index);
}
+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);
+}
}
diff --git a/src/context.hh b/src/context.hh
index fca4ba06..fb45fc2d 100644
--- a/src/context.hh
+++ b/src/context.hh
@@ -42,6 +42,8 @@ private:
using LastSelectFunc = std::function<void (Context&)>;
+struct LocalScope;
+
// A Context is used to access non singleton objects for various services
// in commands.
//
@@ -97,20 +99,23 @@ public:
void set_client(Client& client);
void set_window(Window& window);
- Scope& scope() const;
+ friend struct LocalScope;
+
+ 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() const { return scope().faces(); }
+ FaceRegistry& faces(bool allow_local = true) const { return scope(allow_local).faces(); }
void print_status(DisplayLine status) const;
StringView main_sel_register_value(StringView reg) const;
const String& name() const { return m_name; }
- void set_name(String name) { m_name = std::move(name); }
+ void set_name(String name);
bool is_editing() const { return m_edition_level!= 0; }
void disable_undo_handling() { m_edition_level = -1; }
@@ -155,6 +160,7 @@ private:
SafePtr<InputHandler> m_input_handler;
SafePtr<Window> m_window;
SafePtr<Client> m_client;
+ std::vector<Scope*> m_local_scopes;
class SelectionHistory {
public:
diff --git a/src/diff.hh b/src/diff.hh
index 30a3884d..2f582b16 100644
--- a/src/diff.hh
+++ b/src/diff.hh
@@ -23,7 +23,7 @@ struct Snake
template<bool forward, typename IteratorA, typename IteratorB, typename Equal>
Snake find_end_snake_of_further_reaching_dpath(IteratorA a, int N, IteratorB b, int M,
- const int* V, const int D, const int k, Equal eq)
+ const int* V, const int D, const int k, Equal&& eq)
{
const bool add = k == -D or (k != D and V[k-1] < V[k+1]);
@@ -48,7 +48,7 @@ Snake find_end_snake_of_further_reaching_dpath(IteratorA a, int N, IteratorB b,
template<typename IteratorA, typename IteratorB, typename Equal>
Snake find_middle_snake(IteratorA a, int N, IteratorB b, int M,
- int* V1, int* V2, int cost_limit, Equal eq)
+ int* V1, int* V2, int cost_limit, Equal&& eq)
{
const int delta = N - M;
V1[1] = 0;
@@ -113,7 +113,7 @@ template<typename IteratorA, typename IteratorB, typename Equal, typename OnDiff
void find_diff_rec(IteratorA a, int begA, int endA,
IteratorB b, int begB, int endB,
int* V1, int* V2, int cost_limit,
- Equal eq, OnDiff&& on_diff)
+ Equal&& eq, OnDiff&& on_diff)
{
auto on_diff_ifn = [&](DiffOp op, int len) {
if (len != 0)
@@ -172,7 +172,7 @@ struct Diff
};
template<typename IteratorA, typename IteratorB, typename OnDiff, typename Equal = std::equal_to<>>
-void for_each_diff(IteratorA a, int N, IteratorB b, int M, OnDiff&& on_diff, Equal eq = Equal{})
+void for_each_diff(IteratorA a, int N, IteratorB b, int M, OnDiff&& on_diff, Equal&& eq = Equal{})
{
const int max = 2 * (N + M) + 1;
std::unique_ptr<int[]> data(new int[2*max]);
diff --git a/src/display_buffer.hh b/src/display_buffer.hh
index c13bd0b9..c4ea6fb5 100644
--- a/src/display_buffer.hh
+++ b/src/display_buffer.hh
@@ -138,11 +138,12 @@ public:
iterator insert(iterator pos, It beg, It end)
{
auto has_buffer_range = std::mem_fn(&DisplayAtom::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)
{
auto& last = *std::find_if(std::reverse_iterator(end), std::reverse_iterator(first), has_buffer_range);
- m_range.begin = std::min(m_range.begin, (*first).begin());
- m_range.end = std::max(m_range.end, last.end());
+ m_range.begin = had_range ? std::min(m_range.begin, (*first).begin()) : (*first).begin();
+ m_range.end = had_range ? std::max(m_range.end, last.end()) : last.end();
}
return m_atoms.insert(pos, beg, end);
}
diff --git a/src/event_manager.cc b/src/event_manager.cc
index 60c16eae..ed608fcc 100644
--- a/src/event_manager.cc
+++ b/src/event_manager.cc
@@ -84,17 +84,17 @@ bool EventManager::handle_next_events(EventMode mode, sigset_t* sigmask, bool bl
continue;
const int fd = watcher->fd();
- if (fd != -1)
- {
- max_fd = std::max(fd, max_fd);
- auto events = watcher->events();
- if (events & FdEvents::Read)
- FD_SET(fd, &rfds);
- if (events & FdEvents::Write)
- FD_SET(fd, &wfds);
- if (events & FdEvents::Except)
- FD_SET(fd, &efds);
- }
+ if (fd == -1)
+ continue;
+
+ max_fd = std::max(fd, max_fd);
+ auto events = watcher->events();
+ if (events & FdEvents::Read)
+ FD_SET(fd, &rfds);
+ if (events & FdEvents::Write)
+ FD_SET(fd, &wfds);
+ if (events & FdEvents::Except)
+ FD_SET(fd, &efds);
}
bool with_timeout = false;
diff --git a/src/face_registry.cc b/src/face_registry.cc
index 7364cc2a..d6c5cd53 100644
--- a/src/face_registry.cc
+++ b/src/face_registry.cc
@@ -204,6 +204,7 @@ FaceRegistry::FaceRegistry()
{ "MatchingChar", {Face{ Color::Default, Color::Default, Attribute::Bold }} },
{ "BufferPadding", {Face{ Color::Blue, Color::Default }} },
{ "Whitespace", {Face{ Color::Default, Color::Default, Attribute::FinalFg }} },
+ { "WhitespaceIndent", {Face{}, "Whitespace"} },
}
{}
diff --git a/src/face_registry.hh b/src/face_registry.hh
index e95a02fd..88e52980 100644
--- a/src/face_registry.hh
+++ b/src/face_registry.hh
@@ -24,6 +24,8 @@ class FaceRegistry : public SafeCountable
public:
FaceRegistry(FaceRegistry& parent) : SafeCountable{}, m_parent(&parent) {}
+ void reparent(FaceRegistry& parent) { m_parent = &parent; }
+
Face operator[](StringView facedesc) const;
Face operator[](const FaceSpec& facespec) const;
void add_face(StringView name, StringView facedesc, bool override = false);
diff --git a/src/file.cc b/src/file.cc
index 87112e24..ca2d2834 100644
--- a/src/file.cc
+++ b/src/file.cc
@@ -564,7 +564,7 @@ CandidateList complete_command(StringView prefix, ByteCount cursor_pos)
Vector<RankedMatch> matches;
for (auto& file : files)
{
- if (RankedMatch match{file, real_prefix})
+ if (RankedMatch match{file, fileprefix})
matches.push_back(match);
}
std::sort(matches.begin(), matches.end());
@@ -626,7 +626,7 @@ FsStatus get_fs_status(StringView filename)
{
MappedFile fd{filename};
- return {fd.st.st_mtim, fd.st.st_size, hash_data(fd.data, fd.st.st_size)};
+ return {fd.st.st_mtim, fd.st.st_size, murmur3(fd.data, fd.st.st_size)};
}
String get_kak_binary_path()
@@ -634,9 +634,10 @@ String get_kak_binary_path()
char buffer[2048];
#if defined(__linux__) or defined(__CYGWIN__) or defined(__gnu_hurd__)
ssize_t res = readlink("/proc/self/exe", buffer, 2048);
- kak_assert(res != -1);
- buffer[res] = '\0';
- return buffer;
+ if (res != -1 && res < 2048) {
+ buffer[res] = '\0';
+ return buffer;
+ }
#elif defined(__FreeBSD__) or defined(__NetBSD__)
#if defined(__FreeBSD__)
int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
@@ -644,37 +645,44 @@ String get_kak_binary_path()
int mib[] = {CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME};
#endif
size_t res = sizeof(buffer);
- sysctl(mib, 4, buffer, &res, NULL, 0);
- return buffer;
+ if (sysctl(mib, 4, buffer, &res, NULL, 0) != -1)
+ return buffer;
#elif defined(__APPLE__)
uint32_t bufsize = 2048;
- _NSGetExecutablePath(buffer, &bufsize);
- char* canonical_path = realpath(buffer, nullptr);
- String path = canonical_path;
- free(canonical_path);
- return path;
+ char* canonical_path = NULL;
+ if (_NSGetExecutablePath(buffer, &bufsize) != -1)
+ canonical_path = realpath(buffer, nullptr);
+ if (canonical_path) {
+ String path = canonical_path;
+ free(canonical_path);
+ return path;
+ }
#elif defined(__HAIKU__)
BApplication app("application/x-vnd.kakoune");
app_info info;
- status_t status = app.GetAppInfo(&info);
- kak_assert(status == B_OK);
- BPath path(&info.ref);
- return path.Path();
+ if (app.GetAppInfo(&info) == B_OK) {
+ BPath path(&info.ref);
+ return path.Path();
+ }
#elif defined(__DragonFly__)
ssize_t res = readlink("/proc/curproc/file", buffer, 2048);
- kak_assert(res != -1);
- buffer[res] = '\0';
- return buffer;
+ if (res != -1 && res < 2048) {
+ buffer[res] = '\0';
+ return buffer;
+ }
#elif defined(__OpenBSD__)
+ (void)buffer;
return KAK_BIN_PATH;
#elif defined(__sun__)
ssize_t res = readlink("/proc/self/path/a.out", buffer, 2048);
- kak_assert(res != -1);
- buffer[res] = '\0';
- return buffer;
+ if (res != -1 && res < 2048) {
+ buffer[res] = '\0';
+ return buffer;
+ }
#else
# error "finding executable path is not implemented on this platform"
#endif
+ throw runtime_error("unable to get the executable path");
}
}
diff --git a/src/hash.cc b/src/hash.cc
index 567d8733..3b9fb018 100644
--- a/src/hash.cc
+++ b/src/hash.cc
@@ -27,8 +27,8 @@ static inline uint32_t fmix(uint32_t h)
return h;
}
-// murmur3 hash, based on https://github.com/PeterScott/murmur3
-size_t hash_data(const char* input, size_t len)
+// based on https://github.com/PeterScott/murmur3
+size_t murmur3(const char* input, size_t len)
{
const uint8_t* data = reinterpret_cast<const uint8_t*>(input);
uint32_t hash = 0x1235678;
@@ -73,13 +73,13 @@ size_t hash_data(const char* input, size_t len)
UnitTest test_murmur_hash{[] {
{
constexpr char data[] = "Hello, World!";
- kak_assert(hash_data(data, strlen(data)) == 0xf816f95b);
+ kak_assert(murmur3(data, strlen(data)) == 0xf816f95b);
}
{
constexpr char data[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx";
- kak_assert(hash_data(data, strlen(data)) == 3551113186);
+ kak_assert(murmur3(data, strlen(data)) == 3551113186);
}
- kak_assert(hash_data("", 0) == 2572747774);
+ kak_assert(murmur3("", 0) == 2572747774);
}};
}
diff --git a/src/hash.hh b/src/hash.hh
index 8dcd719f..deed78f2 100644
--- a/src/hash.hh
+++ b/src/hash.hh
@@ -5,11 +5,23 @@
#include <utility>
#include <cstddef>
+#include <cstdint>
namespace Kakoune
{
-size_t hash_data(const char* data, size_t len);
+inline size_t fnv1a(const char* data, size_t len)
+{
+ constexpr uint32_t FNV_prime_32 = 16777619;
+ constexpr uint32_t offset_basis_32 = 2166136261;
+
+ uint32_t hash_value = offset_basis_32;
+ for (size_t i = 0; i < len; ++i)
+ hash_value = (hash_value ^ data[i]) * FNV_prime_32;
+ return hash_value;
+}
+
+size_t murmur3(const char* input, size_t len);
template<typename Type> requires std::is_integral_v<Type>
constexpr size_t hash_value(const Type& val)
diff --git a/src/hash_map.hh b/src/hash_map.hh
index 19406bd1..dcf76d42 100644
--- a/src/hash_map.hh
+++ b/src/hash_map.hh
@@ -74,7 +74,7 @@ struct HashIndex
auto target_slot = compute_slot(entry.hash);
for (auto slot = target_slot; slot < m_entries.size(); ++slot)
{
- if (m_entries[slot].index == -1)
+ if (m_entries[slot].index < 0)
{
m_entries[slot] = entry;
return;
@@ -104,7 +104,7 @@ struct HashIndex
// Recompact following entries
for (auto next = slot+1; next < m_entries.size(); ++next)
{
- if (m_entries[next].index == -1 or
+ if (m_entries[next].index < 0 or
compute_slot(m_entries[next].hash) == next)
break;
kak_assert(compute_slot(m_entries[next].hash) < next);
@@ -224,7 +224,7 @@ struct HashMap
for (auto slot = m_index.compute_slot(hash); slot < m_index.size(); ++slot)
{
auto& entry = m_index[slot];
- if (entry.index == -1)
+ if (entry.index < 0)
return -1;
if (entry.hash == hash and item_key(m_items[entry.index]) == key)
return entry.index;
diff --git a/src/highlighter_group.cc b/src/highlighter_group.cc
index e23b5979..813262c0 100644
--- a/src/highlighter_group.cc
+++ b/src/highlighter_group.cc
@@ -43,7 +43,10 @@ void HighlighterGroup::add_child(String name, std::unique_ptr<Highlighter>&& hl,
void HighlighterGroup::remove_child(StringView id)
{
- m_highlighters.remove(id);
+ auto it = m_highlighters.find(id);
+ if (it == m_highlighters.end())
+ throw child_not_found(format("no such id: '{}'", id));
+ m_highlighters.remove(it);
}
Highlighter& HighlighterGroup::get_child(StringView path)
diff --git a/src/highlighter_group.hh b/src/highlighter_group.hh
index a0873dc0..1502b796 100644
--- a/src/highlighter_group.hh
+++ b/src/highlighter_group.hh
@@ -43,6 +43,8 @@ class Highlighters : public SafeCountable
public:
Highlighters(Highlighters& parent) : SafeCountable{}, m_parent{&parent}, m_group{HighlightPass::All} {}
+ void reparent(Highlighters& parent) { m_parent = &parent; }
+
HighlighterGroup& group() { return m_group; }
const HighlighterGroup& group() const { return m_group; }
diff --git a/src/highlighters.cc b/src/highlighters.cc
index 7e6bd1c0..1ab6466d 100644
--- a/src/highlighters.cc
+++ b/src/highlighters.cc
@@ -367,16 +367,16 @@ template<typename RegexGetter, typename FaceGetter>
class DynamicRegexHighlighter : public Highlighter
{
public:
- DynamicRegexHighlighter(RegexGetter regex_getter, FaceGetter face_getter)
+ DynamicRegexHighlighter(RegexGetter get_regex, FaceGetter resolve_faces)
: Highlighter{HighlightPass::Colorize},
- m_regex_getter(std::move(regex_getter)),
- m_face_getter(std::move(face_getter)),
+ m_get_regex(std::move(get_regex)),
+ m_resolve_faces(std::move(resolve_faces)),
m_highlighter(Regex{}, FacesSpec{}) {}
void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange range) override
{
- Regex regex = m_regex_getter(context.context);
- FacesSpec face = regex.empty() ? FacesSpec{} : m_face_getter(context.context, regex);
+ Regex regex = m_get_regex(context.context);
+ FacesSpec face = regex.empty() ? FacesSpec{} : m_resolve_faces(regex);
if (regex != m_last_regex or face != m_last_face)
{
m_last_regex = std::move(regex);
@@ -390,10 +390,10 @@ public:
private:
Regex m_last_regex;
- RegexGetter m_regex_getter;
+ RegexGetter m_get_regex;
FacesSpec m_last_face;
- FaceGetter m_face_getter;
+ FaceGetter m_resolve_faces;
RegexHighlighter m_highlighter;
};
@@ -418,12 +418,12 @@ std::unique_ptr<Highlighter> create_dynamic_regex_highlighter(HighlighterParamet
faces.emplace_back(String{spec.begin(), colon}, parse_face({colon+1, spec.end()}));
}
- auto make_hl = [](auto& regex_getter, auto& face_getter) {
- return std::make_unique<DynamicRegexHighlighter<std::remove_cvref_t<decltype(regex_getter)>,
- std::remove_cvref_t<decltype(face_getter)>>>(
- std::move(regex_getter), std::move(face_getter));
+ auto make_hl = [](auto& get_regex, auto& resolve_faces) {
+ return std::make_unique<DynamicRegexHighlighter<std::remove_cvref_t<decltype(get_regex)>,
+ std::remove_cvref_t<decltype(resolve_faces)>>>(
+ std::move(get_regex), std::move(resolve_faces));
};
- auto get_face = [faces=std::move(faces)](const Context& context, const Regex& regex){
+ auto resolve_faces = [faces=std::move(faces)](const Regex& regex){
FacesSpec spec;
for (auto& face : faces)
{
@@ -450,7 +450,7 @@ std::unique_ptr<Highlighter> create_dynamic_regex_highlighter(HighlighterParamet
auto get_regex = [option_name = token->content](const Context& context) {
return context.options()[option_name].get<Regex>();
};
- return make_hl(get_regex, get_face);
+ return make_hl(get_regex, resolve_faces);
}
auto get_regex = [expr = params[0]](const Context& context){
@@ -465,7 +465,7 @@ std::unique_ptr<Highlighter> create_dynamic_regex_highlighter(HighlighterParamet
return Regex{};
}
};
- return make_hl(get_regex, get_face);
+ return make_hl(get_regex, resolve_faces);
}
const HighlighterDesc line_desc = {
@@ -719,7 +719,7 @@ struct WrapHighlighter : Highlighter
const ColumnCount marker_len = zero_if_greater(m_marker.column_length(), wrap_column);
for (auto buf_line = setup.first_line, win_line = 0_line;
- win_line < win_height or buf_line <= cursor.line;
+ win_line < win_height or (setup.ensure_cursor_visible and buf_line <= cursor.line);
++buf_line, ++setup.line_count)
{
if (buf_line >= buffer.line_count())
@@ -954,7 +954,7 @@ struct TabulationHighlighter : Highlighter
};
const HighlighterDesc show_whitespace_desc = {
- "Parameters: [-tab <separator>] [-tabpad <separator>] [-lf <separator>] [-spc <separator>] [-nbsp <separator>]\n"
+ "Parameters: [-tab <separator>] [-tabpad <separator>] [-lf <separator>] [-spc <separator>] [-nbsp <separator>] [-indent <separator>]\n"
"Display whitespaces using symbols",
{ {
{ "tab", { ArgCompleter{}, "replace tabulations with the given character" } },
@@ -962,15 +962,16 @@ const HighlighterDesc show_whitespace_desc = {
{ "spc", { ArgCompleter{}, "replace spaces with the given character" } },
{ "lf", { ArgCompleter{}, "replace line feeds with the given character" } },
{ "nbsp", { ArgCompleter{}, "replace non-breakable spaces with the given character" } },
+ { "indent", { ArgCompleter{}, "replace first space of every indent with the given character according to `indentwidth`" } },
{ "only-trailing", { {}, "only highlighting trailing whitespaces" } } },
ParameterDesc::Flags::None, 0, 0
}
};
struct ShowWhitespacesHighlighter : Highlighter
{
- ShowWhitespacesHighlighter(String tab, String tabpad, String spc, String lf, String nbsp, bool only_trailing)
+ ShowWhitespacesHighlighter(String tab, String tabpad, String spc, String lf, String nbsp, String indent, bool only_trailing)
: Highlighter{HighlightPass::Move}, m_tab{std::move(tab)}, m_tabpad{std::move(tabpad)},
- m_spc{std::move(spc)}, m_lf{std::move(lf)}, m_nbsp{std::move(nbsp)}, m_only_trailing{std::move(only_trailing)}
+ m_spc{std::move(spc)}, m_lf{std::move(lf)}, m_nbsp{std::move(nbsp)}, m_indent{std::move(indent)}, m_only_trailing{std::move(only_trailing)}
{}
static std::unique_ptr<Highlighter> create(HighlighterParameters params, Highlighter*)
@@ -985,19 +986,26 @@ struct ShowWhitespacesHighlighter : Highlighter
return value.str();
};
+ String indent = parser.get_switch("indent").value_or("│").str();
+ if (indent.char_length() > 1)
+ throw runtime_error{format("-indent expects a single character or empty parameter")};
+
return std::make_unique<ShowWhitespacesHighlighter>(
get_param("tab", "→"), get_param("tabpad", " "), get_param("spc", "·"),
- get_param("lf", "¬"), get_param("nbsp", "⍽"), only_trailing);
+ get_param("lf", "¬"), get_param("nbsp", "⍽"), indent, only_trailing);
}
private:
void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange) override
{
const int tabstop = context.context.options()["tabstop"].get<int>();
+ const int indentwidth = context.context.options()["indentwidth"].get<int>();
auto whitespaceface = context.context.faces()["Whitespace"];
+ auto indentface = context.context.faces()["WhitespaceIndent"];
const auto& buffer = context.context.buffer();
for (auto& line : display_buffer.lines())
{
+ bool is_indentation = true;
for (auto atom_it = line.begin(); atom_it != line.end(); ++atom_it)
{
if (atom_it->type() != DisplayAtom::Range)
@@ -1024,6 +1032,7 @@ private:
{
auto coord = it.coord();
Codepoint cp = utf8::read_codepoint(it, end);
+ auto face = whitespaceface;
if (is_whitespace(cp))
{
if (m_only_trailing and it.coord() <= last_non_space)
@@ -1040,21 +1049,36 @@ private:
const ColumnCount count = tabstop - (column % tabstop);
atom_it->replace(m_tab + String(m_tabpad[(CharCount)0], count - m_tab.column_length()));
}
- else if (cp == ' ')
- atom_it->replace(m_spc);
+ else if (cp == ' ') {
+ if (m_indent.empty() or indentwidth == 0 or not is_indentation) {
+ atom_it->replace(m_spc);
+ } else {
+ const ColumnCount column = get_column(buffer, tabstop, coord);
+ if (column % indentwidth == 0 and column != 0) {
+ atom_it->replace(m_indent);
+ face = indentface;
+ } else {
+ atom_it->replace(m_spc);
+ }
+ }
+ }
else if (cp == '\n')
atom_it->replace(m_lf);
else if (cp == 0xA0 or cp == 0x202F)
atom_it->replace(m_nbsp);
- atom_it->face = merge_faces(atom_it->face, whitespaceface);
+ atom_it->face = merge_faces(atom_it->face, face);
break;
}
+ else
+ {
+ is_indentation = false;
+ }
}
}
}
}
- const String m_tab, m_tabpad, m_spc, m_lf, m_nbsp;
+ const String m_tab, m_tabpad, m_spc, m_lf, m_nbsp, m_indent;
const bool m_only_trailing;
};
@@ -1368,23 +1392,26 @@ const HighlighterDesc flag_lines_desc = {
};
struct FlagLinesHighlighter : Highlighter
{
- FlagLinesHighlighter(String option_name, String default_face)
+ FlagLinesHighlighter(String option_name, String default_face, bool after)
: Highlighter{HighlightPass::Move},
m_option_name{std::move(option_name)},
- m_default_face{std::move(default_face)} {}
+ m_default_face{std::move(default_face)},
+ m_after(after) {}
static std::unique_ptr<Highlighter> create(HighlighterParameters params, Highlighter*)
{
- if (params.size() != 2)
- throw runtime_error("wrong parameter count");
+ ParametersParser parser{params, {
+ {{"after", {{}, "display at line end" }}},
+ ParameterDesc::Flags::SwitchesOnlyAtStart, 2, 2
+ }};
- const String& option_name = params[1];
- const String& default_face = params[0];
+ const String& default_face = parser[0];
+ const String& option_name = parser[1];
// throw if wrong option type
GlobalScope::instance().options()[option_name].get<LineAndSpecList>();
- return std::make_unique<FlagLinesHighlighter>(option_name, default_face);
+ return std::make_unique<FlagLinesHighlighter>(option_name, default_face, (bool)parser.get_switch("after"));
}
private:
@@ -1422,6 +1449,17 @@ private:
auto it = find_if(lines,
[&](const LineAndSpec& l)
{ return std::get<0>(l) == line_num; });
+ if (m_after)
+ {
+ if (it != lines.end())
+ {
+ DisplayLine& display_line = display_lines[it - lines.begin()];
+ std::copy(std::make_move_iterator(display_line.begin()),
+ std::make_move_iterator(display_line.end()),
+ std::inserter(line, line.end()));
+ }
+ continue;
+ }
if (it == lines.end())
line.insert(line.begin(), empty);
else
@@ -1440,6 +1478,9 @@ private:
void do_compute_display_setup(HighlightContext context, DisplaySetup& setup) const override
{
+ if (m_after)
+ return;
+
auto& line_flags = context.context.options()[m_option_name].get_mutable<LineAndSpecList>();
const auto& buffer = context.context.buffer();
update_line_specs_ifn(buffer, line_flags);
@@ -1461,6 +1502,7 @@ private:
String m_option_name;
String m_default_face;
+ bool m_after;
};
bool is_empty(const InclusiveBufferRange& range)
@@ -1997,7 +2039,10 @@ public:
if (id == m_default_region)
m_default_region = String{};
- m_regions.remove(id);
+ auto it = m_regions.find(id);
+ if (it == m_regions.end())
+ throw child_not_found(format("no such id: {}", id));
+ m_regions.remove(it);
++m_regions_timestamp;
}
diff --git a/src/hook_manager.hh b/src/hook_manager.hh
index 24e88e1a..228a4f23 100644
--- a/src/hook_manager.hh
+++ b/src/hook_manager.hh
@@ -30,6 +30,8 @@ enum class Hook
BufSetOption,
ClientCreate,
ClientClose,
+ ClientRenamed,
+ SessionRenamed,
InsertChar,
InsertDelete,
InsertIdle,
@@ -75,6 +77,8 @@ constexpr auto enum_desc(Meta::Type<Hook>)
{Hook::BufSetOption, "BufSetOption"},
{Hook::ClientCreate, "ClientCreate"},
{Hook::ClientClose, "ClientClose"},
+ {Hook::ClientRenamed, "ClientRenamed"},
+ {Hook::SessionRenamed, "SessionRenamed"},
{Hook::InsertChar, "InsertChar"},
{Hook::InsertDelete, "InsertDelete"},
{Hook::InsertIdle, "InsertIdle"},
@@ -119,6 +123,8 @@ public:
HookManager(HookManager& parent);
~HookManager();
+ void reparent(HookManager& parent) { m_parent = &parent; }
+
void add_hook(Hook hook, String group, HookFlags flags,
Regex filter, String commands, Context& context);
void remove_hooks(const Regex& regex);
diff --git a/src/input_handler.cc b/src/input_handler.cc
index 17e72874..99fb684e 100644
--- a/src/input_handler.cc
+++ b/src/input_handler.cc
@@ -42,7 +42,7 @@ public:
bool enabled() const { return &m_input_handler.current_mode() == this; }
Context& context() const { return m_input_handler.context(); }
- virtual DisplayLine mode_line() const = 0;
+ virtual ModeInfo mode_info() const = 0;
virtual KeymapMode keymap_mode() const = 0;
@@ -56,7 +56,6 @@ public:
}
using Insertion = InputHandler::Insertion;
- Insertion& last_insert() { return m_input_handler.m_last_insert; }
protected:
virtual void on_key(Key key, bool synthesized) = 0;
@@ -162,7 +161,10 @@ struct MouseHandler
case Key::Modifiers::MouseRelease: {
if (not m_dragging)
+ {
+ 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};
@@ -173,7 +175,10 @@ struct MouseHandler
case Key::Modifiers::MousePos: {
if (not m_dragging)
+ {
+ 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};
@@ -365,7 +370,7 @@ public:
m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));
}
- DisplayLine mode_line() const override
+ ModeInfo mode_info() const override
{
AtomList atoms;
auto num_sel = context().selections().size();
@@ -385,7 +390,7 @@ public:
atoms.emplace_back(" reg=", context().faces()["StatusLineInfo"]);
atoms.emplace_back(StringView(m_params.reg).str(), context().faces()["StatusLineValue"]);
}
- return atoms;
+ return {atoms, m_params};
}
KeymapMode keymap_mode() const override { return KeymapMode::Normal; }
@@ -652,7 +657,8 @@ public:
: InputMode(input_handler), m_callback(std::move(callback)), m_completer(std::move(completer)),
m_prompt(prompt.str()), m_prompt_face(face),
m_empty_text{std::move(emptystr)},
- m_line_editor{context().faces()}, m_flags(flags),
+ // This prompt may outlive local scopes so ignore local faces.
+ m_line_editor{context().faces(false)}, m_flags(flags),
m_was_interactive{not context().noninteractive()},
m_history{RegisterManager::instance()[history_register]},
m_current_history{-1},
@@ -946,9 +952,9 @@ public:
}
}
- DisplayLine mode_line() const override
+ ModeInfo mode_info() const override
{
- return { "prompt", context().faces()["StatusLineMode"] };
+ return {{ "prompt", context().faces()["StatusLineMode"] }, {}};
}
KeymapMode keymap_mode() const override { return KeymapMode::Prompt; }
@@ -1121,9 +1127,9 @@ public:
m_callback(key, context());
}
- DisplayLine mode_line() const override
+ ModeInfo mode_info() const override
{
- return { "enter key", context().faces()["StatusLineMode"] };
+ return {{ "enter key", context().faces()["StatusLineMode"] }, {}};
}
KeymapMode keymap_mode() const override { return m_keymap_mode; }
@@ -1140,11 +1146,12 @@ private:
class Insert : public InputMode
{
public:
- Insert(InputHandler& input_handler, InsertMode mode, int count)
+ Insert(InputHandler& input_handler, InsertMode mode, int count, Insertion* last_insert)
: InputMode(input_handler),
m_edition(context()),
m_selection_edition(context()),
m_completer(context()),
+ m_last_insert(last_insert),
m_restore_cursor(mode == InsertMode::Append),
m_auto_complete{context().options()["autocomplete"].get<AutoComplete>() & AutoComplete::Insert},
m_idle_timer{TimePoint::max(), context().flags() & Context::Flags::Draft ?
@@ -1157,11 +1164,14 @@ public:
{
context().buffer().throw_if_read_only();
- last_insert().recording.set();
- last_insert().mode = mode;
- last_insert().keys.clear();
- last_insert().disable_hooks = context().hooks_disabled();
- last_insert().count = count;
+ if (m_last_insert)
+ {
+ m_last_insert->recording.set();
+ m_last_insert->mode = mode;
+ m_last_insert->keys.clear();
+ m_last_insert->disable_hooks = context().hooks_disabled();
+ m_last_insert->count = count;
+ }
prepare(mode, count);
}
@@ -1177,7 +1187,8 @@ public:
if (not from_push)
{
- last_insert().recording.unset();
+ if (m_last_insert)
+ m_last_insert->recording.unset();
auto& selections = context().selections();
if (m_restore_cursor)
@@ -1191,7 +1202,7 @@ public:
}
}
- void on_key(Key key, bool) override
+ void on_key(Key key, bool synthesized) override
{
auto& buffer = context().buffer();
@@ -1289,22 +1300,13 @@ public:
}, "enter register name", register_doc.str());
update_completions = false;
}
- else if (key == ctrl('n'))
- {
- last_insert().keys.pop_back();
- m_completer.select(1, true, last_insert().keys);
- update_completions = false;
- }
- else if (key == ctrl('p'))
- {
- last_insert().keys.pop_back();
- m_completer.select(-1, true, last_insert().keys);
- update_completions = false;
- }
- else if (key.modifiers == Key::Modifiers::MenuSelect)
+ else if (key == ctrl('n') or key == ctrl('p') or key.modifiers == Key::Modifiers::MenuSelect)
{
- last_insert().keys.pop_back();
- m_completer.select(key.key, false, last_insert().keys);
+ if (m_last_insert and not synthesized)
+ m_last_insert->keys.pop_back();
+ bool relative = key.modifiers != Key::Modifiers::MenuSelect;
+ int index = relative ? (key == ctrl('n') ? 1 : -1) : key.key;
+ m_completer.select(index, relative, m_last_insert ? &m_last_insert->keys : nullptr);
update_completions = false;
}
else if (key == ctrl('x'))
@@ -1375,15 +1377,16 @@ public:
m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));
}
- DisplayLine mode_line() const override
+ ModeInfo mode_info() const override
{
auto num_sel = context().selections().size();
auto main_index = context().selections().main_index();
- return {AtomList{ { "insert", context().faces()["StatusLineMode"] },
- { " ", context().faces()["StatusLine"] },
- { num_sel == 1 ? format("{} sel", num_sel)
- : format("{} sels ({})", num_sel, main_index + 1),
- context().faces()["StatusLineInfo"] } }};
+ return {{AtomList{ { "insert", context().faces()["StatusLineMode"] },
+ { " ", context().faces()["StatusLine"] },
+ { num_sel == 1 ? format("{} sel", num_sel)
+ : format("{} sels ({})", num_sel, main_index + 1),
+ context().faces()["StatusLineInfo"] } }},
+ {}};
}
KeymapMode keymap_mode() const override { return KeymapMode::Insert; }
@@ -1503,6 +1506,7 @@ private:
ScopedEdition m_edition;
ScopedSelectionEdition m_selection_edition;
InsertCompleter m_completer;
+ Insertion* m_last_insert;
const bool m_restore_cursor;
bool m_auto_complete;
Timer m_idle_timer;
@@ -1559,7 +1563,7 @@ void InputHandler::reset_normal_mode()
void InputHandler::insert(InsertMode mode, int count)
{
- push_mode(new InputModes::Insert(*this, mode, count));
+ push_mode(new InputModes::Insert(*this, mode, count, m_handle_key_level <= 1 ? &m_last_insert : nullptr));
}
void InputHandler::repeat_last_insert()
@@ -1571,19 +1575,12 @@ void InputHandler::repeat_last_insert()
m_last_insert.recording)
throw runtime_error{"repeating last insert not available in this context"};
- Vector<Key> keys;
- swap(keys, m_last_insert.keys);
ScopedSetBool disable_hooks(context().hooks_disabled(),
m_last_insert.disable_hooks);
- push_mode(new InputModes::Insert(*this, m_last_insert.mode, m_last_insert.count));
- for (auto& key : keys)
- {
- // refill last_insert, this is very inefficient, but necessary at the moment
- // to properly handle insert completion
- m_last_insert.keys.push_back(key);
- current_mode().handle_key(key, true);
- }
+ push_mode(new InputModes::Insert(*this, m_last_insert.mode, m_last_insert.count, nullptr));
+ for (auto& key : m_last_insert.keys)
+ handle_key(key);
kak_assert(dynamic_cast<InputModes::Normal*>(&current_mode()) != nullptr);
}
@@ -1654,11 +1651,8 @@ void InputHandler::handle_key(Key key)
++m_handle_key_level;
auto dec = on_scope_end([this]{ --m_handle_key_level;} );
- auto process_key = [&](Key key, bool synthesized) {
- if (m_last_insert.recording)
- m_last_insert.keys.push_back(key);
- current_mode().handle_key(key, synthesized);
- };
+ if (m_last_insert.recording and m_handle_key_level <= 1)
+ m_last_insert.keys.push_back(key);
const auto keymap_mode = current_mode().keymap_mode();
KeymapManager& keymaps = m_context.keymaps();
@@ -1667,10 +1661,10 @@ void InputHandler::handle_key(Key key)
ScopedSetBool noninteractive{context().noninteractive()};
for (auto& k : keymaps.get_mapping_keys(key, keymap_mode))
- process_key(k, true);
+ current_mode().handle_key(k, true);
}
else
- process_key(key, m_handle_key_level > 1);
+ current_mode().handle_key(key, m_handle_key_level > 1);
// do not record the key that made us enter or leave recording mode,
// and the ones that are triggered recursively by previous keys.
@@ -1715,9 +1709,9 @@ void InputHandler::stop_recording()
m_recording_level = -1;
}
-DisplayLine InputHandler::mode_line() const
+ModeInfo InputHandler::mode_info() const
{
- return current_mode().mode_line();
+ return current_mode().mode_info();
}
std::pair<CursorMode, DisplayCoord> InputHandler::get_cursor_info() const
diff --git a/src/input_handler.hh b/src/input_handler.hh
index eeb42464..ba73d2b1 100644
--- a/src/input_handler.hh
+++ b/src/input_handler.hh
@@ -4,8 +4,10 @@
#include "completion.hh"
#include "constexpr_utils.hh"
#include "context.hh"
+#include "env_vars.hh"
#include "face.hh"
#include "normal.hh"
+#include "optional.hh"
#include "keys.hh"
#include "string.hh"
#include "utils.hh"
@@ -52,6 +54,12 @@ enum class InsertMode : unsigned
OpenLineAbove
};
+struct ModeInfo
+{
+ DisplayLine display_line;
+ Optional<NormalParams> normal_params;
+};
+
class InputHandler : public SafeCountable
{
public:
@@ -97,7 +105,7 @@ public:
Context& context() { return m_context; }
const Context& context() const { return m_context; }
- DisplayLine mode_line() const;
+ ModeInfo mode_info() const;
std::pair<CursorMode, DisplayCoord> get_cursor_info() const;
diff --git a/src/insert_completer.cc b/src/insert_completer.cc
index af387506..3e1bcd2d 100644
--- a/src/insert_completer.cc
+++ b/src/insert_completer.cc
@@ -403,7 +403,10 @@ InsertCompletion complete_line(const SelectionList& sels,
}
InsertCompleter::InsertCompleter(Context& context)
- : m_context(context), m_options(context.options()), m_faces(context.faces())
+ : m_context(context),
+ // local scopes might go away before completion ends, make sure to register on a long lived one
+ m_options(context.scope(false).options()),
+ m_faces(context.scope(false).faces())
{
m_options.register_watcher(*this);
}
@@ -413,7 +416,7 @@ InsertCompleter::~InsertCompleter()
m_options.unregister_watcher(*this);
}
-void InsertCompleter::select(int index, bool relative, Vector<Key>& keystrokes)
+void InsertCompleter::select(int index, bool relative, Vector<Key>* keystrokes)
{
m_enabled = true;
if (not setup_ifn())
@@ -450,12 +453,15 @@ void InsertCompleter::select(int index, bool relative, Vector<Key>& keystrokes)
m_context.client().menu_select(m_current_candidate);
}
- for (auto i = 0_byte; i < prefix_len; ++i)
- keystrokes.emplace_back(Key::Backspace);
- for (auto i = 0_byte; i < suffix_len; ++i)
- keystrokes.emplace_back(Key::Delete);
- for (auto& c : candidate.completion)
- keystrokes.emplace_back(c);
+ if (keystrokes)
+ {
+ for (auto i = 0_byte; i < prefix_len; ++i)
+ keystrokes->emplace_back(Key::Backspace);
+ for (auto i = 0_byte; i < suffix_len; ++i)
+ keystrokes->emplace_back(Key::Delete);
+ for (auto& c : candidate.completion)
+ keystrokes->emplace_back(c);
+ }
if (not candidate.on_select.empty())
CommandManager::instance().execute(candidate.on_select, m_context);
diff --git a/src/insert_completer.hh b/src/insert_completer.hh
index cce433a2..3d5f2ca6 100644
--- a/src/insert_completer.hh
+++ b/src/insert_completer.hh
@@ -78,7 +78,7 @@ public:
InsertCompleter& operator=(const InsertCompleter&) = delete;
~InsertCompleter();
- void select(int index, bool relative, Vector<Key>& keystrokes);
+ void select(int index, bool relative, Vector<Key>* keystrokes);
void update(bool allow_implicit);
void try_accept();
void reset();
diff --git a/src/keymap_manager.hh b/src/keymap_manager.hh
index 208b44f1..4819be05 100644
--- a/src/keymap_manager.hh
+++ b/src/keymap_manager.hh
@@ -31,6 +31,8 @@ class KeymapManager
public:
KeymapManager(KeymapManager& parent) : m_parent(&parent) {}
+ void reparent(KeymapManager& parent) { m_parent = &parent; }
+
using KeyList = Vector<Key, MemoryDomain::Mapping>;
void map_key(Key key, KeymapMode mode, KeyList mapping, String docstring);
void unmap_key(Key key, KeymapMode mode);
diff --git a/src/line_modification.cc b/src/line_modification.cc
index 5b5f523d..49cadf62 100644
--- a/src/line_modification.cc
+++ b/src/line_modification.cc
@@ -196,7 +196,7 @@ void LineRangeSet::remove_range(LineRange range)
UnitTest test_line_modifications{[]()
{
- auto make_lines = [](auto&&... lines) { return BufferLines{StringData::create({lines})...}; };
+ auto make_lines = [](auto&&... lines) { return BufferLines{StringData::create(lines)...}; };
{
Buffer buffer("test", Buffer::Flags::None, make_lines("line 1\n", "line 2\n"));
diff --git a/src/main.cc b/src/main.cc
index 4d14565a..5b316eaa 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -45,13 +45,21 @@ struct {
unsigned int version;
StringView notes;
} constexpr version_notes[] = { {
- 0,
+ 20240518,
+ "» Fix tests failing on some platforms\n"
+ }, {
+ 20240509,
+ "» {+u}flag-lines -after{} highlighter\n"
"» asynchronous {+u}shell-script-candidates{} completion\n"
"» {+b}%val\\{window_range}{} is now emitted as separate strings\n"
"» {+b}+{} only duplicates identical selections a single time\n"
"» {+u}daemonize-session{} command\n"
"» view mode and mouse scrolling no longer change selections\n"
- "» {+u}git apply/edit/grep{} commands\n"
+ "» {+u}git apply/blame-jump/edit/grep{} commands\n"
+ "» {+u}git blame{} works in {+u}git-diff{} and {+u}git-log{} buffers\n"
+ "» custom completions are no longer sorted if the typed text is empty\n"
+ "» {+u}terminal{} now selects implementation based on windowing options\n"
+ "» {+u}local{} scopes\n"
}, {
20230805,
"» Fix FreeBSD/MacOS clang compilation\n"
diff --git a/src/normal.cc b/src/normal.cc
index 019ed58a..128a7193 100644
--- a/src/normal.cc
+++ b/src/normal.cc
@@ -399,6 +399,13 @@ void view_commands(Context& context, NormalParams params)
case 'b':
window.display_line_at(cursor.line, window.dimensions().line-1);
break;
+ case '<':
+ window.display_column_at(context.buffer()[cursor.line].column_count_to(cursor.column), 0);
+ break;
+ case '>':
+ window.display_column_at(context.buffer()[cursor.line].column_count_to(cursor.column),
+ window.dimensions().column-1);
+ break;
case 'h':
window.scroll(-std::max<ColumnCount>(1, count));
break;
@@ -420,6 +427,8 @@ void view_commands(Context& context, NormalParams params)
{{'m'}, "center cursor (horizontally)"},
{{'t'}, "cursor on top"},
{{'b'}, "cursor on bottom"},
+ {{'<'}, "cursor on left"},
+ {{'>'}, "cursor on right"},
{{'h'}, "scroll left"},
{{'j'}, "scroll down"},
{{'k'}, "scroll up"},
@@ -694,7 +703,7 @@ void paste(Context& context, NormalParams params)
ScopedEdition edition(context);
ScopedSelectionEdition selection_edition{context};
context.selections().for_each([&, last=BufferCoord{}](size_t index, Selection& sel) mutable {
- auto& str = strings[std::min(strings.size()-1, index)];
+ auto& str = strings[index % strings.size()];
auto& min = sel.min();
auto& max = sel.max();
BufferRange range = (mode == PasteMode::Replace) ?
@@ -866,7 +875,8 @@ void regex_prompt(Context& context, String prompt, char reg, T func)
RegisterManager::instance()[reg].set(context, str.str());
break;
case PromptEvent::Validate:
- RegisterManager::instance()[reg].set(context, str.str());
+ if (not str.empty())
+ RegisterManager::instance()[reg].set(context, str.str());
context.push_jump();
break;
}
@@ -1704,6 +1714,7 @@ void tabs_to_spaces(Context& context, NormalParams params)
Vector<Selection> tabs;
Vector<String> spaces;
ScopedSelectionEdition selection_edition{context};
+ ScopedEdition edition{context};
for (auto& sel : context.selections())
{
for (auto it = buffer.iterator_at(sel.min()),
@@ -1729,6 +1740,7 @@ void spaces_to_tabs(Context& context, NormalParams params)
const ColumnCount tabstop = params.count == 0 ? opt_tabstop : params.count;
Vector<Selection> spaces;
ScopedSelectionEdition selection_edition{context};
+ ScopedEdition edition{context};
for (auto& sel : context.selections())
{
for (auto it = buffer.iterator_at(sel.min()),
diff --git a/src/option_manager.cc b/src/option_manager.cc
index 5dd3d612..0a77548f 100644
--- a/src/option_manager.cc
+++ b/src/option_manager.cc
@@ -28,6 +28,15 @@ OptionManager::~OptionManager()
kak_assert(m_watchers.empty());
}
+void OptionManager::reparent(OptionManager& parent)
+{
+ if (m_parent)
+ m_parent->unregister_watcher(*this);
+
+ m_parent = &parent;
+ parent.register_watcher(*this);
+}
+
void OptionManager::register_watcher(OptionManagerWatcher& watcher) const
{
kak_assert(not contains(m_watchers, &watcher));
diff --git a/src/option_manager.hh b/src/option_manager.hh
index e4dc230f..3a4bb5a5 100644
--- a/src/option_manager.hh
+++ b/src/option_manager.hh
@@ -89,6 +89,8 @@ public:
OptionManager(OptionManager& parent);
~OptionManager();
+ void reparent(OptionManager& parent);
+
Option& operator[] (StringView name);
const Option& operator[] (StringView name) const;
Option& get_local_option(StringView name);
diff --git a/src/ranked_match.cc b/src/ranked_match.cc
index 0ace0a14..a13decdf 100644
--- a/src/ranked_match.cc
+++ b/src/ranked_match.cc
@@ -45,8 +45,8 @@ static int count_word_boundaries_match(StringView candidate, StringView query)
{
const Codepoint c = *it;
const bool is_word_boundary = prev == 0 or
- (!iswalnum((wchar_t)prev) and iswalnum((wchar_t)c)) or
- (iswlower((wchar_t)prev) and iswupper((wchar_t)c));
+ (!is_word(prev, {}) and is_word(c, {})) or
+ (is_lower(prev) and is_upper(c));
prev = c;
if (not is_word_boundary)
@@ -56,7 +56,7 @@ static int count_word_boundaries_match(StringView candidate, StringView query)
for (auto qit = query_it; qit != query.end(); ++qit)
{
const Codepoint qc = *qit;
- if (qc == (iswlower((wchar_t)qc) ? lc : c))
+ if (qc == (is_lower(qc) ? lc : c))
{
++count;
query_it = qit+1;
@@ -71,7 +71,7 @@ static int count_word_boundaries_match(StringView candidate, StringView query)
static bool smartcase_eq(Codepoint candidate, Codepoint query)
{
- return query == (iswlower((wchar_t)query) ? to_lower(candidate) : candidate);
+ return query == (is_lower(query) ? to_lower(candidate) : candidate);
}
struct SubseqRes
diff --git a/src/regex.cc b/src/regex.cc
index df5af661..b62bdddf 100644
--- a/src/regex.cc
+++ b/src/regex.cc
@@ -6,10 +6,10 @@ namespace Kakoune
{
Regex::Regex(StringView re, RegexCompileFlags flags)
- : m_impl{new CompiledRegex{}},
+ : m_impl{new Impl{}},
m_str{re.str()}
{
- *m_impl = compile_regex(re, flags);
+ static_cast<CompiledRegex&>(*m_impl) = compile_regex(re, flags);
}
int Regex::named_capture_index(StringView name) const
diff --git a/src/regex.hh b/src/regex.hh
index c0f3970e..c65ba60c 100644
--- a/src/regex.hh
+++ b/src/regex.hh
@@ -3,6 +3,7 @@
#include "string.hh"
#include "regex_impl.hh"
+#include "ref_ptr.hh"
namespace Kakoune
{
@@ -27,7 +28,9 @@ public:
const CompiledRegex* impl() const { return m_impl.get(); }
private:
- RefPtr<CompiledRegex> m_impl;
+ struct Impl : RefCountable, CompiledRegex {};
+
+ RefPtr<Impl> m_impl;
String m_str;
};
diff --git a/src/regex_impl.cc b/src/regex_impl.cc
index c793a927..3388cde5 100644
--- a/src/regex_impl.cc
+++ b/src/regex_impl.cc
@@ -17,7 +17,6 @@
namespace Kakoune
{
-constexpr Codepoint CompiledRegex::StartDesc::other;
constexpr Codepoint CompiledRegex::StartDesc::count;
struct ParsedRegex
@@ -46,31 +45,15 @@ struct ParsedRegex
struct Quantifier
{
- enum Type : char
- {
- One,
- Optional,
- RepeatZeroOrMore,
- RepeatOneOrMore,
- RepeatMinMax,
- };
- Type type = One;
+ static constexpr int16_t infinite = std::numeric_limits<int16_t>::max();
+
+ int16_t min = 0, max = 0;
bool greedy = true;
- int16_t min = -1, max = -1;
- bool allows_none() const
- {
- return type == Quantifier::Optional or
- type == Quantifier::RepeatZeroOrMore or
- (type == Quantifier::RepeatMinMax and min <= 0);
- }
+ bool allows_none() const { return min == 0; }
+ bool allows_infinite_repeat() const { return max == infinite; };
- bool allows_infinite_repeat() const
- {
- return type == Quantifier::RepeatZeroOrMore or
- type == Quantifier::RepeatOneOrMore or
- (type == Quantifier::RepeatMinMax and max < 0);
- };
+ friend bool operator==(Quantifier, Quantifier) = default;
};
using NodeIndex = int16_t;
@@ -554,16 +537,16 @@ private:
ParsedRegex::Quantifier quantifier()
{
if (at_end())
- return {ParsedRegex::Quantifier::One};
+ return {1, 1};
constexpr int max_repeat = 1000;
- auto read_bound = [&]() {
+ auto read_bound = [&]() -> Optional<int16_t> {
int16_t res = 0;
for (auto begin = m_pos; m_pos != m_regex.end(); ++m_pos)
{
const auto cp = *m_pos;
if (cp < '0' or cp > '9')
- return m_pos == begin ? (int16_t)-1 : res;
+ return m_pos == begin ? Optional<int16_t>{} : res;
res = res * 10 + cp - '0';
if (res > max_repeat)
parse_error(format("Explicit quantifier is too big, maximum is {}", max_repeat));
@@ -580,28 +563,28 @@ private:
switch (*m_pos)
{
- case '*': ++m_pos; return {ParsedRegex::Quantifier::RepeatZeroOrMore, check_greedy()};
- case '+': ++m_pos; return {ParsedRegex::Quantifier::RepeatOneOrMore, check_greedy()};
- case '?': ++m_pos; return {ParsedRegex::Quantifier::Optional, check_greedy()};
+ case '*': ++m_pos; return {0, ParsedRegex::Quantifier::infinite, check_greedy()};
+ case '+': ++m_pos; return {1, ParsedRegex::Quantifier::infinite, check_greedy()};
+ case '?': ++m_pos; return {0, 1, check_greedy()};
case '{':
{
++m_pos;
- const int16_t min = read_bound();
+ const int16_t min = read_bound().value_or(int16_t{});
int16_t max = min;
if (*m_pos == ',')
{
++m_pos;
- max = read_bound();
+ max = read_bound().value_or(ParsedRegex::Quantifier::infinite);
}
if (*m_pos++ != '}')
parse_error("expected closing bracket");
- return {ParsedRegex::Quantifier::RepeatMinMax, check_greedy(), min, max};
+ return {min, max, check_greedy()};
}
- default: return {ParsedRegex::Quantifier::One};
+ default: return {1, 1};
}
}
- NodeIndex add_node(ParsedRegex::Op op, Codepoint value = -1, ParsedRegex::Quantifier quantifier = {ParsedRegex::Quantifier::One})
+ NodeIndex add_node(ParsedRegex::Op op, Codepoint value = -1, ParsedRegex::Quantifier quantifier = {1, 1})
{
constexpr auto max_nodes = std::numeric_limits<int16_t>::max();
const NodeIndex res = m_parsed_regex.nodes.size();
@@ -641,7 +624,7 @@ private:
to_underlying(Lookaround::OpBegin) <= child.value and
child.value < to_underlying(Lookaround::OpEnd))
parse_error("Lookaround does not support literals codepoint between 0xF0000 and 0xFFFFD");
- if (child.quantifier.type != ParsedRegex::Quantifier::One)
+ if (child.quantifier != ParsedRegex::Quantifier{1, 1})
parse_error("Quantifiers cannot be used in lookarounds");
}
}
@@ -903,17 +886,23 @@ private:
}
// Mutate start_desc with informations on which Codepoint could start a match.
- // Returns true if the node possibly does not consume the char, in which case
- // the next node would still be relevant for the parent node start chars computation.
+ // Returns true if the subsequent nodes are still relevant for computing the
+ // start desc
template<RegexMode direction>
bool compute_start_desc(ParsedRegex::NodeIndex index,
CompiledRegex::StartDesc& start_desc) const
{
+ // fill all bytes that mark the start of an utf8 multi byte sequence
+ auto add_multi_byte_utf8 = [&] {
+ std::fill(start_desc.map + 0b11000000, start_desc.map + 0b11111000, true);
+ };
+ static constexpr Codepoint single_byte_limit = 128;
+
auto& node = get_node(index);
switch (node.op)
{
case ParsedRegex::Literal:
- if (node.value < CompiledRegex::StartDesc::count)
+ if (node.value < single_byte_limit)
{
if (node.ignore_case)
{
@@ -924,14 +913,24 @@ private:
start_desc.map[node.value] = true;
}
else
- start_desc.map[CompiledRegex::StartDesc::other] = true;
+ add_multi_byte_utf8();
return node.quantifier.allows_none();
case ParsedRegex::AnyChar:
+ if (start_desc.offset + node.quantifier.max <= CompiledRegex::StartDesc::OffsetLimits::max())
+ {
+ start_desc.offset += node.quantifier.max;
+ return true;
+ }
for (auto& b : start_desc.map)
b = true;
return node.quantifier.allows_none();
case ParsedRegex::AnyCharExceptNewLine:
- for (Codepoint cp = 0; cp < CompiledRegex::StartDesc::count; ++cp)
+ if (start_desc.offset + node.quantifier.max <= CompiledRegex::StartDesc::OffsetLimits::max())
+ {
+ start_desc.offset += node.quantifier.max;
+ return true;
+ }
+ for (Codepoint cp = 0; cp < single_byte_limit; ++cp)
{
if (cp != '\n')
start_desc.map[cp] = true;
@@ -946,33 +945,33 @@ private:
{
for (auto& range : character_class.ranges)
{
- const auto clamp = [](Codepoint cp) { return std::min(CompiledRegex::StartDesc::count, cp); };
+ const auto clamp = [](Codepoint cp) { return std::min(single_byte_limit, cp); };
for (auto cp = clamp(range.min), end = clamp(range.max + 1); cp < end; ++cp)
start_desc.map[cp] = true;
- if (range.max >= CompiledRegex::StartDesc::count)
- start_desc.map[CompiledRegex::StartDesc::other] = true;
+ if (range.max >= single_byte_limit)
+ add_multi_byte_utf8();
}
}
else
{
- for (Codepoint cp = 0; cp < CompiledRegex::StartDesc::count; ++cp)
+ for (Codepoint cp = 0; cp < single_byte_limit; ++cp)
{
if (start_desc.map[cp] or character_class.matches(cp))
start_desc.map[cp] = true;
}
}
- start_desc.map[CompiledRegex::StartDesc::other] = true;
+ add_multi_byte_utf8();
return node.quantifier.allows_none();
}
case ParsedRegex::CharType:
{
const CharacterType ctype = (CharacterType)node.value;
- for (Codepoint cp = 0; cp < CompiledRegex::StartDesc::count; ++cp)
+ for (Codepoint cp = 0; cp < single_byte_limit; ++cp)
{
if (is_ctype(ctype, cp))
start_desc.map[cp] = true;
}
- start_desc.map[CompiledRegex::StartDesc::other] = true;
+ add_multi_byte_utf8();
return node.quantifier.allows_none();
}
case ParsedRegex::Sequence:
@@ -1089,6 +1088,12 @@ String dump_regex(const CompiledRegex& program)
case CompiledRegex::AnyCharExceptNewLine:
res += "anything but newline\n";
break;
+ case CompiledRegex::CharClass:
+ res += format("character class {}\n", inst.param.character_class_index);
+ break;
+ case CompiledRegex::CharType:
+ res += format("character type {}\n", to_underlying(inst.param.character_type));
+ break;
case CompiledRegex::Jump:
res += format("jump {}\n", inst.param.jump_target);
break;
@@ -1102,12 +1107,6 @@ String dump_regex(const CompiledRegex& program)
case CompiledRegex::Save:
res += format("save {}\n", inst.param.save_index);
break;
- case CompiledRegex::CharClass:
- res += format("character class {}\n", inst.param.character_class_index);
- break;
- case CompiledRegex::CharType:
- res += format("character type {}\n", to_underlying(inst.param.character_type));
- break;
case CompiledRegex::LineAssertion:
res += format("line {}\n", inst.param.line_start ? "start" : "end");;
break;
@@ -1149,7 +1148,7 @@ String dump_regex(const CompiledRegex& program)
res += (char)c;
}
}
- res += "]\n";
+ res += format("]+{}\n", static_cast<int>(desc.offset));
};
if (program.forward_start_desc)
dump_start_desc(*program.forward_start_desc, "forward");
@@ -1568,6 +1567,17 @@ auto test_regex = UnitTest{[]{
}
{
+ TestVM<RegexMode::Forward | RegexMode::Search> vm{"(.{3,4}|f)oo"};
+ kak_assert(vm.forward_start_desc and vm.forward_start_desc->offset == 4);
+ for (int c = 0; c < CompiledRegex::StartDesc::count; ++c)
+ kak_assert(vm.forward_start_desc->map[c] == (c == 'f' or c == 'o'));
+
+ kak_assert(vm.exec("xxxoo", RegexExecFlags::None));
+ kak_assert(vm.exec("xfoo", RegexExecFlags::None));
+ kak_assert(not vm.exec("😄xoo", 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 f59b2249..46267f7a 100644
--- a/src/regex_impl.hh
+++ b/src/regex_impl.hh
@@ -3,12 +3,13 @@
#include "exception.hh"
#include "flags.hh"
-#include "ref_ptr.hh"
#include "unicode.hh"
#include "utf8.hh"
#include "vector.hh"
#include "utils.hh"
+#include <bit>
+
namespace Kakoune
{
@@ -66,7 +67,7 @@ struct CharacterClass
};
-struct CompiledRegex : RefCountable, UseMemoryDomain<MemoryDomain::Regex>
+struct CompiledRegex : UseMemoryDomain<MemoryDomain::Regex>
{
enum Op : char
{
@@ -152,8 +153,9 @@ struct CompiledRegex : RefCountable, UseMemoryDomain<MemoryDomain::Regex>
struct StartDesc : UseMemoryDomain<MemoryDomain::Regex>
{
- static constexpr Codepoint count = 128;
- static constexpr Codepoint other = 0;
+ static constexpr Codepoint count = 256;
+ using OffsetLimits = std::numeric_limits<uint8_t>;
+ uint8_t offset = 0;
bool map[count];
};
@@ -233,12 +235,11 @@ public:
~ThreadedRegexVM()
{
- for (auto* saves : m_saves)
+ for (auto& saves : m_saves)
{
- for (size_t i = m_program.save_count-1; i > 0; --i)
- saves->pos[i].~Iterator();
- saves->~Saves();
- operator delete(saves);
+ for (int i = m_program.save_count-1; i >= 0; --i)
+ saves.pos[i].~Iterator();
+ operator delete(saves.pos, m_program.save_count * sizeof(Iterator));
}
}
@@ -271,14 +272,14 @@ public:
{
if (search)
{
- to_next_start(start, config, *start_desc);
+ 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 < StartDesc::count) ? c : StartDesc::other])
+ if (not start_desc->map[c])
return false;
}
}
@@ -289,42 +290,50 @@ public:
ArrayView<const Iterator> captures() const
{
if (m_captures >= 0)
- return { m_saves[m_captures]->pos, m_program.save_count };
+ {
+ auto& saves = m_saves[m_captures];
+ for (int i = 0; i < m_program.save_count; ++i)
+ {
+ if ((saves.valid_mask & (1 << i)) == 0)
+ saves.pos[i] = Iterator{};
+ }
+ return { saves.pos, m_program.save_count };
+ }
return {};
}
private:
struct Saves
{
- int16_t refcount;
- int16_t next_free;
- Iterator pos[1];
+ int32_t refcount;
+ union {
+ int32_t next_free;
+ uint32_t valid_mask;
+ };
+ Iterator* pos;
};
template<bool copy>
- int16_t new_saves(Iterator* pos)
+ int16_t new_saves(Iterator* pos, uint32_t valid_mask)
{
kak_assert(not copy or pos != nullptr);
const auto count = m_program.save_count;
if (m_first_free >= 0)
{
const int16_t res = m_first_free;
- Saves& saves = *m_saves[res];
+ Saves& saves = m_saves[res];
m_first_free = saves.next_free;
kak_assert(saves.refcount == 1);
- if (copy)
- std::copy_n(pos, count, saves.pos);
- else
- std::fill_n(saves.pos, count, Iterator{});
-
+ if constexpr (copy)
+ std::copy_n(pos, std::bit_width(valid_mask), saves.pos);
+ saves.valid_mask = valid_mask;
return res;
}
- void* ptr = operator new (sizeof(Saves) + (count-1) * sizeof(Iterator));
- Saves* saves = new (ptr) Saves{1, 0, {copy ? pos[0] : Iterator{}}};
- for (size_t i = 1; i < count; ++i)
- new (&saves->pos[i]) Iterator{copy ? pos[i] : Iterator{}};
- m_saves.push_back(saves);
+ auto* new_pos = reinterpret_cast<Iterator*>(operator new (count * sizeof(Iterator)));
+ for (size_t i = 0; i < count; ++i)
+ new (new_pos+i) Iterator{copy ? pos[i] : Iterator{}};
+ m_saves.push_back({1, {.valid_mask=valid_mask}, new_pos});
return static_cast<int16_t>(m_saves.size() - 1);
}
@@ -332,7 +341,7 @@ private:
{
if (index < 0)
return;
- auto& saves = *m_saves[index];
+ auto& saves = m_saves[index];
if (saves.refcount == 1)
{
saves.next_free = m_first_free;
@@ -361,7 +370,8 @@ 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_thread(const CompiledRegex::Instruction* instructions, const Iterator& pos, Codepoint cp,
+ uint16_t current_step, Thread thread, const ExecConfig& config)
{
auto failed = [this, &thread]() {
release_saves(thread.saves);
@@ -370,7 +380,6 @@ private:
m_threads.push_next(thread);
};
- auto* instructions = m_program.instructions.data();
while (true)
{
auto& inst = instructions[thread.inst++];
@@ -406,41 +415,41 @@ private:
if (pos != config.end and cp != '\n')
return consumed();
return failed();
+ case CompiledRegex::CharClass:
+ if (pos == config.end)
+ return failed();
+ return m_program.character_classes[inst.param.character_class_index].matches(cp) ? consumed() : failed();
+ case CompiledRegex::CharType:
+ if (pos == config.end)
+ return failed();
+ return is_ctype(inst.param.character_type, cp) ? consumed() : failed();
case CompiledRegex::Jump:
thread.inst = inst.param.jump_target;
break;
case CompiledRegex::Split:
- if (thread.saves >= 0)
- ++m_saves[thread.saves]->refcount;
-
- if (inst.param.split.prioritize_parent)
- m_threads.push_current({inst.param.split.target, thread.saves});
- else
+ if (auto target = inst.param.split.target;
+ instructions[target].last_step != current_step)
{
- m_threads.push_current(thread);
- thread.inst = inst.param.split.target;
+ if (thread.saves >= 0)
+ ++m_saves[thread.saves].refcount;
+ if (not inst.param.split.prioritize_parent)
+ std::swap(thread.inst, target);
+ m_threads.push_current({target, thread.saves});
}
break;
case CompiledRegex::Save:
- if (mode & RegexMode::NoSaves)
+ if constexpr (mode & RegexMode::NoSaves)
break;
if (thread.saves < 0)
- thread.saves = new_saves<false>(nullptr);
- else if (m_saves[thread.saves]->refcount > 1)
+ thread.saves = new_saves<false>(nullptr, 0);
+ else if (auto& saves = m_saves[thread.saves]; saves.refcount > 1)
{
- --m_saves[thread.saves]->refcount;
- thread.saves = new_saves<true>(m_saves[thread.saves]->pos);
+ --saves.refcount;
+ thread.saves = new_saves<true>(saves.pos, saves.valid_mask);
}
- m_saves[thread.saves]->pos[inst.param.save_index] = pos;
+ m_saves[thread.saves].pos[inst.param.save_index] = pos;
+ m_saves[thread.saves].valid_mask |= (1 << inst.param.save_index);
break;
- case CompiledRegex::CharClass:
- if (pos == config.end)
- return failed();
- return m_program.character_classes[inst.param.character_class_index].matches(cp) ? consumed() : failed();
- case CompiledRegex::CharType:
- if (pos == config.end)
- return failed();
- return is_ctype(inst.param.character_type, cp) ? consumed() : failed();
case CompiledRegex::LineAssertion:
if (not (inst.param.line_start ? is_line_start(pos, config) : is_line_end(pos, config)))
return failed();
@@ -472,10 +481,12 @@ private:
const int16_t first_inst = forward ? 0 : m_program.first_backward_inst;
m_threads.push_current({first_inst, -1});
- const auto& start_desc = forward ? m_program.forward_start_desc : m_program.backward_start_desc;
+ const auto* start_desc = (forward ? m_program.forward_start_desc : m_program.backward_start_desc).get();
+ auto next_start = pos;
constexpr bool search = mode & RegexMode::Search;
constexpr bool any_match = mode & RegexMode::AnyMatch;
+ ConstArrayView<CompiledRegex::Instruction> insts{m_program.instructions};
uint16_t current_step = -1;
m_found_match = false;
while (true) // Iterate on all codepoints and once at the end
@@ -485,20 +496,17 @@ private:
idle_func();
// We wrapped, avoid potential collision on inst.last_step by resetting them
- ConstArrayView<CompiledRegex::Instruction> instructions{m_program.instructions};
- instructions = forward ? instructions.subrange(0, m_program.first_backward_inst)
- : instructions.subrange(m_program.first_backward_inst);
-
- for (auto& inst : instructions)
+ for (auto& inst : forward ? insts.subrange(0, m_program.first_backward_inst)
+ : insts.subrange(m_program.first_backward_inst))
inst.last_step = 0;
current_step = 1; // step 0 is never valid
}
auto next = pos;
- Codepoint cp = pos != config.end ? codepoint(next, config) : -1;
+ Codepoint cp = codepoint(next, config);
while (not m_threads.current_is_empty())
- step_thread(pos, cp, current_step, m_threads.pop_current(), config);
+ step_thread(insts.pointer(), pos, cp, current_step, m_threads.pop_current(), config);
if (pos == config.end or
(m_threads.next_is_empty() and (not search or m_found_match)) or
@@ -509,38 +517,44 @@ private:
return m_found_match;
}
- pos = next;
if (search and not m_found_match)
{
- if (start_desc and m_threads.next_is_empty())
- to_next_start(pos, config, *start_desc);
- m_threads.push_next({first_inst, -1});
+ if (start_desc)
+ {
+ if (pos == next_start)
+ next_start = find_next_start(next, config, *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});
}
+ pos = next;
m_threads.swap_next();
}
}
- static void to_next_start(Iterator& start, const ExecConfig& config, const StartDesc& start_desc)
+ static Iterator find_next_start(Iterator start, const ExecConfig& config, const StartDesc& start_desc)
{
- while (start != config.end)
+ auto pos = start;
+ while (pos != config.end)
{
- static_assert(StartDesc::count <= 128, "start desc should be ascii only");
+ static_assert(StartDesc::count <= 256, "start desc should be ascii only");
if constexpr (forward)
{
- const unsigned char c = *start;
- if (start_desc.map[(c < StartDesc::count) ? c : StartDesc::other])
- return;
- utf8::to_next(start, config.end);
+ if (start_desc.map[static_cast<unsigned char>(*pos)])
+ return utf8::advance(pos, start, -CharCount(start_desc.offset));
+ ++pos;
}
else
{
- auto prev = utf8::previous(start, config.end);
- const unsigned char c = *prev;
- if (start_desc.map[(c < StartDesc::count) ? c : StartDesc::other])
- return;
- start = prev;
+ auto prev = utf8::previous(pos, config.end);
+ if (start_desc.map[static_cast<unsigned char>(*prev)])
+ return pos;
+ pos = prev;
}
}
+ return pos;
}
bool lookaround(CompiledRegex::Param::Lookaround param, Iterator pos, const ExecConfig& config) const
@@ -619,6 +633,7 @@ private:
is_word(utf8::codepoint(pos, config.subject_end));
}
+ [[gnu::flatten]]
static Codepoint codepoint(Iterator& it, const ExecConfig& config)
{
if constexpr (forward)
@@ -632,8 +647,6 @@ private:
}
}
- const CompiledRegex& m_program;
-
struct DualThreadStack
{
bool current_is_empty() const { return m_current == m_next_begin; }
@@ -660,7 +673,6 @@ private:
static_assert(initial_capacity >= 4);
m_data.reset(new Thread[initial_capacity]);
m_capacity = initial_capacity;
-
}
void grow_ifn(bool pushed_current)
@@ -707,8 +719,9 @@ private:
static constexpr bool forward = mode & RegexMode::Forward;
+ const CompiledRegex& m_program;
DualThreadStack m_threads;
- Vector<Saves*, MemoryDomain::Regex> m_saves;
+ Vector<Saves, MemoryDomain::Regex> m_saves;
int16_t m_first_free = -1;
int16_t m_captures = -1;
bool m_found_match = false;
diff --git a/src/scope.cc b/src/scope.cc
index 64bcd445..fe33f6f5 100644
--- a/src/scope.cc
+++ b/src/scope.cc
@@ -4,6 +4,16 @@
namespace Kakoune
{
+void Scope::reparent(Scope& parent)
+{
+ m_options.reparent(parent.m_options);
+ m_hooks.reparent(parent.m_hooks);
+ m_keymaps.reparent(parent.m_keymaps);
+ m_aliases.reparent(parent.m_aliases);
+ m_faces.reparent(parent.m_faces);
+ m_highlighters.reparent(parent.m_highlighters);
+}
+
GlobalScope::GlobalScope()
: m_option_registry(m_options)
{
diff --git a/src/scope.hh b/src/scope.hh
index 870e1159..b98be7bb 100644
--- a/src/scope.hh
+++ b/src/scope.hh
@@ -36,6 +36,8 @@ public:
Highlighters& highlighters() { return m_highlighters; }
const Highlighters& highlighters() const { return m_highlighters; }
+ void reparent(Scope& parent);
+
private:
friend class GlobalScope;
Scope() = default;
diff --git a/src/shared_string.cc b/src/shared_string.cc
index 7291699c..f88008b1 100644
--- a/src/shared_string.cc
+++ b/src/shared_string.cc
@@ -1,30 +1,9 @@
#include "shared_string.hh"
#include "buffer_utils.hh"
-#include <cstring>
-
namespace Kakoune
{
-StringDataPtr StringData::create(ArrayView<const StringView> strs)
-{
- const int len = accumulate(strs, 0, [](int l, StringView s) {
- return l + (int)s.length();
- });
- void* ptr = StringData::operator new(sizeof(StringData) + len + 1);
- auto* res = new (ptr) StringData(len);
- auto* data = reinterpret_cast<char*>(res + 1);
- for (auto& str : strs)
- {
- if (str.length() == 0) // memccpy(..., nullptr, 0) is UB
- continue;
- memcpy(data, str.begin(), (size_t)str.length());
- data += (int)str.length();
- }
- *data = 0;
- return RefPtr<StringData, PtrPolicy>{res};
-}
-
StringDataPtr StringData::Registry::intern(StringView str, size_t hash)
{
kak_assert(hash_value(str) == hash);
@@ -51,15 +30,16 @@ void StringData::Registry::remove(StringView str)
void StringData::Registry::debug_stats() const
{
- write_to_debug_buffer("Shared Strings stats:");
+ write_to_debug_buffer("Interned Strings stats:");
+ size_t count = m_strings.size();
size_t total_refcount = 0;
size_t total_size = 0;
- size_t count = m_strings.size();
for (auto& st : m_strings)
{
- total_refcount += (st.value->refcount & refcount_mask) - 1;
+ total_refcount += st.value->refcount & refcount_mask;
total_size += (int)st.value->length;
}
+ write_to_debug_buffer(format(" count: {}", count));
write_to_debug_buffer(format(" data size: {}, mean: {}", total_size, (float)total_size/count));
write_to_debug_buffer(format(" refcounts: {}, mean: {}", total_refcount, (float)total_refcount/count));
}
diff --git a/src/shared_string.hh b/src/shared_string.hh
index 17bb72d8..78e9fef4 100644
--- a/src/shared_string.hh
+++ b/src/shared_string.hh
@@ -7,6 +7,7 @@
#include "hash_map.hh"
#include <numeric>
+#include <cstring>
namespace Kakoune
{
@@ -32,12 +33,13 @@ private:
static void inc_ref(StringData* r, void*) noexcept { ++r->refcount; }
static void dec_ref(StringData* r, void*) noexcept
{
- if ((--r->refcount & refcount_mask) == 0)
- {
- if (r->refcount & interned_flag)
- Registry::instance().remove(r->strview());
- StringData::operator delete(r, sizeof(StringData) + r->length + 1);
- }
+ if ((--r->refcount & refcount_mask) > 0)
+ return;
+ if (r->refcount & interned_flag)
+ Registry::instance().remove(r->strview());
+ auto alloc_len = sizeof(StringData) + r->length + 1;
+ r->~StringData();
+ operator delete(r, alloc_len);
}
static void ptr_moved(StringData*, void*, void*) noexcept {}
};
@@ -57,7 +59,23 @@ public:
HashMap<StringView, StringData*, MemoryDomain::SharedString> m_strings;
};
- static Ptr create(ArrayView<const StringView> strs);
+ static Ptr create(ConvertibleTo<StringView> auto&&... strs)
+ {
+ const int len = ((int)StringView{strs}.length() + ...);
+ void* ptr = operator new(sizeof(StringData) + len + 1);
+ auto* res = new (ptr) StringData(len);
+ auto* data = reinterpret_cast<char*>(res + 1);
+ auto append = [&](StringView str) {
+ if (str.empty()) // memcpy(..., nullptr, 0) is UB
+ return;
+ memcpy(data, str.begin(), (size_t)str.length());
+ data += (int)str.length();
+ };
+ (append(strs), ...);
+ *data = 0;
+ return RefPtr<StringData, PtrPolicy>{res};
+ }
+
};
using StringDataPtr = StringData::Ptr;
diff --git a/src/shell_manager.hh b/src/shell_manager.hh
index 6b45961a..f6ef38a4 100644
--- a/src/shell_manager.hh
+++ b/src/shell_manager.hh
@@ -65,7 +65,7 @@ public:
Shell spawn(StringView cmdline,
const Context& context,
bool open_stdin,
- const ShellContext& shell_complete = {});
+ const ShellContext& shell_context = {});
Vector<String> get_val(StringView name, const Context& context) const;
diff --git a/src/string.hh b/src/string.hh
index b6b313a1..004be04c 100644
--- a/src/string.hh
+++ b/src/string.hh
@@ -21,7 +21,7 @@ public:
friend constexpr size_t hash_value(const Type& str)
{
- return hash_data(str.data(), (int)str.length());
+ return fnv1a(str.data(), (int)str.length());
}
using iterator = CharType*;
diff --git a/src/terminal_ui.cc b/src/terminal_ui.cc
index db967976..52a25e26 100644
--- a/src/terminal_ui.cc
+++ b/src/terminal_ui.cc
@@ -702,7 +702,7 @@ Optional<Key> TerminalUI::get_next_key()
static constexpr auto control = [](char c) { return c & 037; };
- static auto convert = [this](Codepoint c) -> Codepoint {
+ auto convert = [this](Codepoint c) -> Codepoint {
if (c == control('m') or c == control('j'))
return Key::Return;
if (c == control('i'))
@@ -717,7 +717,7 @@ Optional<Key> TerminalUI::get_next_key()
return Key::Escape;
return c;
};
- static auto parse_key = [](unsigned char c) -> Key {
+ auto parse_key = [&convert](unsigned char c) -> Key {
if (Codepoint cp = convert(c); cp > 255)
return Key{cp};
// Special case: you can type NUL with Ctrl-2 or Ctrl-Shift-2 or
@@ -756,7 +756,7 @@ Optional<Key> TerminalUI::get_next_key()
return mod;
};
- auto parse_csi = [this]() -> Optional<Key> {
+ auto parse_csi = [this, &convert]() -> Optional<Key> {
auto next_char = [] { return get_char().value_or((unsigned char)0xff); };
int params[16][4] = {};
auto c = next_char();
@@ -1311,7 +1311,7 @@ void TerminalUI::info_show(const DisplayLine& title, const DisplayLineList& cont
max_size.line -= m_menu.size.line;
const auto max_content_width = (m_info_max_width > 0 ? std::min(max_size.column, m_info_max_width) : max_size.column) -
- (framed ? 4 : 2) -
+ (framed ? 4 : 0) -
(assisted ? m_assistant[0].column_length() : 0);
if (max_content_width <= 0)
return;
diff --git a/src/unicode.hh b/src/unicode.hh
index a385b6eb..0acf4005 100644
--- a/src/unicode.hh
+++ b/src/unicode.hh
@@ -60,12 +60,23 @@ inline bool is_blank(Codepoint c) noexcept
is_horizontal_blank(c) ;
}
+inline bool is_basic_alpha(Codepoint c) noexcept
+{
+ return (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z');
+}
+
+inline bool is_basic_digit(Codepoint c) noexcept
+{
+ return c >= '0' and c <= '9';
+}
+
enum WordType { Word, WORD };
template<WordType word_type = Word>
inline bool is_word(Codepoint c, ConstArrayView<Codepoint> extra_word_chars = {'_'}) noexcept
{
- return iswalnum((wchar_t)c) or contains(extra_word_chars, c);
+ return (c < 128 ? is_basic_alpha(c) or is_basic_digit(c) : iswalnum((wchar_t)c)) or
+ contains(extra_word_chars, c);
}
template<>
@@ -79,16 +90,6 @@ inline bool is_punctuation(Codepoint c, ConstArrayView<Codepoint> extra_word_cha
return not (is_word(c, extra_word_chars) or is_blank(c));
}
-inline bool is_basic_alpha(Codepoint c) noexcept
-{
- return (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z');
-}
-
-inline bool is_basic_digit(Codepoint c) noexcept
-{
- return c >= '0' and c <= '9';
-}
-
inline bool is_identifier(Codepoint c) noexcept
{
return is_basic_alpha(c) or is_basic_digit(c) or
@@ -123,18 +124,18 @@ inline CharCategories categorize(Codepoint c, ConstArrayView<Codepoint> extra_wo
return CharCategories::Punctuation;
}
-inline Codepoint to_lower(Codepoint cp) noexcept { return towlower((wchar_t)cp); }
-inline Codepoint to_upper(Codepoint cp) noexcept { return towupper((wchar_t)cp); }
-
-inline bool is_lower(Codepoint cp) noexcept { return iswlower((wchar_t)cp); }
-inline bool is_upper(Codepoint cp) noexcept { return iswupper((wchar_t)cp); }
-
inline char to_lower(char c) noexcept { return c >= 'A' and c <= 'Z' ? c - 'A' + 'a' : c; }
inline char to_upper(char c) noexcept { return c >= 'a' and c <= 'z' ? c - 'a' + 'A' : c; }
inline bool is_lower(char c) noexcept { return c >= 'a' and c <= 'z'; }
inline bool is_upper(char c) noexcept { return c >= 'A' and c <= 'Z'; }
+inline Codepoint to_lower(Codepoint cp) noexcept { return cp < 128 ? (Codepoint)to_lower((char)cp) : towlower((wchar_t)cp); }
+inline Codepoint to_upper(Codepoint cp) noexcept { return cp < 128 ? (Codepoint)to_upper((char)cp) : towupper((wchar_t)cp); }
+
+inline bool is_lower(Codepoint cp) noexcept { return cp < 128 ? is_lower((char)cp) : iswlower((wchar_t)cp); }
+inline bool is_upper(Codepoint cp) noexcept { return cp < 128 ? is_upper((char)cp) : iswupper((wchar_t)cp); }
+
}
#endif // unicode_hh_INCLUDED
diff --git a/src/window.cc b/src/window.cc
index 1f444211..e296a62d 100644
--- a/src/window.cc
+++ b/src/window.cc
@@ -148,6 +148,8 @@ const DisplayBuffer& Window::update_display_buffer(const Context& context)
for (auto& line : m_display_buffer.lines())
line.trim_from(setup.widget_columns, setup.first_column, m_dimensions.column);
+ if (m_display_buffer.lines().size() > m_dimensions.line)
+ m_display_buffer.lines().resize((size_t)m_dimensions.line);
m_builtin_highlighters.highlight({context, setup, HighlightPass::Colorize, {}}, m_display_buffer, range);
diff --git a/src/word_db.cc b/src/word_db.cc
index 498d6a41..755304ca 100644
--- a/src/word_db.cc
+++ b/src/word_db.cc
@@ -223,7 +223,7 @@ UnitTest test_word_db{[]()
});
};
- auto make_lines = [](auto&&... lines) { return BufferLines{StringData::create({lines})...}; };
+ auto make_lines = [](auto&&... lines) { return BufferLines{StringData::create(lines)...}; };
Buffer buffer("test", Buffer::Flags::None,
make_lines("tchou mutch\n", "tchou kanaky tchou\n", "\n", "tchaa tchaa\n", "allo\n"));