diff options
| author | Igor Ramazanov <igor.ramazanov@protonmail.com> | 2024-06-04 23:19:47 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-06-04 23:19:47 +0200 |
| commit | 4d21fbb3b0b66ef48270e5655bf2687a1f78becf (patch) | |
| tree | 479c9ca7cd6e9fcf16d7abc7ebea8c8fe0b502e9 /src | |
| parent | 7e8c430ad0706604c0b919207f322a5c14150575 (diff) | |
| parent | 727d2391c7695056ce6bb170b127c6e6ca9e1ab4 (diff) | |
Merge branch 'mawww:master' into contrib/gendocs.sh
Diffstat (limited to 'src')
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*>(¤t_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")); |
