Introduce the 'completion_extra_word_chars' option

This string option is used to get all the additional characters
that should be considered as "word" character for the purpose of
insert mode completion.

Fixes #758
This commit is contained in:
Maxime Coste 2016-08-21 20:25:11 +01:00
parent 9124851029
commit 3f0415c765
6 changed files with 72 additions and 13 deletions

View File

@ -899,6 +899,9 @@ Some options are built in Kakoune, and can be used to control it's behaviour:
where the completion apply in the buffer. where the completion apply in the buffer.
* `static_words` _str-list_: list of words that are always added to completion * `static_words` _str-list_: list of words that are always added to completion
candidates when completing words in insert mode. candidates when completing words in insert mode.
* `completions_extra_word_chars` _str_: a string containing all additional character
that should be considered as word character for the purpose of insert mode
completion.
* `autoreload` _enum(yes|no|ask)_: auto reload the buffers when an external * `autoreload` _enum(yes|no|ask)_: auto reload the buffers when an external
modification is detected. modification is detected.
* `debug` _flags(hooks|shell|profile)_: dump various debug information in * `debug` _flags(hooks|shell|profile)_: dump various debug information in

View File

@ -124,9 +124,13 @@ Builtin options
to define where the completion apply in the buffer, and the to define where the completion apply in the buffer, and the
other strings are the candidates other strings are the candidates
*static_words* 'str-list'::: *static_words* 'str-list'::
list of words that are always added to completion candidates list of words that are always added to completion candidates
when completing words in insert mode when completing words in insert mode
*completions_extra_word_chars* 'str'::
a string containing all additional character that should be considered
as word character for the purpose of insert mode completion.
*autoreload* 'enum(yes|no|ask)':: *autoreload* 'enum(yes|no|ask)'::
auto reload the buffers when an external modification is detected auto reload the buffers when an external modification is detected

View File

@ -76,12 +76,15 @@ WordDB& get_word_db(const Buffer& buffer)
template<bool other_buffers> template<bool other_buffers>
InsertCompletion complete_word(const SelectionList& sels, const OptionManager& options) InsertCompletion complete_word(const SelectionList& sels, const OptionManager& options)
{ {
StringView extra_word_char = options["completion_extra_word_char"].get<String>();
auto is_word_pred = [extra_word_char](Codepoint c) { return is_word(c) or contains(extra_word_char, c); };
const Buffer& buffer = sels.buffer(); const Buffer& buffer = sels.buffer();
ByteCoord cursor_pos = sels.main().cursor(); ByteCoord cursor_pos = sels.main().cursor();
using Utf8It = utf8::iterator<BufferIterator>; using Utf8It = utf8::iterator<BufferIterator>;
Utf8It pos{buffer.iterator_at(cursor_pos), buffer}; Utf8It pos{buffer.iterator_at(cursor_pos), buffer};
if (pos == buffer.begin() or not is_word(*(pos-1))) if (pos == buffer.begin() or not is_word_pred(*(pos-1)))
return {}; return {};
ByteCoord word_begin; ByteCoord word_begin;
@ -92,7 +95,7 @@ InsertCompletion complete_word(const SelectionList& sels, const OptionManager& o
Utf8It end{buffer.iterator_at(sels[i].cursor()), buffer}; Utf8It end{buffer.iterator_at(sels[i].cursor()), buffer};
Utf8It begin = end-1; Utf8It begin = end-1;
if (not skip_while_reverse(begin, buffer.begin(), if (not skip_while_reverse(begin, buffer.begin(),
[](Codepoint c) { return is_word(c); })) [&](Codepoint c) { return is_word_pred(c); }))
++begin; ++begin;
if (i == sels.main_index()) if (i == sels.main_index())
@ -101,7 +104,7 @@ InsertCompletion complete_word(const SelectionList& sels, const OptionManager& o
prefix = buffer.string(word_begin, end.base().coord()); prefix = buffer.string(word_begin, end.base().coord());
} }
skip_while(end, buffer.end(), [](Codepoint c) { return is_word(c); }); skip_while(end, buffer.end(), [&](Codepoint c) { return is_word_pred(c); });
auto word = buffer.string(begin.base().coord(), end.base().coord()); auto word = buffer.string(begin.base().coord(), end.base().coord());
++sel_word_counts[word]; ++sel_word_counts[word];

View File

@ -266,6 +266,9 @@ void register_options()
"%val{bufname} %val{cursor_line}:%val{cursor_char_column} "_str); "%val{bufname} %val{cursor_line}:%val{cursor_char_column} "_str);
reg.declare_option("debug", "various debug flags", DebugFlags::None); reg.declare_option("debug", "various debug flags", DebugFlags::None);
reg.declare_option("readonly", "prevent buffers from being modified", false); reg.declare_option("readonly", "prevent buffers from being modified", false);
reg.declare_option("completion_extra_word_char",
"Additional characters to be considered as words for insert completion",
""_str);
} }
struct convert_to_client_mode struct convert_to_client_mode

View File

@ -10,7 +10,7 @@ namespace Kakoune
using WordList = Vector<StringView>; using WordList = Vector<StringView>;
static WordList get_words(StringView content) static WordList get_words(StringView content, StringView extra_word_chars)
{ {
WordList res; WordList res;
using Utf8It = utf8::iterator<const char*>; using Utf8It = utf8::iterator<const char*>;
@ -19,7 +19,7 @@ static WordList get_words(StringView content)
for (Utf8It it{word_start, content}, end{content.end(), content}; it != end; ++it) for (Utf8It it{word_start, content}, end{content.end(), content}; it != end; ++it)
{ {
Codepoint c = *it; Codepoint c = *it;
const bool word = is_word(c); const bool word = is_word(c) or contains(extra_word_chars, c);
if (not in_word and word) if (not in_word and word)
{ {
word_start = it.base(); word_start = it.base();
@ -36,9 +36,14 @@ static WordList get_words(StringView content)
return res; return res;
} }
static StringView get_extra_word_chars(const Buffer& buffer)
{
return buffer.options()["completion_extra_word_char"].get<String>();
}
void WordDB::add_words(StringView line) void WordDB::add_words(StringView line)
{ {
for (auto& w : get_words(line)) for (auto& w : get_words(line, get_extra_word_chars(*m_buffer)))
{ {
auto it = m_words.find(w); auto it = m_words.find(w);
if (it == m_words.end()) if (it == m_words.end())
@ -56,7 +61,7 @@ void WordDB::add_words(StringView line)
void WordDB::remove_words(StringView line) void WordDB::remove_words(StringView line)
{ {
for (auto& w : get_words(line)) for (auto& w : get_words(line, get_extra_word_chars(*m_buffer)))
{ {
auto it = m_words.find(w); auto it = m_words.find(w);
kak_assert(it != m_words.end() and it->second.refcount > 0); kak_assert(it != m_words.end() and it->second.refcount > 0);
@ -66,14 +71,44 @@ void WordDB::remove_words(StringView line)
} }
WordDB::WordDB(const Buffer& buffer) WordDB::WordDB(const Buffer& buffer)
: m_buffer{&buffer}, m_timestamp{buffer.timestamp()} : m_buffer{&buffer}
{ {
buffer.options().register_watcher(*this);
rebuild_db();
}
WordDB::WordDB(WordDB&& other)
: m_buffer{std::move(other.m_buffer)},
m_lines{std::move(other.m_lines)},
m_words{std::move(other.m_words)},
m_timestamp{other.m_timestamp}
{
kak_assert(m_buffer);
m_buffer->options().unregister_watcher(other);
other.m_buffer = nullptr;
m_buffer->options().register_watcher(*this);
}
WordDB::~WordDB()
{
if (m_buffer)
m_buffer->options().unregister_watcher(*this);
}
void WordDB::rebuild_db()
{
auto& buffer = *m_buffer;
m_words.clear();
m_lines.clear();
m_lines.reserve((int)buffer.line_count()); m_lines.reserve((int)buffer.line_count());
for (auto line = 0_line, end = buffer.line_count(); line < end; ++line) for (auto line = 0_line, end = buffer.line_count(); line < end; ++line)
{ {
m_lines.push_back(buffer.line_storage(line)); m_lines.push_back(buffer.line_storage(line));
add_words(m_lines.back()->strview()); add_words(m_lines.back()->strview());
} }
m_timestamp = buffer.timestamp();
} }
void WordDB::update_db() void WordDB::update_db()
@ -118,6 +153,12 @@ void WordDB::update_db()
m_lines = std::move(new_lines); m_lines = std::move(new_lines);
} }
void WordDB::on_option_changed(const Option& option)
{
if (option.name() == "completion_extra_word_char")
rebuild_db();
}
int WordDB::get_word_occurences(StringView word) const int WordDB::get_word_occurences(StringView word) const
{ {
auto it = m_words.find(word); auto it = m_words.find(word);

View File

@ -13,12 +13,13 @@ namespace Kakoune
using RankedMatchList = Vector<RankedMatch>; using RankedMatchList = Vector<RankedMatch>;
// maintain a database of words available in a buffer // maintain a database of words available in a buffer
class WordDB class WordDB : public OptionManagerWatcher
{ {
public: public:
WordDB(const Buffer& buffer); WordDB(const Buffer& buffer);
~WordDB();
WordDB(const WordDB&) = delete; WordDB(const WordDB&) = delete;
WordDB(WordDB&&) = default; WordDB(WordDB&&);
RankedMatchList find_matching(StringView str); RankedMatchList find_matching(StringView str);
@ -28,6 +29,10 @@ private:
void add_words(StringView line); void add_words(StringView line);
void remove_words(StringView line); void remove_words(StringView line);
void rebuild_db();
void on_option_changed(const Option& option) override;
struct WordInfo struct WordInfo
{ {
StringDataPtr word; StringDataPtr word;