diff --git a/src/buffer.cc b/src/buffer.cc index 0587cf45..2d2e106c 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -227,6 +227,9 @@ void Buffer::apply_modification(const BufferModification& modification) void Buffer::append_modification(BufferModification&& modification) { + for (auto filter : m_filters) + filter.second(*this, modification); + apply_modification(modification); m_current_undo_group.push_back(std::move(modification)); } @@ -277,4 +280,22 @@ void Buffer::unregister_modification_listener(BufferModificationListener* listen m_modification_listeners.erase(it); } +void Buffer::add_filter(FilterAndId&& filter) +{ + if (m_filters.contains(filter.first)) + throw filter_id_not_unique(filter.first); + m_filters.append(filter); +} + +void Buffer::remove_filter(const std::string& id) +{ + m_filters.remove(id); +} + +CandidateList Buffer::complete_filterid(const std::string& prefix, + size_t cursor_pos) +{ + return m_filters.complete_id(prefix, cursor_pos); +} + } diff --git a/src/buffer.hh b/src/buffer.hh index 5a336f23..0221e46a 100644 --- a/src/buffer.hh +++ b/src/buffer.hh @@ -7,6 +7,10 @@ #include #include "line_and_column.hh" +#include "filter.hh" +#include "exception.hh" +#include "completion.hh" +#include "idvaluemap.hh" namespace Kakoune { @@ -149,6 +153,18 @@ public: void register_modification_listener(BufferModificationListener* listener); void unregister_modification_listener(BufferModificationListener* listener); + struct filter_id_not_unique : public runtime_error + { + filter_id_not_unique(const std::string& id) + : runtime_error("filter id not unique: " + id) {} + }; + + void add_filter(FilterAndId&& filter); + void remove_filter(const std::string& id); + + CandidateList complete_filterid(const std::string& prefix, + size_t cursor_pos = std::string::npos); + // returns an iterator pointing to the first character of the line // iterator is on BufferIterator iterator_at_line_begin(const BufferIterator& iterator) const; @@ -190,6 +206,8 @@ private: size_t m_last_save_undo_index; std::vector m_modification_listeners; + + idvaluemap m_filters; }; } diff --git a/src/filter.hh b/src/filter.hh new file mode 100644 index 00000000..3368ee5c --- /dev/null +++ b/src/filter.hh @@ -0,0 +1,18 @@ +#ifndef filter_hh_INCLUDED +#define filter_hh_INCLUDED + +#include +#include + +namespace Kakoune +{ + +class Buffer; +class BufferModification; + +typedef std::function FilterFunc; +typedef std::pair FilterAndId; + +} + +#endif // filter_hh_INCLUDED diff --git a/src/filter_registry.cc b/src/filter_registry.cc new file mode 100644 index 00000000..d6a71ba0 --- /dev/null +++ b/src/filter_registry.cc @@ -0,0 +1,38 @@ +#include "filter_registry.hh" + +#include "exception.hh" +#include "buffer.hh" + +namespace Kakoune +{ + +struct factory_not_found : public runtime_error +{ + factory_not_found() : runtime_error("filter factory not found") {} +}; + +void FilterRegistry::register_factory(const std::string& name, + const FilterFactory& factory) +{ + assert(not m_factories.contains(name)); + m_factories.append(std::make_pair(name, factory)); +} + +void FilterRegistry::add_filter_to_buffer(Buffer& buffer, + const std::string& name, + const FilterParameters& parameters) +{ + auto it = m_factories.find(name); + if (it == m_factories.end()) + throw factory_not_found(); + + buffer.add_filter(it->second(buffer, parameters)); +} + +CandidateList FilterRegistry::complete_filter(const std::string& prefix, + size_t cursor_pos) +{ + return m_factories.complete_id(prefix, cursor_pos); +} + +} diff --git a/src/filter_registry.hh b/src/filter_registry.hh new file mode 100644 index 00000000..4fbb8df7 --- /dev/null +++ b/src/filter_registry.hh @@ -0,0 +1,42 @@ +#ifndef filter_registry_h_INCLUDED +#define filter_registry_h_INCLUDED + +#include +#include + +#include "filter.hh" +#include "utils.hh" +#include "completion.hh" +#include "idvaluemap.hh" + +namespace Kakoune +{ + +class Window; + +typedef std::vector FilterParameters; + +typedef std::function FilterFactory; + +class FilterRegistry : public Singleton +{ +public: + void register_factory(const std::string& name, + const FilterFactory& factory); + + void add_filter_to_buffer(Buffer& window, + const std::string& factory_name, + const FilterParameters& parameters); + + CandidateList complete_filter(const std::string& prefix, + size_t cursor_pos); + +private: + idvaluemap m_factories; +}; + +} + +#endif // filter_registry_h_INCLUDED + diff --git a/src/filters.cc b/src/filters.cc new file mode 100644 index 00000000..73ae91cb --- /dev/null +++ b/src/filters.cc @@ -0,0 +1,45 @@ +#include "filters.hh" +#include "filter_registry.hh" +#include "buffer.hh" + +namespace Kakoune +{ + +void preserve_indent(Buffer& buffer, BufferModification& modification) +{ + if (modification.type == BufferModification::Insert and + modification.content == "\n") + { + BufferIterator line_begin = buffer.iterator_at_line_begin(modification.position - 1); + BufferIterator first_non_white = line_begin; + while ((*first_non_white == '\t' or *first_non_white == ' ') and + not first_non_white.is_end()) + ++first_non_white; + + modification.content += buffer.string(line_begin, first_non_white); + } +} + +template +class SimpleFilterFactory +{ +public: + SimpleFilterFactory(const std::string& id) : m_id(id) {} + + FilterAndId operator()(Buffer& buffer, + const FilterParameters& params) const + { + return FilterAndId(m_id, FilterFunc(filter_func)); + } +private: + std::string m_id; +}; + +void register_filters() +{ + FilterRegistry& registry = FilterRegistry::instance(); + + registry.register_factory("preserve_indent", SimpleFilterFactory("preserve_indent")); +} + +} diff --git a/src/filters.hh b/src/filters.hh new file mode 100644 index 00000000..0d88da46 --- /dev/null +++ b/src/filters.hh @@ -0,0 +1,11 @@ +#ifndef filters_hh_INCLUDED +#define filters_hh_INCLUDED + +namespace Kakoune +{ + +void register_filters(); + +} + +#endif // filters_hh_INCLUDED diff --git a/src/main.cc b/src/main.cc index 36cf3445..3b290854 100644 --- a/src/main.cc +++ b/src/main.cc @@ -9,6 +9,8 @@ #include "debug.hh" #include "highlighters.hh" #include "highlighter_registry.hh" +#include "filters.hh" +#include "filter_registry.hh" #include "hooks_manager.hh" #include @@ -458,6 +460,32 @@ void rm_highlighter(const CommandParameters& params, const Context& context) context.window->remove_highlighter(params[0]); } +void add_filter(const CommandParameters& params, const Context& context) +{ + if (params.size() < 1) + throw wrong_argument_count(); + + try + { + FilterRegistry& registry = FilterRegistry::instance(); + FilterParameters filter_params(params.begin()+1, params.end()); + registry.add_filter_to_buffer(*context.buffer, params[0], + filter_params); + } + catch (runtime_error& err) + { + print_status("error: " + err.description()); + } +} + +void rm_filter(const CommandParameters& params, const Context& context) +{ + if (params.size() != 1) + throw wrong_argument_count(); + + context.buffer->remove_filter(params[0]); +} + void add_hook(const CommandParameters& params, const Context& context) { if (params.size() < 3) @@ -678,6 +706,7 @@ int main(int argc, char* argv[]) BufferManager buffer_manager; RegisterManager register_manager; HighlighterRegistry highlighter_registry; + FilterRegistry filter_registry; HooksManager hooks_manager; command_manager.register_command(std::vector{ "e", "edit" }, edit, @@ -701,12 +730,22 @@ int main(int argc, char* argv[]) [&](const std::string& prefix, size_t cursor_pos) { return main_context.window->complete_highlighterid(prefix, cursor_pos); } }); + command_manager.register_command(std::vector{ "af", "addfilter" }, add_filter, + PerArgumentCommandCompleter { + std::bind(&FilterRegistry::complete_filter, &filter_registry, _1, _2) + }); + command_manager.register_command(std::vector{ "rf", "rmfilter" }, rm_filter, + PerArgumentCommandCompleter { + [&](const std::string& prefix, size_t cursor_pos) + { return main_context.buffer->complete_filterid(prefix, cursor_pos); } + }); command_manager.register_command(std::vector{ "hook" }, add_hook); command_manager.register_command(std::vector{ "source" }, exec_commands_in_file, PerArgumentCommandCompleter{ complete_filename }); register_highlighters(); + register_filters(); try {