From 8dcffd8f5a4a29d89716c114214d24803fe93d9f Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Thu, 17 Dec 2015 04:07:09 +0000 Subject: [PATCH] Initial, WIP spelling implementation Add a ranges highlighter that takes a timestamped list of ranges and associated face. Add a spell.kak file that uses aspell pipe interface to fill a range-faces option. --- rc/spell.kak | 40 ++++++++++++++++++++++++++++++++++++ src/commands.cc | 5 ++++- src/display_buffer.cc | 27 +++++++++++++++++++++++++ src/display_buffer.hh | 3 +++ src/highlighters.cc | 47 +++++++++++++++++++++++++++++++++++++++++++ src/highlighters.hh | 1 + 6 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 rc/spell.kak diff --git a/rc/spell.kak b/rc/spell.kak new file mode 100644 index 00000000..52dd20ee --- /dev/null +++ b/rc/spell.kak @@ -0,0 +1,40 @@ +decl -hidden range-faces spell_regions +decl -hidden str spell_tmp_file + +def spell %{ + try %{ addhl ranges 'spell_regions' } + %sh{ + file=$(mktemp -d -t kak-spell.XXXXXXXX)/buffer + echo "write ${file}" + echo "set buffer spell_tmp_file ${file}" + } + %sh{ + sed -ie 's/^/^/' $kak_opt_spell_tmp_file + aspell -a < $kak_opt_spell_tmp_file 2>&1 | { + line_num=1 + regions=$kak_timestamp + while read line; do + case $line in + \&*) + word=$(echo "$line" | cut -d ' ' -f 2) + begin=$(echo "$line" | cut -d ' ' -f 4 | sed 's/:$//') + end=$((begin + ${#word})) + # echo "echo -debug -- line: $line_num, word: $word, begin: $begin, end: $end" + regions="$regions:$line_num.$begin,$line_num.$end|Error" + ;; + '#'*) + word=$(echo "$line" | cut -d ' ' -f 2) + begin=$(echo "$line" | cut -d ' ' -f 3) + end=$((begin + ${#word})) + # echo "echo -debug -- line: $line_num, word: $word, begin: $begin, end: $end" + regions="$regions:$line_num.$begin,$line_num.$end|Error" + ;; + '') ((++line_num)) ;; + *) ;; + esac + done + echo "set buffer spell_regions %{$regions}" + } + rm -r $(dirname $kak_opt_spell_tmp_file) + } +} diff --git a/src/commands.cc b/src/commands.cc index dcee70cd..1df0d097 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -1104,7 +1104,8 @@ const CommandDesc declare_option_cmd = { " regex: regular expression\n" " int-list: list of integers\n" " str-list: list of character strings\n" - " line-flags: list of line flags\n", + " line-flags: list of line flags\n" + " range-faces: list of range faces\n", ParameterDesc{ { { "hidden", { false, "do not display option name when completing" } }, { "docstring", { true, "specify option description" } } }, @@ -1138,6 +1139,8 @@ const CommandDesc declare_option_cmd = { opt = ®.declare_option>(parser[1], docstring, {}, flags); else if (parser[0] == "line-flags") opt = ®.declare_option>(parser[1], docstring, {}, flags); + else if (parser[0] == "range-faces") + opt = ®.declare_option>(parser[1], docstring, {}, flags); else throw runtime_error(format("unknown type {}", parser[0])); diff --git a/src/display_buffer.cc b/src/display_buffer.cc index 61078657..c112679d 100644 --- a/src/display_buffer.cc +++ b/src/display_buffer.cc @@ -9,6 +9,33 @@ namespace Kakoune { +String option_to_string(BufferRange range) +{ + return format("{}.{},{}.{}", + range.begin.line+1, range.begin.column+1, + range.end.line+1, range.end.column+1); +} + +void option_from_string(StringView str, BufferRange& opt) +{ + auto comma = find(str, ','); + auto dot_begin = find(StringView{str.begin(), comma}, '.'); + auto dot_end = find(StringView{comma, str.end()}, '.'); + + if (comma == str.end() or dot_begin == comma or dot_end == str.end()) + throw runtime_error(format("'{}' does not follow .,. format", str)); + + ByteCoord begin{str_to_int({str.begin(), dot_begin}) - 1, + str_to_int({dot_begin+1, comma}) - 1}; + + ByteCoord end{str_to_int({comma+1, dot_end}) - 1, + str_to_int({dot_end+1, str.end()}) - 1}; + + opt.begin = begin; + opt.end = end; +} + + StringView DisplayAtom::content() const { switch (m_type) diff --git a/src/display_buffer.hh b/src/display_buffer.hh index 177a5920..14e7044e 100644 --- a/src/display_buffer.hh +++ b/src/display_buffer.hh @@ -13,6 +13,9 @@ namespace Kakoune class Buffer; struct BufferRange{ ByteCoord begin, end; }; +String option_to_string(BufferRange range); +void option_from_string(StringView str, BufferRange& opt); + inline bool operator==(const BufferRange& lhs, const BufferRange& rhs) { return lhs.begin == rhs.begin and lhs.end == rhs.end; diff --git a/src/highlighters.cc b/src/highlighters.cc index c88f078d..8b0d669d 100644 --- a/src/highlighters.cc +++ b/src/highlighters.cc @@ -982,6 +982,48 @@ HighlighterAndId create_flag_lines_highlighter(HighlighterParameters params) return {"hlflags_" + params[1], make_simple_highlighter(func) }; } +HighlighterAndId create_ranges_highlighter(HighlighterParameters params) +{ + if (params.size() != 1) + throw runtime_error("wrong parameter count"); + + const String& option_name = params[0]; + + // throw if wrong option type + GlobalScope::instance().options()[option_name].get>(); + + auto func = [=](const Context& context, HighlightFlags flags, + DisplayBuffer& display_buffer, BufferRange) + { + auto& range_and_faces = context.options()[option_name].get_mutable>(); + auto& ranges = range_and_faces.list; + + auto& buffer = context.buffer(); + if (range_and_faces.timestamp != buffer.timestamp()) + { + // TODO: update ranges to current timestamp + return; + } + + for (auto& range : ranges) + { + try + { + auto& r = std::get<0>(range); + if (not buffer.is_valid(r.begin) or not buffer.is_valid(r.end)) + continue; + + highlight_range(display_buffer, r.begin, r.end, true, + apply_face(get_face(std::get<1>(range)))); + } + catch (runtime_error&) + {} + } + }; + + return {"hlranges_" + params[1], make_simple_highlighter(func) }; +} + HighlighterAndId create_highlighter_group(HighlighterParameters params) { if (params.size() != 1) @@ -1441,6 +1483,11 @@ void register_highlighters() "Parameters: