diff --git a/rc/base/haskell.kak b/rc/base/haskell.kak index 341602e2..666dc344 100644 --- a/rc/base/haskell.kak +++ b/rc/base/haskell.kak @@ -57,7 +57,7 @@ def -hidden haskell-indent-on-new-line %{ hook -group haskell-highlight global WinSetOption filetype=haskell %{ add-highlighter ref haskell } hook global WinSetOption filetype=haskell %{ - set buffer completion_extra_word_char "'" + set window extra_word_chars "'" hook window InsertEnd .* -group haskell-hooks haskell-filter-around-selections hook window InsertChar \n -group haskell-indent haskell-indent-on-new-line } diff --git a/rc/core/kakrc.kak b/rc/core/kakrc.kak index 43494d5a..48503993 100644 --- a/rc/core/kakrc.kak +++ b/rc/core/kakrc.kak @@ -38,7 +38,7 @@ add-highlighter -group / regions -default code kakrc \ # Add the language's grammar to the static completion list printf %s\\n "hook global WinSetOption filetype=kak %{ set window static_words '${keywords}:${attributes}:${types}:${values}' - set -- window completion_extra_word_char '-' + set -- window extra_word_chars '-' }" | sed 's,|,:,g' # Highlight keywords. Teach \b that - does not create a word boundary diff --git a/src/insert_completer.cc b/src/insert_completer.cc index dd259756..087dd00a 100644 --- a/src/insert_completer.cc +++ b/src/insert_completer.cc @@ -79,8 +79,8 @@ WordDB& get_word_db(const Buffer& buffer) template InsertCompletion complete_word(const SelectionList& sels, const OptionManager& options) { - auto& extra_word_char = options["completion_extra_word_char"].get>(); - auto is_word_pred = [extra_word_char](Codepoint c) { return is_word(c) or contains(extra_word_char, c); }; + ConstArrayView extra_word_chars = options["extra_word_chars"].get>(); + auto is_word_pred = [extra_word_chars](Codepoint c) { return is_word(c, extra_word_chars); }; const Buffer& buffer = sels.buffer(); BufferCoord cursor_pos = sels.main().cursor(); diff --git a/src/main.cc b/src/main.cc index 85d79ea7..b5b5cddc 100644 --- a/src/main.cc +++ b/src/main.cc @@ -48,7 +48,9 @@ static const char* startup_info = " * `*` will now strip surrounding whitespaces from the selection\n" " * lint/grep/make next/prev commands have been renamed to more\n" " explicit names (lint-next-error, grep-previous-match, ...)\n" -" * ctags commands have been renamed to use the ctags- prefix\n"; +" * ctags commands have been renamed to use the ctags- prefix\n" +" * completion_extra_word_char option is now extra_word_chars (note the plural form)\n" +" and is used for word selection commands\n"; struct startup_error : runtime_error { @@ -251,7 +253,7 @@ static void check_timeout(const int& timeout) throw runtime_error{"the minimum acceptable timeout is 50 milliseconds"}; } -static void check_extra_word_char(const Vector& extra_chars) +static void check_extra_word_chars(const Vector& extra_chars) { if (contains_that(extra_chars, is_blank)) throw runtime_error{"blanks are not accepted for extra completion characters"}; @@ -324,8 +326,8 @@ void register_options() reg.declare_option("debug", "various debug flags", DebugFlags::None); reg.declare_option("readonly", "prevent buffers from being modified", false); - reg.declare_option, check_extra_word_char>( - "completion_extra_word_char", + reg.declare_option, check_extra_word_chars>( + "extra_word_chars", "Additional characters to be considered as words for insert completion", {}); } diff --git a/src/selectors.cc b/src/selectors.cc index 62ef8363..597619f8 100644 --- a/src/selectors.cc +++ b/src/selectors.cc @@ -36,17 +36,24 @@ Selection utf8_range(const Utf8Iterator& first, const Utf8Iterator& last) return {first.base().coord(), last.base().coord()}; } +ConstArrayView get_extra_word_chars(const Context& context) +{ + return context.options()["extra_word_chars"].get>(); +} + } template Optional select_to_next_word(const Context& context, const Selection& selection) { + auto extra_word_chars = get_extra_word_chars(context); auto& buffer = context.buffer(); Utf8Iterator begin{buffer.iterator_at(selection.cursor()), buffer}; if (begin+1 == buffer.end()) return {}; - if (categorize(*begin) != categorize(*(begin+1))) + if (categorize(*begin, extra_word_chars) != + categorize(*(begin+1), extra_word_chars)) ++begin; if (not skip_while(begin, buffer.end(), @@ -54,10 +61,12 @@ select_to_next_word(const Context& context, const Selection& selection) return {}; Utf8Iterator end = begin+1; - if (word_type == Word and is_punctuation(*begin)) + auto is_word = [&](Codepoint c) { return Kakoune::is_word(c, extra_word_chars); }; + + if (is_word(*begin)) + skip_while(end, buffer.end(), is_word); + else if (is_punctuation(*begin)) skip_while(end, buffer.end(), is_punctuation); - else if (is_word(*begin)) - skip_while(end, buffer.end(), is_word); skip_while(end, buffer.end(), is_horizontal_blank); @@ -70,11 +79,13 @@ template Optional select_to_next_word_end(const Context& context, const Selection& selection) { + auto extra_word_chars = get_extra_word_chars(context); auto& buffer = context.buffer(); Utf8Iterator begin{buffer.iterator_at(selection.cursor()), buffer}; if (begin+1 == buffer.end()) return {}; - if (categorize(*begin) != categorize(*(begin+1))) + if (categorize(*begin, extra_word_chars) != + categorize(*(begin+1), extra_word_chars)) ++begin; if (not skip_while(begin, buffer.end(), @@ -83,10 +94,12 @@ select_to_next_word_end(const Context& context, const Selection& selection) Utf8Iterator end = begin; skip_while(end, buffer.end(), is_horizontal_blank); - if (word_type == Word and is_punctuation(*end)) + auto is_word = [&](Codepoint c) { return Kakoune::is_word(c, extra_word_chars); }; + + if (is_word(*end)) + skip_while(end, buffer.end(), is_word); + else if (is_punctuation(*end)) skip_while(end, buffer.end(), is_punctuation); - else if (is_word(*end)) - skip_while(end, buffer.end(), is_word); return utf8_range(begin, end-1); } @@ -97,22 +110,25 @@ template Optional select_to_previous_word(const Context& context, const Selection& selection) { + auto extra_word_chars = get_extra_word_chars(context); auto& buffer = context.buffer(); Utf8Iterator begin{buffer.iterator_at(selection.cursor()), buffer}; if (begin == buffer.begin()) return {}; - if (categorize(*begin) != categorize(*(begin-1))) + if (categorize(*begin, extra_word_chars) != + categorize(*(begin-1), extra_word_chars)) --begin; skip_while_reverse(begin, buffer.begin(), [](Codepoint c){ return is_eol(c); }); Utf8Iterator end = begin; - bool with_end = skip_while_reverse(end, buffer.begin(), is_horizontal_blank); - if (word_type == Word and is_punctuation(*end)) - with_end = skip_while_reverse(end, buffer.begin(), is_punctuation); + auto is_word = [&](Codepoint c) { return Kakoune::is_word(c, extra_word_chars); }; - else if (is_word(*end)) - with_end = skip_while_reverse(end, buffer.begin(), is_word); + bool with_end = skip_while_reverse(end, buffer.begin(), is_horizontal_blank); + if (is_word(*end)) + with_end = skip_while_reverse(end, buffer.begin(), is_word); + else if (is_punctuation(*end)) + with_end = skip_while_reverse(end, buffer.begin(), is_punctuation); return utf8_range(begin, with_end ? end : end+1); } @@ -124,21 +140,25 @@ Optional select_word(const Context& context, const Selection& selection, int count, ObjectFlags flags) { + auto extra_word_chars = get_extra_word_chars(context); auto& buffer = context.buffer(); + + auto is_word = [&](Codepoint c) { return Kakoune::is_word(c, extra_word_chars); }; + Utf8Iterator first{buffer.iterator_at(selection.cursor()), buffer}; - if (not is_word(*first)) + if (not is_word(*first)) return {}; Utf8Iterator last = first; if (flags & ObjectFlags::ToBegin) { - skip_while_reverse(first, buffer.begin(), is_word); - if (not is_word(*first)) + skip_while_reverse(first, buffer.begin(), is_word); + if (not is_word(*first)) ++first; } if (flags & ObjectFlags::ToEnd) { - skip_while(last, buffer.end(), is_word); + skip_while(last, buffer.end(), is_word); if (not (flags & ObjectFlags::Inner)) skip_while(last, buffer.end(), is_horizontal_blank); --last; diff --git a/src/string.cc b/src/string.cc index 89b7c3fe..1ab56481 100644 --- a/src/string.cc +++ b/src/string.cc @@ -429,7 +429,7 @@ Vector wrap_lines(StringView text, ColumnCount max_width) Vector lines; while (it != end) { - const CharCategories cat = categorize(*it); + const CharCategories cat = categorize(*it, {}); if (cat == CharCategories::EndOfLine) { lines.emplace_back(line_begin.base(), it.base()); @@ -438,7 +438,7 @@ Vector wrap_lines(StringView text, ColumnCount max_width) } Utf8It word_end = it+1; - while (word_end != end and categorize(*word_end) == cat) + while (word_end != end and categorize(*word_end, {}) == cat) ++word_end; while (word_end > line_begin and diff --git a/src/unicode.hh b/src/unicode.hh index eade3510..ba340b3a 100644 --- a/src/unicode.hh +++ b/src/unicode.hh @@ -6,6 +6,8 @@ #include #include "units.hh" +#include "array_view.hh" +#include "containers.hh" namespace Kakoune { @@ -30,13 +32,13 @@ inline bool is_blank(Codepoint c) noexcept enum WordType { Word, WORD }; template -inline bool is_word(Codepoint c) noexcept +inline bool is_word(Codepoint c, ConstArrayView extra_word_chars = {}) noexcept { - return c == '_' or iswalnum((wchar_t)c); + return c == '_' or iswalnum((wchar_t)c) or contains(extra_word_chars, c); } template<> -inline bool is_word(Codepoint c) noexcept +inline bool is_word(Codepoint c, ConstArrayView) noexcept { return not is_blank(c); } @@ -65,13 +67,13 @@ enum class CharCategories }; template -inline CharCategories categorize(Codepoint c) noexcept +inline CharCategories categorize(Codepoint c, ConstArrayView extra_word_chars) noexcept { if (is_eol(c)) return CharCategories::EndOfLine; if (is_horizontal_blank(c)) return CharCategories::Blank; - if (word_type == WORD or is_word(c)) + if (word_type == WORD or is_word(c, extra_word_chars)) return CharCategories::Word; return CharCategories::Punctuation; } diff --git a/src/word_db.cc b/src/word_db.cc index a486b86b..8bcfa250 100644 --- a/src/word_db.cc +++ b/src/word_db.cc @@ -32,7 +32,7 @@ static WordList get_words(StringView content, ConstArrayView extra_wo static ConstArrayView get_extra_word_chars(const Buffer& buffer) { - return buffer.options()["completion_extra_word_char"].get>(); + return buffer.options()["extra_word_chars"].get>(); } void WordDB::add_words(StringView line) @@ -147,7 +147,7 @@ void WordDB::update_db() void WordDB::on_option_changed(const Option& option) { - if (option.name() == "completion_extra_word_char") + if (option.name() == "extra_word_chars") rebuild_db(); } diff --git a/test/normal/extra-word-chars/cmd b/test/normal/extra-word-chars/cmd new file mode 100644 index 00000000..d81d8778 --- /dev/null +++ b/test/normal/extra-word-chars/cmd @@ -0,0 +1 @@ +ww diff --git a/test/normal/extra-word-chars/in b/test/normal/extra-word-chars/in new file mode 100644 index 00000000..ae82db18 --- /dev/null +++ b/test/normal/extra-word-chars/in @@ -0,0 +1 @@ +a-word another'one diff --git a/test/normal/extra-word-chars/rc b/test/normal/extra-word-chars/rc new file mode 100644 index 00000000..feb52869 --- /dev/null +++ b/test/normal/extra-word-chars/rc @@ -0,0 +1 @@ +set buffer extra_word_chars "-:'" diff --git a/test/normal/extra-word-chars/selections b/test/normal/extra-word-chars/selections new file mode 100644 index 00000000..05122515 --- /dev/null +++ b/test/normal/extra-word-chars/selections @@ -0,0 +1 @@ +another'one